Dagger2 Learning Note
image from Mert Şimşek: New Android Injector with Dagger 2 — part 2
Injecting Activity objects | commit
-public class DemoApplication extends Application {
+public class DemoApplication extends DaggerApplication {
+
+ @Override
+ protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
+ AppComponent appComponent = DaggerAppComponent.builder().application(this).build();
+ appComponent.inject(this);
+ return appComponent;
+ }
+/**
+ * Android apps have one application class. That is why we have one application component.
+ * This component is responsible for providing application scope instances (eg. OkHttp, Database, SharedPrefs.).
+ * This Component is root of our dagger graph. Application component is providing 3 module in our app:
+ *
+ * - {@link AndroidInjectionModule} We didn't create this. It is an internal class in Dagger 2.10.
+ * Provides our activities and fragments with given module,
+ * to ensure that all bindings necessary for these base types are available.
+ *
+ * - {@link ActivityBuilder} We created this module and map all our activities here.
+ *
+ * - {@link AppModule} We created this module to provide retrofit, persistence db, shared pref etc here.
+ */
+@Singleton
+@Component(modules = {
+ AndroidSupportInjectionModule.class,
+ AppModule.class,
+ ActivityBuilder.class,
+})
+public interface AppComponent extends AndroidInjector<DaggerApplication> {
+
+ void inject(DemoApplication app);
+
+ @Override
+ void inject(DaggerApplication instance);
+
+ @Component.Builder
+ interface Builder {
+ @BindsInstance
+ Builder application(Application application);
+ AppComponent build();
+ }
+}
+/**
+ * We provide retrofit, OKHttp, persistence db, shared pref etc here.
+ * There is an important detail here. We have to add our sub-components to AppModule.
+ * So our dagger graph will understand that.
+ */
+@Module
+public class AppModule {
+ @Provides
+ @Singleton
+ Context provideContext(Application application) {
+ return application;
+ }
+}
+/**
+ * This is a given module to dagger.
+ * We map ALL our activities here, then Dagger knows our activities in compile time.
+ */
+@Module
+public abstract class ActivityBuilder {
+
+ @ContributesAndroidInjector(modules = ArticleDetailActivityModule.class)
+ abstract ArticleDetailActivity bindArticleDetailActivity();
+}
+/**
+ * This module provides the activity related instances.
+ */
+@Module
+public class ArticleDetailActivityModule {
+
+ @Provides
+ ArticleDetailViewModel provideArticleDetailViewModel(ArticleDetailActivity activity, ArticleDetailViewModelFactory factory) {
+ return ViewModelProviders.of(activity, factory).get(ArticleDetailViewModel.class);
+ }
+}
-public class ArticleDetailActivity extends AppCompatActivity {
+public class ArticleDetailActivity extends DaggerAppCompatActivity {
- private ArticleDetailViewModel mViewModel;
+ // error: Dagger does not support injection into private fields
+ @Inject
+ ArticleDetailViewModel mViewModel;
- ArticleDetailViewModelFactory factory = InjectorUtils.provideArticleDetailViewModelFactory(this);
- mViewModel = ViewModelProviders.of(this, factory).get(ArticleDetailViewModel.class);
mViewModel.getArticle(mArticleId).observe(this, article -> {
Injecting Fragment objects | commit
- its host Activity extends
DaggerAppCompatActivity
; - it extends
DaggerFragment
; - Use
@Inject
to annotate fields of this fragment that REQUIRE dependency, then delete relates code that create the fields instance; - Use
@Inject
to annotate constructors that PROVIDE dependency, or - Create a
@Module
annotated class, and within this class use an@Provides
annotated method to provide a dependency. Use
ContributesAndroidInjector
to install module to dagger graph, because the fragment must be hosted by one activity, that means the activity should also be added to dagger graph:This annotation must be applied to an abstract method in a {@link dagger.Module} that returns a concrete Android framework type (e.g. Activity, Fragment, Service, etc). The method should have no parameters.
/** * ArticleListFragment is hosted by ArticleListActivity, so we need to * install the fragment's module to this activity. */ @ContributesAndroidInjector(modules = { ArticleListActivityHostedFragment.class }) abstract ArticleListActivity bindArticleListActivity(); /** * The reason to create this class is that this activity maybe host many fragments. */ @Module abstract class ArticleListActivityHostedFragment { @ContributesAndroidInjector(modules = ArticleListFragmentModule.class) abstract ArticleListFragment bindArticleListFragment(); // another fragment ... }
@Inject doesn’t work everywhere to provide dependency
- Interfaces can’t be constructed.
- Third-party classes can’t be annotated.
- Configurable objects must be configured!
Add @Inject to constructor to provide dependency, instead of defining an unnecessary @Provides annotation | commit
Because @Inject
-annotated constructor works here !
@Module
public class ArticleListFragmentModule {
ArticleListFragmentViewModel provideArticleListFragmentViewModel(ArticleListFragment fragment, ArticleListViewModelFactory factory) {
return ViewModelProviders.of(fragment, factory).get(ArticleListFragmentViewModel.class);
}
- @Provides
- ArticleListViewModelFactory provideArticleListViewModelFactory(Context context, DemoRepository repository) {
- return new ArticleListViewModelFactory(repository);
- }
}
public class ArticleListViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private final DemoRepository mRepository;
+ @Inject
public ArticleListViewModelFactory(DemoRepository repository) {
mRepository = repository;
}
Remove the parameters of constructor which the fields can be injected | commit | commit
simplify
public class ArticleListViewModelFactory extends ViewModelProvider.NewInstanceFactory {
- private final DemoRepository mRepository;
+ @Inject
+ DemoRepository mRepository;
@Inject
- public ArticleListViewModelFactory(DemoRepository repository) {
- mRepository = repository;
+ public ArticleListViewModelFactory() {
}
This is another good code snippet to show the advantage of dagger. With dagger, we can simplify the constructor to a default one without any parameters. The fields which need to be initialized in constructor, just @Inject
it. If we need to provide a singleton instance, just @Singleton
it, instead of using a static
filed to hold the instance.
One of three types of dependency injection, WIKI- Constructor injection: This method requires the client to provide a parameter in a constructor for the dependency.
A class contains constructors that are invoked to create objects from the class blueprint. Java Doc - Providing Constructors for Your Classes
@Singleton
public class DemoRepository {
- private static final Object LOCK = new Object();
- private static DemoRepository sInstance;
- private final ArticleDao mArticleDao;
- private final DemoWebService mDemoWebService;
- private final AppExecutors mExecutors;
- private final Context mContext;
+ @Inject
+ ArticleDao mArticleDao;
+ @Inject
+ DemoWebService mDemoWebService;
+ @Inject
+ AppExecutors mExecutors;
+ @Inject
+ Context mContext;
+
private RateLimiter<String> repoListRateLimit = new RateLimiter<>(2, TimeUnit.MINUTES);
- public DemoRepository(
- Context context, ArticleDao articleDao, DemoWebService demoWebService, AppExecutors executors) {
- mContext = context;
- mArticleDao = articleDao;
- mDemoWebService = demoWebService;
- mExecutors = executors;
- }
-
- public synchronized static DemoRepository getInstance(
- Context context, ArticleDao articleDao, DemoWebService demoWebService, AppExecutors executors) {
- Log.d(LOG_TAG, "Getting the repository");
- if (sInstance == null) {
- synchronized (LOCK) {
- if (sInstance == null) {
- sInstance = new DemoRepository(context, articleDao, demoWebService, executors);
- Log.d(LOG_TAG, "Made new repository");
- }
- }
- }
- return sInstance;
+ @Inject
+ public DemoRepository(){
}
Provide dependency for Configurable objects must be configured! | commit
But sometimes the object need to be configured, for example, it needs exactly integer instead of another instance:
public class DemoRepository {
- private RateLimiter<String> repoListRateLimit = new RateLimiter<>(2, TimeUnit.MINUTES);
+ @Inject
+ RateLimiter<String> repoListRateLimit;
}
public class AppModule {
+ @Provides
+ RateLimiter<String> provideRateLimiter() {
+ return new RateLimiter<>(2, TimeUnit.MINUTES);
+ }
}
Consider Dependency Inject whenever you want to use keyword new
| commit
cannot be provided without an @Inject constructor or from an @Provides-annotated method.
Check if all the dependencies are satisfied
Because the dependency of ArticlesContract.View
& LifecycleOwner
were no provided, so NullPointerException
happens.
//ArticleListFragment
@Inject
ArticlesContract.Presenter mPresenter;
//ArticlesPresenter
@Inject
public ArticlesPresenter(ArticlesContract.View articlesView, LifecycleOwner lifecycleOwner,
ArticleListFragmentViewModel viewModel, DemoRepository repository) {
mArticlesView = articlesView;
mLifecycleOwner = lifecycleOwner;
mRepository = repository;
mArticlesView.setPresenter(this);
}
Refer
New Android Injector with Dagger 2 — part 1, part 2, part 3
In part1, to attach activities/fragments to dagger graph, it create a lot boilerplate classes and needs a lot repetitive tasks, so is too complicated, so just walk through part1.
In part2, use
@ContributesAndroidInjector
to simplify dagger graph, check this commit for detailsIn part3, use
DaggerActivity
,DaggerFragment
,DaggerApplication
to reduce boilerplate in your Activity/Fragment/Application, useAndroidInjector<T>
in your dagger components to reduce boilerplate too.Github/codepath Wiki page: Dependency Injection with Dagger 2
JANISHAR ALI: Introduction to Dagger 2, Using Dependency Injection in Android: Part 2
Eugene Matsyuk: Dagger 2. Part I. Basic principles, graph dependencies, scopes.