ZOFTINO.COM android and web dev tutorials

Dependency Injection Android Using Dagger2

Table of Contents

Dependency Injection

One of the responsibilities that need to be considered for building loosely coupled components is creating instances of dependent objects of classes. If a class owns the responsibility of creating its dependents, it will not be easy to test, maintain and enhance such classes.

For example, below class is a tightly coupled class as it instantiates dependent class in its constructor.

 public class RemoteCoupons {

    RemoteConnection remoteConnection;

    public RemoteCoupons(){
        remoteConnection = new RemoteConnection();
    }

    public String getRemoteCoupon(){
        return remoteConnection.getCoupon;
    }

}
 

While patterns such as factory and service locator can be used to create loose coupling classes, they don’t completely remove the dependency. Classes have to use factory or service locator classes to get instances of dependent objects.

Dependency injection not only separates the responsibility of building dependent instances but it also injects them to the consumer classes.

Dagger is a framework which allows applications to implement dependency injection to build loosely coupled components leading to creation of applications which are easy to maintain and enhance.

Dagger uses standard annotations defined in javax.inject package as per JSR 330 dependency injection specification.

Dagger2 Setup

In order to use dagger, you need to add below libraries to your project.

 compile 'com.google.dagger:dagger:2.11'
 annotationProcessor 'com.google.dagger:dagger-compiler:2.11'

Making Classes to be Part of Dagger Dependency Management

If you want dagger to create an instance of a class and make it part of dagger object graph, you need to mark the class with annotations. There are two annotations, Inject and Provides, which Dagger uses to consider a class for building graph of objects.

Annotation @Inject is used on the constructors of a class to make it part of the dagger object graph. If constructor takes arguments, you need to make sure that constructor argument types are also part of dagger so that those argument types can be instantiated and provided by dagger.

Methods which return instances should be annotated with @Provides so that the instance will be part of the dagger object graph.

Below example uses Inject annotation on constructor of a class to make it part of Dagger objects graph.

 public class CouponRepository {
    @Inject
    public  CouponRepository(){};

    public String getAllCoupon(){
        return "here my test coupon";
    }
}

You will get below compilation error, if you try to inject a class which doesn’t have constructor with Inject annotation or provides methods are not defined for it.

error:  cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.

Dagger Modules

As mentioned above, to make a class part of dagger object graph, you need to annotate constructor of the class with Inject annotation. This can be done if you own the class or creation of object is simple.

If you don’t own a class and you want it to be part of dagger object graph, since constructor can’t be annotated with Inject annotation in this case, you need to create a method which returns target class type and annotate the method with @Provides.

@Provides methods are also useful for making classes, which can be instantiated only using builder classes, to be part of dagger object graph.

Classes which are annotated with Module and contain @Provides annotated methods are called modules in dagger. Provider methods in modules are used by dagger to build object graph. Below is an example of Module class.

 @Module
public class CouponModule {

    @Provides
    public CouponRepository getCouponRepository(){
        return new CouponRepository();
    }
}
 

If you add @Provides method to a class, you will get below compilation error. That is because only Module classes can contain methods annotated with Provides.

 error: @Provides methods can only be present within a @Module or @ProducerModule

Singleton Instances

Dagger creates only one instance for classes if classes are annotated with Singleton. Dagger also creates one instance and caches it in the case of provides-methods which are annotated with Singleton and it provides the same instance from cache to consumers. Dagger creates new instance each time an instance is requested for non scoped classes.

Dagger Singleton class

Dagger creates and servers only one instance of RemoteCoupons class.

 @Singleton
public class RemoteCoupons {
    @Inject
    RemoteConnection remoteConnection;

    @Inject
    public RemoteCoupons(){}

    public String getRemoteCoupon(){
        return "get remote coupon";
    }

}

Dagger Singleton Provides method example

Dagger creates and serves only on instance of LocalConnection class.

@Module
public class CouponModule {
    @Provides
    @Singleton
    public LocalConnection getLocalConnection(){
        return new LocalConnection();
    }
}

Annotating Fields with Inject to Define Dependency

In order for Dagger to inject instances in a particular class, fields in the class need to be marked with Inject annotation.

Constructor with Inject annotation

Below class can be instantiated by Dagger as constructor is annotated with Inject.

 public class RemoteCoupons {
    @Inject
    public RemoteCoupons(){}
    public String getRemoteCoupon(){
        return "get remote coupon";
    }
}

Fields annotated with Inject

Dagger can inject dependency for below class as it has fields annotated with Inject.

 public class CouponRepository {

    @Inject
    RemoteCoupons remoteCoupons;

    @Inject
    public  CouponRepository(){};

    public String getAllCoupon(){
       return remoteCoupons.getRemoteCoupon();
    }
}

Dagger 2 Constructor Injection

If you annotate private fields with Inject, you will get compilation error.

 error: Dagger does not support injection into private fields

To inject to private fields, constructor injection can be used as shown in below example.

public class RemoteCoupons {
    private RemoteConnection remoteConnection;

    @Inject
    public RemoteCoupons(RemoteConnection rc){
        remoteConnection = rc;
    }

    public String getRemoteCoupon(){
        return "get remote coupon "+remoteConnection.getCoupon();
    }

}

If a class can be instantiated by dagger using provides method from a module and the class’s constructor is not marked with Inject annotation but class has fields annotated with Inject, then Dagger can’t inject objects to those fields.

To make dagger fill those fields, constructor injection needs to be used. Meaning, you need to define constructor which takes arguments and sets those arguments to fields. And also provides-method should be defined to take those arguments as input parameters so that dagger can pass objects to the providers-method and those arguments can be used to construct instance of target class.

Class with private fields

public class CouponRepository {

    private RemoteCoupons remoteCoupons;

    private LocalCoupons localCoupons;

    public  CouponRepository(RemoteCoupons rCoupons, LocalCoupons lCoupons){
        remoteCoupons = rCoupons;
        localCoupons = lCoupons;
    };

    public String getAllCoupon(){
      return localCoupons.getLocalCoupon() +" "+remoteCoupons.getRemoteCoupon();
    }
}

Provides method constructor injection

@Module
public class CouponModule {
    @Provides
    public CouponRepository getCouponRepository(RemoteCoupons rCoupons, LocalCoupons lCoupons){
        return new CouponRepository(rCoupons, lCoupons);
    }
}

Defining Dagger Component Interface

Annotating constructors and fields with Inject is not enough to make Dagger work. What is needed is an interface for your app’s main code to get objects from dagger object graph. You need to define the interface and annotate it with Component so that dagger will generate implementation of the interface.

In the interface, you need to define methods which return the types you are interested in. You can name these methods however you want, but what is important is return type. Below is a component interface example. Following section explains how to use components and its methods.

 @Component
public interface CouponComponent {

    CouponRepository getCouponRep();
}

There is another type of method you can define in component interface which can provide instances to all fields which are annotated with Inject in a class. The method takes target class as parameter whose fields will be assigned from the object graph.

Below example component interface has inject method which can be used to inject objects to MainActivity, meaning all dependencies in MainActivity class can be filled using this method. Following section explains how to use components and inject methods.

 @Component
public interface CouponComponent { 

    public void inject(MainActivity mainActivity);

}

If your project has dagger module classes, you need add module classes to component interface as shown below so that instances provided by modules will be available for dependency injection.

 @Component(modules={CouponModule.class})
public interface CouponComponent {

    public void inject(MainActivity mainActivity);

}
 

Creating Dagger Component Instance and Using Component Methods

Dagger generates implementation of dagger component interface which you define and the implementation class name is same as interface but Dagger prefix gets added to it. For example, your component interface is CouponComponent then the implementation class generated by dagger for it is DaggerCouponComponent.

You need to build your project after you define component interface so that dagger can generate component implementation class.

To get instances from dagger object graph in target classes where you want to inject objects, you need to instantiate component. Dagger component implementation class can be instantiated by calling create() method or builder() method on it.

If there are no modules or included modules don’t have constructors with arguments, you can use create() method to create Dagger component instance.

 DaggerCouponComponent daggerCouponComponent = DaggerCouponComponent.create();

If included modules have constructors which take parameters, then you need to use builder and add module to it to build Dagger component instance.

 DaggerCouponComponent.builder().couponModule(new CouponModule()).build() 

Using Component in Application Main Classes

Once you create instance of component object, you can call methods on it to get instances and assign to fields in your main classes.

 public class MainActivity extends AppCompatActivity {

    @Inject
    CouponRepository couponRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        couponRepository = DaggerCouponComponent.create().getCouponRep();
    }
}

Calling inject method on component object injects instances for all injectable fields in the class.

 public class MainActivity extends AppCompatActivity {

    @Inject
    CouponRepository couponRepository;
    @Inject
    RetrofitService retrofitService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerCouponComponent.builder().couponModule(new CouponModule()).build().inject(this);        
    }
}

Steps to Use Dagger

  • Add dagger libraries to your project.
  • Annotate constructors of classes with Inject so that Dagger will add instance of these classes to objects graph.
  • Create Module class and add @Provide methods which will be used by dagger to create instances of third party classes and add them to objects graph.
  • Define dependencies of classes by annotating fields with Inject.
  • Create component interface, adding modules list to it, and defining inject methods and methods which return instances.
  • Instantiate component implementation class generated by dagger and call appropriate method on it to fill fields.

Dagger Interface Injection

If any of your classes has a field of interface type, you need to define provides method in order for dagger to inject it.

Below provides method is used to fill dependencies of Biller type. SpecialBiller class implements Biller interface.

    @Provides
    public Biller provideSpecialBiller(SpecialBiller sb){
	return sb;
    }

Interface injection can also be done without provides methods using Binds annotation, for that, you need to define an abstract method with Binds annotation.

    @Binds
    public abstract Biller provideSpecialBiller(SpecialBiller sb);

Dagger Qualifiers

In some classes, you may have fields of same type. To make dagger differentiate them, you need to use qualifiers. Dagger provides @Name qualifier for this. You need to annotate both provider and consumer with this annotation.

 @Module
public class CouponModule {
    @Provides @Named("json")
    @Singleton
    public CouponRepository getCouponRepositoryJson(){
        return new LocalConnection("json");
    }

    @Provides @Named("xml")
    @Singleton
    public CouponRepository getLocalConnectionXml(){
        return new LocalConnection("xml");
    }
}
 public class LocalCoupons {

    @Inject @Named("json")
    LocalConnection localConnectionJson;

    @Inject @Named("xml")
    LocalConnection localConnectionXml;

    @Inject
    public LocalCoupons(){}

    public String getLocalCoupon(){
        return "get local coupon";
    }

}

Dagger Scope

In one of the sections above, we have talked about annotating classes and Provides-methods with Singleton annotation and its impact. Singleton annotation is a dagger defined scope. You can use scope annotation on class and provides-methods in modules to make those instances tied to that scope.

Scope allows you to group instances based on the life time of instances so that once instances of a particular scope are no longer used, they can be removed more memory. For example, instances which are used throughout the application can have application level scope; instances which are used during a user session can have session level scope and instances which are used during a flow of task can have activity level scope. This way instances belong to unneeded scope can be removed from memory.

Dagger associates instances to a component. If one of the instances in a component is tagged to a particular scope, you need to annotate the component with the same scope; otherwise you will get below compilation error.

 Error:(5, 1) error: (unscoped) may not reference scoped bindings

And also, component can have instances of a particular scope that the component is annotated with. If you try to associated instances belong to multiple scopes to a component, you will get below compilation error.

 error: may not reference bindings with different scopes:

These errors make sense, dagger component can be tied to a scope and it can contain only instances belong to that scope and unscoped instances. That means you have to create multiple components for different scopes.

Objects in one scope can depend on objects in different scope, Dagger allows component dependency. Topic of Dagger component dependencies is explained in the following section.

Dagger Custom Scope

You can define your own scope like as shown below. You can use custom scope in the same way as Singleton is used, which was shown in the above example.

Custom scope example

 @Scope
@Retention(value= RetentionPolicy.RUNTIME)
public  @interface CouponFlowScope {
} 
 @CouponFlowScope
@Component(modules={CouponModule.class})
public interface CouponComponent {
    public void inject(MainActivity mainActivity);
}
 

As long as there is a reference to component instance, all the instances that the component provides can be used using the component object. For a particular custom scoped component, you need to get and save reference to component object in such a way that the component object can be accessed during the life time of the scope so that all the instance it provides can be used.

Following sections shows various scopes you can define in android so that dagger object graph will be clean.

Dagger Android Application Scope

In android applications, application level scoped component can be created by instantiating a component while android application context is created so that the component can be accessed anywhere in the application and use the component to fill dependencies anywhere in the application. Annotating application scoped component with Singleton is the right approach as most of instances used at application level should be instantiated once.

Below example shows how to instantiate application level scoped component by extending android application class. You need to inform android about the new application class by setting it in manifest using application element.

 public class CouponApp extends Application {

    private CouponComponent component;

    @Override public void onCreate() {
        super.onCreate();

        component = DaggerCouponComponent.builder()
                .couponModule(new CouponModule())
                .build();
    }

    public static CouponComponent getComponent(Context context) {
        return ((CouponApp)context.getApplicationContext()).component;
    }
}
 

Getting dagger component from application context in activity and filling dependency.

 public class MainActivity extends AppCompatActivity {

    @Inject
    CouponRepository couponRepository;
    @Inject
    StoreRepository storeRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        CouponApp.getComponent(this).inject(this);
    }

} 

Dagger Android Activity Scope

Similarly you can create activity scoped component by instantiating component in the activity onCreate method. You can define custom scope for activities and annotate classes, provides-methods and component with the activity custom scope.

Dagger Android Fragment Scope

For instances that are used only in fragment, you can define custom scope for it, modules and fragment scoped component. You need to instantiate the cmponent in onCreate method of fragment or other lifecycle method that suits your need.

Dagger Component Dependencies

In your app, let’s say you defined fragment, activity and application level components. Fragment may depend on instances provided by activity or application components. Activity may depend on instances provided by application component. This can be done using dagger component dependency. There are two ways you can define component dependency in dagger, dependent components and subcomponents.

Dependent Components

You can declare child component’s dependency on parent by setting dependencies field of Component annotation as shown below. But you need to make sure that child and parent components are in different scopes, parent component needs to expose objects used by child component, and if child component declares inject methods, there shouldn’t be any inject methods in parent.

Parent component

Parent component doesn’t have inject methods as child component has inject method and it exposes CouponRepository object which child component can use.

 @Singleton
@Component(modules={CouponModule.class})
public interface CouponComponent {
    CouponRepository getCouponRep();
}
 

Child component

ActivityComponent depends on parent component CouponComponent.

 @CouponFlowScope
@Component(dependencies = CouponComponent.class,modules={StoreModule.class})
public interface ActivityComponent {
    public void inject(MainActivity mainActivity);
}
 

Activity

Activity depends on objects provided by both child and parent component. Child component is built passing parent component object obtained from application context. Calling inject on child component sets all dependencies including objects provided by parent component.

 public class MainActivity extends AppCompatActivity {

    // dependency from parent component
    @Inject
    CouponRepository couponRepository;
    // dependency from child component
    @Inject
    StoreRepository storeRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerActivityComponent.builder().couponComponent(CouponApp.getComponent(this))
                    .storeModule(new StoreModule())
                    .build().inject(this);
    }
} 

Sub components

The goal of sub components is same as dependent component. Both of them allow you to create scoped components with dependency between them. The end goal of scoped components, removing instances from memory when they are out of scope, can be achieved using both dependent and subcomponents.

To implement sub components, first you need to define sub component by annotating component interface with Subcomponent and add inject methods to it, then in the parent component, you need to expose subcomponent by adding a method which returns the sub component and takes sub component module as argument; method name can be any name you want.

Sub component module

 @Module
public class CashbackModule {
    @CashbackScope
    @Provides
    public CashbackRepository getCashbackRepository(){
        return new CashbackRepository();
    }
}
 

Sub component

Annotate sub component with Subcomponent and add inject methods.

 @CashbackScope
@Subcomponent(modules = CashbackModule.class)
public interface CashbackComponent{
    void inject(MainActivity mainActivity);
}
 

Parent component

Expose sub component in parent component.

 @Singleton
@Component(modules={CouponModule.class})
public interface CouponComponent {
    CashbackComponent getSubComp(CashbackModule cashbackModule);
}
 

Using sub component

Above steps make instances provided by parent component available for sub component. You can call inject on sub component to fill dependencies in target class, for that first you need to build parent component, then call method on parent component which exposes subcomponent passing sub component module and finally call inject method as shown below. You can implement subcomponents using subcomponent builders also, this way you don’t need to supply subcomponent module when component is instantiated.

 public class MainActivity extends AppCompatActivity {
	//dependency from parent component
    @Inject
    CouponRepository couponRepository;
	//dependency from sub component
    @Inject
    CashbackRepository cashbackRepository;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerCouponComponent.builder().build().getSubComp(new CashbackModule()).inject(this);
    }
} 

Other Dagger Articles

  1. Dagger Subcomponents
  2. Dagger Multibinding
  3. Decoupling Dagger Code from Android Components Using Dagger Android Library
  4. Android Dagger Example

Reference: https://guides.codepath.com/android/Dependency-Injection-with-Dagger-2