Dagger 2 on Android. Different ways to store and access a @Singleton Component
This is the Nth question about how to store @Singleton scoped Dagger 2 Components whose lifetime should equal the application's lifetime.
In Android apps using Dagger 2 there is usually at least one Component which is @Singleton scoped and should last for all the application's lifetime: because of these requirements it is usually initialised and stored inside a custom Application class.
Since the instance of this Component must be reachable in all parts of our Application I've seen code like this:
1. Store the component in a public static variable inside the application class.
public class App extends Application {
public static AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this)).build();
}
}
This way it can be accessed anywhere else with:
App.appComponent.inject(this);
2. Store the component in a private variable inside the application instance and create a static accessor for it.
public class App extends Application {
private static AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this)).build();
}
public static AppComponent getAppComponent() {
return appComponent;
}
}
This way it can be accessed anywhere else with:
App.getAppComponent().inject(this);
3. Store the component in a private variable inside the application instance and create a non static accessor for it.
public class App extends Application {
private AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this)).build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
This way it can be accessed only from class instances which hold a reference to a Context:
// From within an Activity.
((App) getApplication()).getAppComponent().inject(this);
// From within a Fragment.
((App) getActivity().getApplication()).getAppComponent().inject(this);
// From within any other class which holds a reference to a Context.
((App) context.getApplicationContext()).getAppComponent().inject(this);
This last way makes it pretty much compulsory to pass a Context reference to any class willing to access the Component (even if that Context isn't needed by that class for any other purposes).
IMHO having to "manually inject" a Context instance only to access the injector itself sounds a bit counter intuitive.
On the other side many advise against using static variables but: why? If an object must stay in memory for the application's lifetime (which means for the whole lifetime of the JVM instance) what's the problem if it's stored in a static variable?
Others say that static stuff can't be mocked in tests and it's true, though I'm not sure I totally get this because it is the DI pattern which enables easy mocking/testing and not the injector itself, so why would we want to mock the injector itself?
What are the pros and cons of these alternatives? Are there any other possible alternatives besides the ones already mentioned here?
I use method #2. The main problem with method #1 is that you're exposing a mutable field. If your module doesn't require a Context
to construct, you could make the field final
. But as a matter of style, I still prefer not to expose fields.
You should normally avoid global state, especially in Android because of the complex and sometimes unintuitive lifecycles of components and the VM itself. But Application
is the exception to this rule. Exactly one instance of it exists per VM, and its onCreate()
method is called exactly once, before any other component is created. This makes it an acceptable place to create and store a static singleton.
With 1 and 2 you are using static references. This is a good thread about why to avoid them
Why are static variables considered evil?
So the only option left is the 3rd. That is what I am using on my projects. About if you should pass the context as argument or not, depends on the architecture of your project and how you designed the Dagger dependencies. Personally I don't have that problem because I am only injecting in Activities/Fragments. Can you give me an example where you need to pass the context to inject dependencies?
链接地址: http://www.djcxy.com/p/36036.html