blog posts

What is Dagger in Android programming ( Dependency Injection ) !

Dependency Injection can be problematic depending on the size of your Android project, which is why Dagger has been around for some time, and Android developers have worked with the Dagger framework or at least heard about it. In the dagger tutorial article on Android, we will get acquainted with its features and functions. Join us to learn about Dagger in Android, learn about it, and be able to work with it and take advantage of its features.

What is a dagger in Android?

In short, it is a tool for generating code to manage dependencies between different objects.

Dagger is a Dependency Injector for Java and Android. Dagger is a Dependency Injection Framework that runs at compile time. Also, dagger does not use code at runtime, does all its analysis at compile-time, and generates Java source code.

As you know, Dagger works with annotations, which help it read and generate all the code needed for the job. Also, all the different annotations used in Dagger are a way of saying things that Dagger does not know, and Annotations are used to achieve your goal and create the code that you would otherwise have to write yourself.

Dependency injection offers you the following benefits:
  • Ability to reuse written code
  • Ease of code modification
  • Ease of testing

Dagger tutorial on Android

Differences between Dagger 1 and Dagger 2 

In short, Dagger 2 is an improved version of Dagger 1 and has partially solved the problems inside Dagger 1.

Dagger problems 1:

  • Ugly generated code
  • Runtime graph composition
  • Inefficient graph creation
  • Partial traceability
  • Map-like API

And Dagger 2 Solutions for Dagger 1 Problems:

  • Compile-time validation of the  entire  graph
  • Easy debugging; entirely concrete call stack for provision  and  creation
  • Fully traceable
  • POJO API
  • Performance

And one of the problems with Dagger 2 is its lack of flexibility.

Dagger 2 uses the following annotations:

  • Module @ and Provides @ Deliver classes and methods to depend on us.
  • Inject @: is a dependencies request that can be used in a field, constructor, or method.
  • Component @: Activates the selected modules and is used to perform dependency injection.

Dagger 2 uses the generated code to access the fields. Therefore, the use of private fields for field injection is not allowed.

Dagger tutorial on Android

Defining dependency providers (object providers)

The term dependency injection context is commonly used to describe a set of injectable objects.

Also, in Dagger 2, classes annotated with Module @ are responsible for preparing injectable objects. So such classes can define methods annotated with Provides @. Returned objects from these methods are available for dependency injection.

With Provides @, annotation methods can express dependencies through method parameters. These dependencies are met by Dagger 2 if possible.

Defining dependencies (Object consumers)

You use @Inject annotations to define a dependency. If you annotate a Constructor with @Inject, Dagger 2 can use an instance of this object to execute dependencies. This is done to prevent more Provides methods built for these objects.

Communication between consumers and providers

  They are used in an interface. Such an interface is used by Dagger 2 to generate code. The basic pattern for the generated class is that Dagger is used as a prefix and then as an interface name. It generates a class that creates a method and allows objects to be configured based on the given configuration. The methods defined in the Interface are available for accessing the generated objects.

An @Component Interface expresses the relationship between the provider of objects (modules) and objects, which expresses the dependency.

Scope annotations

You can use the @ Singleton annotation to indicate that there should be only one object instance.

Fields in Dagger

Dagger 2 does not automatically inject fields. It also cannot inject private fields. If you want to use field injection, you must define a method in your Component @ interface that injects an instance of it as a parameter.

Use Dagger in Android Studio

To enable Dagger 2, add the following dependencies to your build. Gradle file.
implementation 'com.google.dagger: dagger: 2.15' 
annotationProcessor 'com.google.dagger: dagger-compiler: 2.10'
Many components of Android (Android Components), for example, Activities, are done by the Android framework and are not in our code. This makes it difficult to secure dependency on Android components through constructors.
Suppose you want to use the dagger. Android package classes like the DaggerActivity class, add the following dependencies to your build. gradle file, and if you want to inject Activities components such as Activities or Fragments, this is also necessary.
implementation 'com.google.dagger: dagger-android: 2.15' 
annotationProcessor 'com.google.dagger: dagger-android-processor: 2.10'
If you want to use the Support Library with Dagger 2, you must also add the following to the build. Gradle.
implementation 'com.google.dagger: dagger-android-support: 2.10'
If you get the same error: (Conflict with dependency ‘com.google.code.findbugs: jsr305’ in project ‘: app’)
You can add the following to your app/build.gradle file.
android {
    configurations. all {
        resolutionStrategy.force 'com.google.code.findbugs: jsr305: 1.3.9'
    }
}

Example of Dagger with RxJava and Retrofit with MVVM architecture

Project configuration:
def supportVersion = '27 .1.1 ' 
def retrofitVersion = ' 2.3.0 ' 
def rxJavaVersion = ' 2.0.1 ' 
def butterKnifeVersion = ' 8.8.1 ' 
def daggerVersion = ' 2.15 '
dependencies {
    ...
    implementation "android.arch.lifecycle: extensions: 1.1.1" 
    implementation "com.squareup.retrofit2: retrofit: $ retrofitVersion " 
    implementation "com.squareup.retrofit2 : converter-gson: $ retrofitVersion " 
    implementation " com.squareup.retrofit2 : adapter-rxjava2: $ retrofitVersion " 
    implementation " com.squareup.retrofit2 : converter-moshi: $ retrofitVersion " 
    implementation " io.reactivex.rxjava2: rxandroid: $ rxJavaVersion " implementation 
    " io.reactivex.rxjava2: rxjava: "
    implementation "com.jakewharton: butterknife: $ butterKnifeVersion " 
    implementation 'com.android.support:support-v4:27.1.1' 
    annotationProcessor "com.jakewharton: butterknife-compiler: $ butterKnifeVersion " 
    implementation "com.google.dagger: dagger: $ daggerVersion " 
    annotationProcessor " com.google.dagger: dagger-compiler: $ daggerVersion " 
    implementation " com.google.dagger: dagger-android-support: $ daggerVersion " 
    annotationProcessor " com.google.dagger: dagger-android-processor:$ daggerVersion "
    ...
}
We will work as Package by Feature. This makes your code more modular and controllable.

What is Dagger?

Launch Retrofit

public  interface  RepoService  {
    @GET ( "orgs / Google / repos" )
    Single < List <Repo>> getRepositories ();
    @GET ( "repos / {owner} / {name}" )
    Single <Repo> getRepo (@Path ( "owner" ) String owner, @Path ( "name" ) String name);
}
Set Base Fragment, Base Activity, Application Class.

BaseApplication.java

public class  BaseApplication  extends  DaggerApplication {
    @ Override 
    public void  onCreate () {
         super . onCreate ();
    }
    @ Override 
    protected AndroidInjector <? extends  DaggerApplication > applicationInjector () {
         ApplicationComponent component = DaggerApplicationComponent . builder (). application ( this ). build ();
        component. inject ( this );
        return component;
    }
}

BaseActivity.java

public  abstract  class  BaseActivity  extends  DaggerAppCompatActivity  {
    @LayoutRes
    protected  abstract  int layoutRes ();
    @Override
    protected  void onCreate (@Nullable Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (layoutRes ());
        ButterKnife.bind (this);
    }
}
We use the abstract LayoutRes () function to give the Resource Layout Id.

BaseFragment.java

public abstract class  BaseFragment  extends  DaggerFragment {
    private Unbinder unbinder;
    private AppCompatActivity activity;
    @ LayoutRes 
    protected abstract int layoutRes ();
    @ Override 
    public View  onCreateView ( @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState ) {
         View view = inflater. inflate ( layoutRes (), container, false );
        unbinder = ButterKnife . bind ( this , view);
        return view;
    }
    @ Override 
    public void  onAttach ( Context context ) {
         super . onAttach (context);
        activity = ( AppCompatActivity ) context;
    }
    @ Override 
    public void  onDetach () {
         super . onDetach ();
        activity = null ;
    }
    public AppCompatActivity getBaseActivity  () {
         return activity;
    }
    @ Override 
    public void  onDestroyView () {
         super . onDestroyView ();
        if (unbinder! = null ) {
            unbinder. unbind ();
            unbinder = null ;
        }
    }
}
Set Dagger 2 Component & Modules.

ApplicationComponent.java

@Singleton
@Component (modules = {ContextModule. Class , ApplicationModule. Class , AndroidSupportInjectionModule. Class , ActivityBindingModule. Class })
 public  interface  ApplicationComponent  extends  AndroidInjector < DaggerApplication > {
     void inject (BaseApplication application);
    @ Component.Builder
    interface  Builder  {
        @BindsInstance
        Builder application (Application application);
        ApplicationComponent build ();
    }
}
As you can see, we just wrote AndroidSupportInjectionModule, ActivityBindingModule, and ViewModelModule in the module parameter. We will write the other required modules that will require Activity or Fragment.

ActivityBindingModule.java

@Module
 public  abstract  class  ActivityBindingModule  {
    @ContributesAndroidInjector (modules = {MainFragmentBindingModule. Class })
     abstract MainActivity bindMainActivity ();
}

MainFragmentBindingModule.java

@Module
 public  abstract  class  MainFragmentBindingModule  {
    @ContributesAndroidInjector
    abstract ListFragment provideListFragment ();
    @ContributesAndroidInjector
    abstract DetailsFragment provideDetailsFragment ();
}

ApplicationModule.java

@Singleton
@Module (includes = ViewModelModule. Class )
 public  class  ApplicationModule  {
     private  static  final  String BASE_URL = "https://api.github.com/" ;
    @Singleton
    @Provides
    static Retrofit provideRetrofit () {
         return  new Retrofit.Builder (). baseUrl (BASE_URL)
                .addCallAdapterFactory (RxJava2CallAdapterFactory.create ())
                .addConverterFactory (GsonConverterFactory.create ())
                .build ();
    }
    @Singleton
    @Provides
    static RepoService provideRetrofitService (Retrofit retrofit) {
         return retrofit.create (RepoService. class );
    }
}

ContextModule.java

@Module
 public  abstract  class  ContextModule  {
    @Binds
    abstract Context provideContext (Application application);
}

ViewModelModule.java

@Singleton 
@Module 
abstract class  ViewModelModule {
     @Binds 
    @IntoMap 
    @ViewModelKey ( ListViewModel. Class )
    abstract ViewModel bindListViewModel (ListViewModel listViewModel);
    @Binds 
    @IntoMap 
    @ViewModelKey ( DetailsViewModel. Class )
    abstract ViewModel bindDetailsViewModel (DetailsViewModel detailsViewModel);
    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory (ViewModelFactory factory);
}

Build Custom ViewModel Factory

ViewModelFactory is a factory that extends ViewModelProvider.Factive to provide ViewModel instances to consumer Fragment classes. We did that class with Inject, ViewModelModule.

ViewModelFactory.java

@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map <Class <?  extends ViewModel>, Provider <ViewModel>> creators;
    @Inject
    public ViewModelFactory (Map < Class <? extends  ViewModel >, Provider < ViewModel >> creators ) {
        this.creators = creators;
    }
    @NonNull
    @SuppressWarnings ( "unchecked" )
    @Override
    public <T extends ViewModel> T create (@NonNull Class < T > modelClass ) {
        Provider <?  extends ViewModel> creator = creators.get (modelClass);
        if (creator == null ) {
             for (Map.Entry < Class <? extends  ViewModel >, Provider < ViewModel >> entry : creators . entrySet ()) {
                 if (modelClass.isAssignableFrom (entry.getKey ())) {
                    creator = entry.getValue ();
                    break ;
                }
            }
        }
        if (creator == null ) {
             throw  new IllegalArgumentException ( "unknown model class" + modelClass);
        }
        try {
             return (T) creator.get ();
        } catch ( Exception e) {
             throw  new  RuntimeException (e);
        }
    }
}
Launch ViewModel.

ListViewModel.java

public  class  ListViewModel  extends  ViewModel  {
     private  final RepoRepository repoRepository;
    private CompositeDisposable disposable;
    private  final MutableLiveData < List <Repo>> repos = new MutableLiveData <> ();
    private  final MutableLiveData < Boolean > repoLoadError = new MutableLiveData <> ();
    private  final MutableLiveData < Boolean > loading = new MutableLiveData <> ();
    @Inject
    public ListViewModel (RepoRepository repoRepository) {
        this.repoRepository = repoRepository;
        disposable = new CompositeDisposable ();
        fetchRepos ();
    }
    LiveData < List <Repo>> getRepos () {
         return repos;
    }
    LiveData < Boolean > getError () {
         return repoLoadError;
    }
    LiveData < Boolean > getLoading () {
         return loading;
    }
    private  void fetchRepos () {
        loading.setValue ( true );
        disposable.add (repoRepository.getRepositories (). subscribeOn (Schedulers.io ())
                .observeOn (AndroidSchedulers.mainThread ()). subscribeWith ( new DisposableSingleObserver < List <Repo>> () {
                    @Override
                    public  void onSuccess ( List <Repo> value) {
                        repoLoadError.setValue ( false );
                        repos.setValue (value);
                        loading.setValue ( false );
                    }
                    @Override
                    public  void onError ( Throwable e) {
                        repoLoadError.setValue ( true );
                        loading.setValue ( false );
                    }
                }));
    }
    @Override
    protected  void onCleared () {
        super.onCleared ();
        if (disposable! = null ) {
            disposable.clear ();
            disposable = null ;
        }
    }
}
Create a fragment.

ListFragment.java

public class  ListFragment  extends  BaseFragment implements RepoSelectedListener {
    @ BindView (R. id . RecyclerView ) RecyclerView listView;
    @ BindView (R. id . Tv_error ) TextView errorTextView;
    @ BindView (R. id . Loading_view ) View loadingView;
    @ Inject  ViewModelFactory viewModelFactory;
    private ListViewModel viewModel;
    @ Override 
    protected int layoutRes () {
         return R. layout . screen_list ;
    }
    @ Override 
    public void  onViewCreated ( @NonNull View view, @Nullable Bundle savedInstanceState ) {
        viewModel = ViewModelProviders . of ( this , viewModelFactory). get ( ListViewModel . class );
        listView. addItemDecoration ( new  DividerItemDecoration ( getBaseActivity (), DividerItemDecoration . VERTICAL ));
        listView. setAdapter ( new  RepoListAdapter (viewModel, this , this ));
        listView. setLayoutManager ( new  LinearLayoutManager ( getContext ()));
        observableViewModel ();
    }
    @ Override 
    public void  onRepoSelected ( Repo repo ) {
         DetailsViewModel detailsViewModel = ViewModelProviders . of ( getBaseActivity (), viewModelFactory). get ( DetailsViewModel . class );
        detailsViewModel. setSelectedRepo (repo);
        getBaseActivity (). getSupportFragmentManager (). beginTransaction (). replace (R. id . screenContainer , new  DetailsFragment ())
                . addToBackStack ( null ). commit ();
    }
    private void  observableViewModel () {
        viewModel. getRepos (). observe ( this , repos -> {
             if (repos! = null ) listView. setVisibility ( View . VISIBLE );
        });
        viewModel. getError (). observe ( this , isError -> {
             if (isError! = null ) if (isError) {
                errorTextView. setVisibility ( View . VISIBLE );
                listView. setVisibility ( View . GONE );
                errorTextView. setText ( "An Error Occurred While Loading Data!" );
            } else {
                errorTextView. setVisibility ( View . GONE );
                errorTextView. setText ( null );
            }
        });
        viewModel. getLoading (). observe ( this , isLoading -> {
             if (isLoading! = null ) {
                loadingView. setVisibility ( isLoading ? View . VISIBLE : View . GONE );
                if (isLoading) {
                    errorTextView. setVisibility ( View . GONE );
                    listView. setVisibility ( View . GONE );
                }
            }
        });
    }
}
Create the RecyclerView Adapter.

RepoListAdapter.java

public  class  RepoListAdapter  extends  RecyclerView . Adapter < RepoListAdapter . RepoViewHolder > {
     private RepoSelectedListener repoSelectedListener;
    private  final  List <Repo> data = new ArrayList <> ();
    RepoListAdapter (ListViewModel viewModel, LifecycleOwner lifecycleOwner, RepoSelectedListener repoSelectedListener) {
        this.repoSelectedListener = repoSelectedListener;
        viewModel.getRepos (). observe (lifecycleOwner, repos -> {
            data.clear ();
            if (repos! = null ) {
                data.addAll (repos);
                notifyDataSetChanged ();
            }
        });
        setHasStableIds ( true );
    }
    @NonNull
    @Override
    public RepoViewHolder onCreateViewHolder (@NonNull ViewGroup parent , int viewType) {
        View view = LayoutInflater. from ( parent .getContext ()). inflate (R.layout.view_repo_list_item, parent , false );
        return  new RepoViewHolder (view, repoSelectedListener);
    }
    @Override
    public  void onBindViewHolder (@NonNull RepoViewHolder holder, int position) {
        holder.bind (data.get (position));
    }
    @Override
    public  int getItemCount () {
         return data.size ();
    }
    @Override
    public long getItemId ( int position) {
         return data.get (position) .id;
    }
    static  final  class  RepoViewHolder  extends  RecyclerView . ViewHolder  {
        @BindView (R.id.tv_repo_name) TextView repoNameTextView;
        @BindView (R.id.tv_repo_description) TextView repoDescriptionTextView;
        @BindView (R.id.tv_forks) TextView forksTextView;
        @BindView (R.id.tv_stars) TextView starsTextView;
        private Repo repo;
        RepoViewHolder (View itemView, RepoSelectedListener repoSelectedListener) {
            super (itemView);
            ButterKnife.bind (this, itemView);
            itemView.setOnClickListener (v -> {
                if (repo! = null ) {
                    repoSelectedListener.onRepoSelected (repo);
                }
            });
        }
        void bind (Repo repo) {
            this.repo = repo;
            repoNameTextView.setText (repo.name);
            repoDescriptionTextView.setText (repo.description);
            forksTextView.setText ( String .valueOf (repo.forks));
            starsTextView.setText ( String .valueOf (repo.stars));
        }
    }
}
Summary :
In the Dagger tutorial on Android, we learned about Dagger and its functionality and realized the benefits of using Dagger to develop Android apps.