Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 588
Marco Romano Avatar asked Sep 01 '16 06:09

Marco Romano


People also ask

What is Dagger dependency injection Android?

Dagger automatically generates code that mimics the code you would otherwise have hand-written. Because the code is generated at compile time, it's traceable and more performant than other reflection-based solutions such as Guice. Note: Use Hilt for dependency injection on Android.

What does @singleton annotation do?

@Singleton (and any other scope annotation) makes your class a single instance in your dependencies graph (it means that this instance will be "singleton" as long as Component object exists).

How do you inject a Dagger in fragment?

onCreate() , an activity attaches fragments that might want to access activity bindings. When using fragments, inject Dagger in the fragment's onAttach() method. In this case, it can be done before or after calling super. onAttach() .

How does a Dagger work internally?

To instantiate the dependency, Dagger will go through a class using the @Module annotation. Inside it, it will be instantiated using a method using the @Provides annotation. The @Singleton annotation ensures that the dependency is instantiated only once.


2 Answers

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.

like image 52
Kevin Krumwiede Avatar answered Oct 21 '22 16:10

Kevin Krumwiede


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?

like image 21
mromer Avatar answered Oct 21 '22 17:10

mromer