Using dagger for dependency injection involves defining modules and components, instantiating dagger components in main classes and calling appropriate dependency injection methods on components to fill dependencies. In android, main classes are activities and fragments. Since Android OS instantiates these classes, dagger code to perform dependency injection is usually done in life cycle methods of android components.
public class MainActivity extends AppCompatActivity {
@Inject
ObjectA objA;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerCouponComponent.builder().couponModule(
new CouponModule()).build().inject(this);
}
}
Keeping dependency injection code in android components as shown above has disadvantages. With the way in which dagger components are created in android activities and fragments, these classes will know about its dependency injectors causing violation of dependency injection principle.
Another disadvantage is that since the code which performs dependency injection need to be placed in all activities and fragments, maintenance will become difficult.
To solve these issues and perform dependency injection in a clean way into android classes, dagger provides dagger.android package.Using dagger android library, all you need to use is call AndroidInjection.inject(this) in activities and fragments to fill dependencies. Above example can be rewritten as shown below with dagger android libarary.
public class MainActivity extends AppCompatActivity {
@Inject
ObjectA objA;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AndroidInjection.inject(this);
}
}
In this article, I’ll show how to use dagger android package with an example. If you want to know about dagger, you can read dependency injection using dagger2, dagger multibinding and dagger subcomponents.
Add dagger and dagger android libraries to your project by adding below entries to build.gradle file.
compile 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
compile 'com.google.dagger:dagger-android-support:2.11'
Below are the steps to use dagger android library. Each step will be explained in detail with examples.
First, dagger components related to activity or fragment need to be modified. You need to make these components extend AndroidInjector interface and provide Subcomponent.Builder that extends AndroidInjector.Builder as shown below.
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<MainActivity> {
}
}
@AddExpenseScope
@Subcomponent(modules = AddExpenseModule.class)
public interface AddExpenseComponent extends AndroidInjector<AddExpenseFragment> {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<AddExpenseFragment> {
}
}
As usual, you need to add sub components to parent component module in order to add them to dagger object hierarchy. But you need to perform one extra step for each subcomponent, which is defining in parent module a method that takes subcomponent builder object as input and returns AndroidInjector.Factory.
Since activity component’s parent is app component, AndroidInjector.Factory method needs to be defined in dagger component module for application in order to simplify dependency injection for activities, as shown below. Or you can create a separate module that contains just AndroidInjector.Factory method and add the module to parent component.
@Module(subcomponents = {ActivityComponent.class})
public abstract class AppModule {
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(ActivityComponent.Builder builder);
....
Since fragment component’s parent is activity component, AndroidInjector.Factory method needs to be defined in dagger component module for activity in order to simplify dependency injection for fragments, as shown below. Or you can create a separate module that contains just AndroidInjector.Factory method and add the module to dagger component for activity.
@Module(subcomponents = {AddExpenseComponent.class, ViewExpenseComponent.class})
public abstract class ActivityModule {
………………
@Binds
@IntoMap
@FragmentKey(AddExpenseFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> provideAddExpenseFragment(AddExpenseComponent.Builder builder);
@Binds
@IntoMap
@FragmentKey(ExpenseListFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> provideExpenseListFragment(ViewExpenseComponent.Builder builder);
}
You need to add AndroidInjectionModule to your dagger root component. AndroidInjectionModule is part of dagger android package.
@Singleton
@Component(modules ={AndroidInjectionModule.class,
AppModule.class})
public interface AppComponent {
…………..
Next, we need to make android components, in which dagger components are initialized, implement one of the interfaces, HasActivityInjector, HasBroadcastReceiverInjector, HasContentProviderInjector, HasFragmentInjector, or HasServiceInjector, depending on the type of component. For example, if the component in which dagger component is created is application, it needs to implement HasActivityInjector interface and its method activityInjector() that returns AndroidInjector<Activity>. This method just returns dagger injected DispatchingAndroidInjector<Activity> object.
To make DispatchingAndroidInjector available in activities, application needs to implement HasActivityInjector as shown below. DispatchingAndroidInjector object in application gets injected by dagger.
public class ExpenseApplication extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
……………………….
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
To make DispatchingAndroidInjector available in fragments to perform dependency injection, activities need to implement HasFragmentInjector as shown below. In the activities, DispatchingAndroidInjector object gets injected by dagger. We need to implement fragmentInjector method defined in HasFragmentInjector interface. This method simply returns DispatchingAndroidInjector object.
public class MainActivity extends AppCompatActivity implements HasFragmentInjector {
@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
@Inject
Message message;
private FragmentManager fm;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fm = getSupportFragmentManager();
Toast.makeText(this, message.getMessage(), Toast.LENGTH_LONG).show();
}
@Override
public AndroidInjector<Fragment> fragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
..............
Finally, you need to call AndroidInjection.inject(this) in the activities or fragments to fill dependencies. AndroidInjection uses DispatchingAndroidInjector object to perform the dependency injection. This is the last step in simplifying dagger dependency injection.
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
@Override
public void onAttach(Context context) {
AndroidSupportInjection.inject(this);
super.onAttach(context);
}
To simplify dependency injection even further, dagger provides components which extend android components, such as DaggerActivity,DaggerApplication, DaggerFragment, DaggerBroadcastReceiver, DaggerContentProvider, DaggerIntentService, and DaggerService.
For example, to make AndroidInjection.inject(this) work in fragments, activities need to define DispatchingAndroidInjector
public class MainActivity extends DaggerAppCompatActivity {
private FragmentManager fm;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fm = getSupportFragmentManager();
}
}
We can further decouple dagger from android components by removing AndroidInjection.inject calls from all activities and fragments. To do that, we need to register lifecycle callback handler on android application object and call dagger AndroidInjection.inject in the callback handler as shown below. With below implementation, you don’t need to call AndroidInjection.inject in activities and fragments.
public class DaggerAndroidInjector {
private DaggerAndroidInjector(){}
public static void initialize(ExpenseApplication expenseApplication) {
DaggerAppComponent.builder().application(expenseApplication)
.build().inject(expenseApplication);
expenseApplication
.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
handleActivity(activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
}
private static void handleActivity(Activity activity) {
if (activity instanceof DaggerInjectable) {
AndroidInjection.inject(activity);
}
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager()
.registerFragmentLifecycleCallbacks(
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentCreated(FragmentManager fm, Fragment f,
Bundle savedInstanceState) {
if (f instanceof DaggerInjectable) {
AndroidSupportInjection.inject(f);
}
}
}, true);
}
}
}
In application’s onCreate method, you can call life cycle adapter as shown below.
public class ExpenseApplication extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAndroidInjector.initialize(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
Using expense tracker example app, implementation of above listed steps for utilizing dagger android package is shown. The example has one activity with two fragments. First fragment lets user to add expenditure and second fragment shows list of expenditures. This example uses Room to store data in SQLite database.
You can find complete project on github at https://github.com/srinurp/DaggerAndroid
Below are few dagger components and modules from the project.
@Scope
@Retention(value= RetentionPolicy.RUNTIME)
public @interface ViewExpenseScope {
}
@Module
public class ViewExpenseModule {
@ViewExpenseScope
@Named("view")
@Provides
public Message getExpMessage(ViewExpenseMessage viewExpenseMessage){
return viewExpenseMessage;
}
}
@ViewExpenseScope
@Subcomponent(modules = ViewExpenseModule.class)
public interface ViewExpenseComponent extends AndroidInjector<ExpenseListFragment> {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<ExpenseListFragment> {
}
}
@Module(subcomponents = {AddExpenseComponent.class, ViewExpenseComponent.class})
public abstract class ActivityModule {
@Provides
public static Message getExpMessage(ActivityMessage activityMessage){
return activityMessage;
}
@Binds
@IntoMap
@FragmentKey(AddExpenseFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> provideAddExpenseFragment(AddExpenseComponent.Builder builder);
@Binds
@IntoMap
@FragmentKey(ExpenseListFragment.class)
abstract AndroidInjector.Factory<? extends Fragment> provideExpenseListFragment(ViewExpenseComponent.Builder builder);
}
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
public abstract class Builder extends AndroidInjector.Builder<MainActivity> {
}
}
@Module(subcomponents = {ActivityComponent.class})
public abstract class AppModule {
@Provides
@Singleton
public static Context provideAppContext(Application application) {
return application;
}
@Singleton
@Provides
public static ExpenseDAO getCouponDAO(ExpenseDatabase expenseDatabase){
return expenseDatabase.expenseDAO();
}
@Singleton
@Provides
public static ExpenseDatabase getCouponDatabase(Application application){
return Room.databaseBuilder(application.getApplicationContext(),
ExpenseDatabase.class, "expense.db")
.build();
}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity>
bindMainActivityInjectorFactory(ActivityComponent.Builder builder);
}
@Singleton
@Component(modules ={AndroidInjectionModule.class,
AppModule.class})
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
void inject(ExpenseApplication app);
}
public class ExpenseApplication extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}