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

  1. its host Activity extends DaggerAppCompatActivity;
  2. it extends DaggerFragment;
  3. Use @Inject to annotate fields of this fragment that REQUIRE dependency, then delete relates code that create the fields instance;
  4. Use @Inject to annotate constructors that PROVIDE dependency, or
  5. Create a @Module annotated class, and within this class use an @Provides annotated method to provide a dependency.
  6. 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