Dagger with Retrofit Example – Set up Dependency Injection

Today, we’re going to learn about Dagger 2. Dagger 2, first of all, is really an annotation processor or just the code generator. Many of the people seem to think that Dagger 2 is how you do dependency injection, actually, it’s not. It is just a boilerplate code generator and dependency injection is something that you code yourself.

Dagger use these following annotations.

  1. @Module and @Provide: Module classes are basically there for providing an object which can be injected in future.
  2. @Component: Component is an interface and knows everything about object creation. Every object we want to use delivered by the component in some way.
  3. @Inject: Inject can be used on a constructor, object and on a field.
  4. @Qualifier: A Qualifier is an annotation tells you the difference between the two objects.

So, without further ado, we are going to start our example in which we learn how to work with Dagger2. In this example, we simply hit a URL and download some JSON text and display them into RecyclerView.

Android App Setup

For the libraries, we are using a sort of standard libraries. Below is the app level build.gradle file.

apply plugin: 'com.android.application'
apply plugin: 'com.jakewharton.butterknife'

android {
    compileSdkVersion 27
    buildToolsVersion "27.0.3"
    defaultConfig {
        applicationId "Change with your project name"
        minSdkVersion 19
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    } // For to enable java8
}

ext {
    supportVersion = '27.1.0'
    daggerVersion = '2.13'
    retrofitVersion = '2.3.0'
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    // Dagger dependencies
    implementation "com.google.dagger:dagger-android:$daggerVersion"
    implementation "com.google.dagger:dagger:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"

    // Timber dependency
    implementation'com.jakewharton.timber:timber:4.6.0'

    // Retrofit dependencies
    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"

    // OkHttp3 dependencies
    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
    implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

    // Picasso dependency
    implementation 'com.squareup.picasso:picasso:2.5.2'

    // Android support dependencies
    implementation "com.android.support:appcompat-v7:$supportVersion"
    implementation "com.android.support:recyclerview-v7:$supportVersion"
    implementation "com.android.support:cardview-v7:$supportVersion"
    implementation "com.android.support:design:$supportVersion"
    implementation "com.android.support:support-v4:$supportVersion"
    implementation "com.android.support:animated-vector-drawable:$supportVersion"

    // Ui constraint dependency
    implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta6'

    testImplementation 'junit:junit:4.12'
}

All the dependencies which we require through the app are written in components.

Main App level Component.

@CustomApplicationScope
@Component(modules = {ServiceUtilModule.class, PicassoModule.class})
public interface ServiceApplicationComponent {

    Picasso getPicasso();

    ServiceUtil getServiceUtil();  
}

We are telling our component in order to generate these instances you need ServiceUtilModule.class and PicasoModule.class.

You see all the internal stuff should be kept internally. We don’t want that stuff flying off the sunset. We don’t want people to use our dependency graph to know about the how do we make Picasso and ServiceUtil Object. It should be automated and under the hood.

Now we need to provide Picasso and ServiceUtil instance where does he get that from because dagger can’t magically generate all this stuff. This is where Module comes to play.

One last thing you guys noticed in code is a CustomApplicationScope annotation. Scope means any instance we got of that object should be a single instance. This is what a scope does.

CustomApplicationScope

@Scope
@Retention(RetentionPolicy.CLASS)
public @interface CustomApplicationScope {
}

Now in which object, class or method you include @CustomApplicationScope that means you need a single instance of that object throughout the app.

ServiceUtilModule

@Module(includes = NetworkModule.class)
public class ServiceUtilModule {

    private static final String BASE_URL = "https://www.frisbeego.com/secure/index.php/";

    @Provides
    @CustomApplicationScope
    ServiceUtil getServiceUtil(Retrofit retrofit) {   // How does the retrofit object came as a parameter to this function.You see @Provides annotation on every method this means it providing that object.
        return retrofit.create(ServiceUtil.class);
    }

    @Provides
    @CustomApplicationScope
    Gson getGson() {
        GsonBuilder gsonBuilder = new GsonBuilder();
        return gsonBuilder.create();
    }

    @Provides
    @CustomApplicationScope
    Retrofit getRetrofit(OkHttpClient okHttpClient, Gson gson) {
        return new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(BASE_URL)
                .client(okHttpClient)
                .build();
    }
}

Now our ServiceUtilModule class also depends on NetworkModule and for that we need NetworkModule.

NetworkModule

@Module(includes = ApplicationContextModule.class)
public class NetworkModule {

    @Provides
    @CustomApplicationScope
    HttpLoggingInterceptor getInterceptor() {
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(message -> Timber.i(message));
        interceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
        return interceptor;
    }

    @Provides
    @CustomApplicationScope
    Cache getCache(File cacheFile) {
        return new Cache(cacheFile, 10 * 1000 * 1000);  // 10 MiB cache
    }

    @Provides
    @CustomApplicationScope
    File getFile(@ApplicationContextQualifier Context context) {
        File file = new File(context.getFilesDir(), "cache_dir");
        if (!file.exists())
            file.mkdirs();
        return file;
    }

    @Provides
    @CustomApplicationScope
    OkHttpClient getOkHttpClient(HttpLoggingInterceptor interceptor, Cache cache) {
        return new OkHttpClient.Builder()
                .writeTimeout(15, TimeUnit.SECONDS)
                .readTimeout(15, TimeUnit.SECONDS)
                .connectTimeout(15, TimeUnit.SECONDS)
                .cache(cache)
                .addInterceptor(interceptor)
                .build();
    }
}

NetworkModule also depends on ApplicationContextModule class. Now you guys are getting the clear picture that our graph of dependencies are getting bigger and bigger and we need to structure it.

ApplicationContextModule

@Module
public class ApplicationContextModule {

    private final Context context;

    public ApplicationContextModule(Context context) {
        this.context = context.getApplicationContext();
    }

    @Provides
    @CustomApplicationScope
    @ApplicationContextQualifier
    Context getContext() {
        return context;
    }
}

You see in here we have a new annotation called @AppilcationContextQualifier. Now why we need @Qualifer here because in an android app there are two types of context that’s why we need to specify which context we are providing.

ApplicationContextQualifier

@Qualifier
public @interface ApplicationContextQualifier {

}

Here’s everything is done for ServiceUtilModule.

PicassoModule

@Module(includes = NetworkModule.class)
public class PicassoModule {

    @Provides
    @CustomApplicationScope
    Picasso getPicasso(@ApplicationContextQualifier Context context, OkHttp3Downloader okHttp3Downloader) {
        return new Picasso.Builder(context)
                .downloader(okHttp3Downloader)
                .build();
    }

    @Provides
    @CustomApplicationScope
    OkHttp3Downloader getOkHttp3Downloader(OkHttpClient okHttpClient) {
        return new OkHttp3Downloader(okHttpClient);
    }
}

You see in here PicassoModule also depends on NetworkModule. Here we are going to reuse our NetworkModule for PicassoModule which we previously use in ServiceUtilModule.

This was all for the dependency injection. Let’s clap your hand with joy because you guys have learned something which people uses in the huge application.

Alright, guys, I’m going to end this blog here, for further reading see my next blog.

I hope this blog gives you a good understanding of Dagger. If you’ve any queries regarding this post please do comment below.

Thank you for being here and keep reading…

Next Part

LEAVE A REPLY

Please enter your comment!
Please enter your name here