Dagger 2 is great, but it can be intimidating! It’s also difficult to find current information. This post is intended to help fix that by using a raw—“just show me what to change!—format. Here’s how I set up Dagger in a recent project. This example is in Java (as was required for my Udacity capstone project).
Initial Setup
First, add Dagger to your app’s build.gradle
as shown below.
Second, create an Application
class, if one doesn’t already exist, and add it to your AndroidManifest.xml
. It should extend DaggerApplication and implement the abstract method applicationInjector()
. Ignore the Cannot resolve symbol error; DaggerAppComponent
will be generated later.
Then, create a package to contain your top-level Dagger code, such as di (for dependency injection). Inside di, add a new interface called AppComponent
. It should be annotated with both @Singleton
and @Component
. The latter should specify a module list that includes AndroidInjectionModule. It should also include an abstract Factory class annotated with @Component.Factory
that implements AndroidInjector.Factory
with your Application class as the generic type argument.
1 2 3 4 |
implementation 'com.google.dagger:dagger-android:2.23.2' implementation 'com.google.dagger:dagger-android-support:2.23.2' annotationProcessor 'com.google.dagger:dagger-android-processor:2.23.2' annotationProcessor 'com.google.dagger:dagger-compiler:2.23.2' |
1 2 3 4 5 6 |
public class MyApp extends DaggerApplication { @Override protected AndroidInjector<? extends DaggerApplication> applicationInjector() { return DaggerAppComponent.factory().create(this); } } |
1 2 3 4 5 |
<?xml version="1.0" encoding="utf-8"?> <manifest> <application android:name=".MyApp" ... |
1 2 3 4 5 6 7 |
@Singleton @Component(modules = {AndroidInjectionModule.class}) public interface AppComponent extends AndroidInjector<MyApp> { @Component.Factory abstract class Factory implements AndroidInjector.Factory<MyApp> { } } |
Rebuild your project and expect a cannot find symbol error in your Application class. Open it to add the missing import.
Custom Scopes
Next, create the ScopeActivity
and ScopeFragment
annotation types in your di package. Much like how @Singleton tells Dagger to keep instances around for the life of our Application, these scopes are for the life of our Activities and Fragments.
1 2 3 4 5 |
@Documented @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ScopeActivity { } |
1 2 3 4 5 |
@Scope @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface ScopeFragment { } |
Activities
Add an abstract inner class in AppComponent. Annotate it with @Module
and add it to the @Component
module list. By adding an abstract method that returns our MainActivity
and annotating with ContributesAndroidInjector
, we’ll cause Dagger to generate the necessary classes to inject into our Activity. The method name doesn’t matter, but stick to a convention. Also, edit your Activity so it extends DaggerAppCompatActivity
. Before onCreate()
is called, injected values will be assigned.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@Singleton @Component(modules = {AndroidInjectionModule.class, ActivityBindingModule.class}) public interface AppComponent extends AndroidInjector<MyApp> { @Component.Factory abstract class Factory implements AndroidInjector.Factory<MyApp> { } @Module abstract class ActivityBindingModule { @ScopeActivity @ContributesAndroidInjector abstract MainActivity contributeMainActivity(); } } |
Injection
Now we’re ready! We’ll do our first silly injection example using a String. Our inner class will include a @Provides
annotated method that returns a String value. Again, the name of the method doesn’t matter. In case we later have multiple String values to inject, we include a @Named
annotation to allow identifying the one we want. Edit your Activity to inject the value.
As your project grows, you’ll want ActivityBindingModule to become a top-level class alongside other modules that contribute to the object graph. Keeping them together makes it easier when you’re just getting started.
1 2 3 4 5 6 7 8 |
@Module abstract class ActivityBindingModule { ... @Provides @Named("test1") static String provideTestString1() { return "test of injected string"; } |
1 2 3 4 5 6 7 8 9 10 11 |
public class MainActivity extends DaggerAppCompatActivity { @Inject @Named("test1") String testString1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.v(LOG_TAG, "injected value: " + testString1); } } |
Fragments
To inject into Fragments, I like to include a module class in the same package as the Fragment (as in the Google I/O 2018 App). For now, you can put it inside AppComponent. Add it to the module list of the @ContributesAndroidInjector annotation of its parent Activity. Edit the Fragment to extend DaggerFragment
and add the injected String. Before onAttach()
is called, injected values will be assigned.
1 2 3 4 5 6 |
@Module abstract class ActivityBindingModule { @ScopeActivity @ContributesAndroidInjector(modules = {MainModule.class}) abstract MainActivity contributeMainActivity(); } |
1 2 3 4 5 6 |
@Module public abstract class MainModule { @ScopeFragment @ContributesAndroidInjector abstract MainFragment contributeMainFragment(); } |
1 2 3 4 5 6 7 8 9 10 11 |
public class MainFragment extends DaggerFragment { @Inject @Named("test1") String testString1; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.v(LOG_TAG, "injected value: " + testString1); } } |
ViewModels
Injecting into ViewModels requires a couple new classes. I recommend just pasting them into the di package.
ViewModelFactory (Rename GithubViewModelFactory to ViewModelFactory)
ViewModelKey
Add another abstract method to a new module class and add it to the modules list for contributeMainActivity().
Now we need to contribute our ViewModel into Dagger’s injectable multibound map so the factory can find it. This can be pasted just below contributeMainFragment().
Finally, we change our Activity
to use our factory to obtain the ViewModel
. Fragments
work the same way.
1 2 3 |
@Binds @NotNull public abstract ViewModelProvider.Factory bindViewModelFactory(@NotNull ViewModelFactory factory); |
1 2 3 4 |
@Binds @IntoMap @ViewModelKey(MainActivityViewModel.class) abstract ViewModel bindMainActivityViewModel(MainActivityViewModel mainActivityViewModel); |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MainActivity extends DaggerAppCompatActivity { @Inject ViewModelProvider.Factory viewModelFactory; private MainActivityViewModel viewModel; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel.class); } } |
Repositories
We’re almost done. Let’s see how we would inject a Repository
into our ViewModel
.
Add @Singleton
to the repository and @Inject
to the constructor. We’ll also inject our test String here as an example.
1 2 3 4 5 6 7 |
@Singleton public class SomeRepository { @Inject public SomeRepository(@Named("test1") String testString1) { Log.v(LOG_TAG, "injected value: " + testString1); } } |
1 2 3 4 5 |
public class MainActivityViewModel extends ViewModel { @Inject public MainActivityViewModel(SomeRepository someRepository) { } } |
IntentService and RemoteViewsService
For IntentService, just extend DaggerIntentService, and, for RemoteViewsService, modify onCreate() as shown below. Don’t forget to add your @ContributesAndroidInjector abstract method.
1 2 3 4 5 |
@Override public void onCreate() { AndroidInjection.inject(this); super.onCreate(); } |
The End!
Please let me know if you found this useful or if you’d like more information. Thanks!