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.
-
Ability to reuse written code
-
Ease of code modification
- Ease of testing
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.
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)
Communication between consumers and providers
An @Component Interface expresses the relationship between the provider of objects (modules) and objects, which expresses the dependency.
Scope annotations
Fields in Dagger
Use Dagger in Android Studio
implementation 'com.google.dagger: dagger: 2.15'
annotationProcessor 'com.google.dagger: dagger-compiler: 2.10'
implementation 'com.google.dagger: dagger-android: 2.15'
annotationProcessor 'com.google.dagger: dagger-android-processor: 2.10'
implementation 'com.google.dagger: dagger-android-support: 2.10'
android {
configurations. all {
resolutionStrategy.force 'com.google.code.findbugs: jsr305: 1.3.9'
}
}
Example of Dagger with RxJava and Retrofit with MVVM architecture
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 "
...
}
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);
}
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);
}
}
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 ;
}
}
}
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 ();
}
}
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.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);
}
}
}
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 ;
}
}
}
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 );
}
}
});
}
}
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));
}
}
}