Simplest way to create a Singleton w/ Dagger 2?
This is a simplified (not completely precise -- check links below) text gradually explaining Dagger2 and the idea behind it. My hopes are that you'll be able to read and understand other details on Dagger2 after this text.
Don't focus on the term singleton. Dagger2 (forget dagger and use dagger2) exists to enforce object scope. Not the scope we usually talk about (classes, methods, loops) but the scope on an architectural level (you define those layers).
Some typical layers in Android are Application, Activity and Fragment. As you know, your android app gets only one instance of Application class. Many instances of Activity class and many instances of Fragment class.
You want to keep your app nice and clean and you want to keep your objects right where they belong (enforce semantics). These objects need to be created somewhere (usually you have to type factory classes/methods/projects --last one was a joke), that creation utility needs to be called somewhere and you need to pass those objects from somewhere to where they belong!
That's a lot of somewheres to type (usually classes with strange names) and passing down those objects from where they were created to where they belong can be quite a challenge. Especially when many different classes use that special object of yours.
Dagger2 to the rescue! Basically there are two terms you need to understand. Components and Modules.
Components are here to inject. They inject your classes with objects they require to be constructed. Components only inject, they don't create the objects. So who creates objects?
Modules create objects that Components inject into classes that need to be constructed. Modules are full of provideInsertName methods. These methods create objects that you need to pass to classes that need them. A provide method will always create a new object or it will reuse (singleton) already created object if that provide method is annotated with @Scope (you'll need to type a new scope annotation but that's detail). Names of provide methods don't really matter. What matters is the return type of these methods (just remember that information it will be useful later).
When you type your Component you need to say which Modules are associated with that Component and during Component instantiation you will have to pass associated Module instances to that Component constructor. Together, Components and Modules form a mean injecting machine.
Instantiate your Component (Dagger2 generates builder classes for your Components) in the layer you feel that Component should inject. For instance, you create an ApplicationComponent instance inside Application class. Objects injected by ApplicationComponent are created only once and they exist while ApplicationComponent instance exists (during injection they will be fetched, not recreated). ApplicationComponent instance exists while Application instance exists (so in Android enviroment that's basically always/during app lifetime).
You can repeat the same story with Activity classes. Instantiate ActivityComponent inside your Activity class, that Component exists while that Activity exists. Notice how objects injected by ActivityComponent exist only while ActivityComponent (Activity class instance) exists. That's the beauty of Dagger2. Objects that belong to Activity layers are literally scoped to the Activity layer.
Note: Components can depend on each other. Your ActivityComponent can depend on your ApplicationComponent so Activity layer can use objects from Application layer (but not other way around, that's bad design). This way, Components form a dependency tree which makes object fetching and injection very efficient.
After reading this (my congratulations good sir) I recommend checking out this link and checking out Jake Wharton's talk on Dagger2.
You only need modules for things that you can't annotate with @Inject
constructor (because for example, the framework creates it for you - like context). If you can't add an @Inject constructor, you also need to specify a void inject(...)
method in the component as well.
However, if you can create it with a @Inject
constructor, then @Inject
works as a field annotation as well
@Component(modules={ContextModule.class})
@Singleton
public interface SingletonComponent {
void inject(MainActivity mainActivity);
}
@Module
public class ContextModule {
Context context;
public ContextModule(Context context) {
this.context = context;
}
@Provides
Context context() {
return context;
}
}
@Singleton
public class MyOtherSingleton {
@Inject
public MyOtherSingleton() {
}
}
@Singleton
public class MySingleton {
@Inject Context context;
@Inject MyOtherSingleton myOtherSingleton;
@Inject
public MySingleton() {
}
}
You can also do constructor parameters
private final Context context;
private final MyOtherSingleton myOtherSingleton;
@Inject
public MySingleton(Context context, MyOtherSingleton myOtherSingleton) {
this.context = context;
this.myOtherSingleton = myOtherSingleton;
}
SingletonComponent singletonComponent = DaggerSingletonComponent.builder()
.contextModule(CustomApplication.this)
.build();
// and elsewhere
@Inject
MySingleton mySingleton;
// ...
singletonComponent.inject(this);
Verified to work:
08-23 04:39:28.418 com.zhuinden.rxrealm D/DogView: My singleton has [com.zhuinden.rxrealm.application.CustomApplication@53348a58] and [com.zhuinden.rxrealm.application.injection.test.MyOtherSingleton@5336bb74]
08-23 04:39:36.422 com.zhuinden.rxrealm D/CatView: My singleton has [com.zhuinden.rxrealm.application.CustomApplication@53348a58] and [com.zhuinden.rxrealm.application.injection.test.MyOtherSingleton@5336bb74]