OkHttp Interceptor Multibindings with Dagger (ENG)

Introduction

Looking at the structure of the app I am currently using, the sub-project depends on the core module.
In the world, this structure called this a ‘Multi-Module projects’
Of course, the ‘Core’ module managed by the ‘JFrog artifactory’ server.

One reason for using Multi-Module projects structure is there are many boilerplate codes for an individual project.
Another reason is code can be recyclable with not depend on the specifics of individual projects.

So that, Dagger also managed both modules such as ‘Core’ module and sub-projects.
In ‘Core’ module, they have ‘@Module’ class, In sub-projects, they have project-specific ‘@Module’ class and ‘@Component’ class.

The thing with I am, ‘Retrofit’ or ‘OKHttpClient’ for networking module managed with ‘Core’ module, but Interceptor of OKHttpClient can have specifics of projects. Also, Interceptor might be 1 or more.

Solve this problem, Make interceptors into collections and inject when making an instance using ‘Multibinding’ feature of Dagger.

The language of example code is Kotlin and Java.

What is Multibindings?

First, we should explain about ‘Multibinding’ in Dagger.

Multibindings is a feature that has the power to collect the same type of instance with binding in a different module.
In Dagger, process with collections did not depend on individual bindings.

Dagger provides two kinds of Multibindings, one is ‘@IntoSet’ annotation, other is ‘@IntroMap’ annotation.

Two ‘Multibindings’ annotations provide a different solution for making collections.
One of the annotations, ‘@IntoSet’ provides the collection of instances with ‘Set’.
Another ‘@IntoMap’ provides the collection of instances with ‘Map<Class<*>, Provider<T>>’.

Today, I’ll use ‘@IntoSet’ annotation.

Make the Interceptor

First, I need Interceptor that provides into Dagger.

    class TestInterceptor : Interceptor {
        @Throws(IOException::class)
        override fun intercept(chain: Interceptor.Chain): Response? {
            val original = chain.request()
            val originalHttpUrl = original.url()
            val requestBuilder = original.newBuilder()
                .url(originalHttpUrl.newBuilder().build())
            return chain.proceed(requestBuilder.build())
        }
    }

Next, Provide Interceptor into ‘@Module’ annotated class.

    @Module
    public class AppInterceptorModule {
        @Provides
        @IntoSet
        public Interceptor provideTestInterceptor() {
            return new TestInterceptor();
        }
    }

The basic form of providing is same as other providers, except ‘@IntoSet’ annotations.
If you want to use ‘Multibindings’ feature in another class, You can set ‘Qualifier‘ annotation.

Inject when making an instance of OKHttpClient

    @Provides
        fun provideClient(interceptors: Set<@JvmSuppressWildcards Interceptor>): OkHttpClient {
            val builder = OkHttpClient().newBuilder()
            builder.readTimeout(Config.timeout.toLong(), TimeUnit.MILLISECONDS)
            builder.connectTimeout(Config.connectTimeout.toLong(), TimeUnit.MILLISECONDS)
            if (interceptors.isNotEmpty()) {
                interceptors.forEach {
                    builder.addInterceptor(it)
                }
            }
            return builder.build()
    }

Looking at the code of above, they have ‘Set’ parameters with named with ‘interceptors’.
Dagger will inject their collection of Interceptor into this parameter.

You can check the empty state of Set and add Interceptor into OKHttpClient using ‘builder.addInterceptor()’.

In this code, You might notice the ‘@JvmSuppressWildcards’ annotation in Type parameter of ‘Set’.
‘@JvmSuppressWildcards’ annotation is Kotlin annotations for interop with Java.

In default, Kotlin compiler converts ‘Set<Interceptor>’ into ‘Set<? extends Interceptor>’ type while Dagger needs `Set<Interceptor>. It can be a compile-time error.
In addition, If the type of Type parameters has a final modifier such as String, Kotlin compiler doesn’t generate wildcard.

Solve this problem, we can attach ‘@JvmSuppressWildcards’ to not convert to ‘Set’.

Looking generated code

When compiler builds success, we can check generated code in DaggerAppComponent class.

First, Dagger generated fields named ‘setOfInterceptorProvider’. this field contains an instance of Set.

    private Provider<Set<Interceptor>> setOfInterceptorProvider;

Here is code to assign ‘setOfInterceptorProvider’ field.

    this.setOfInterceptorProvider =
            SetFactory.<Interceptor>builder(2, 0)
                .addProvider(provideTestInterceptorProvider)
                .addProvider((Provider) logInterceptorProvider)
                .build();

In test projects, they have an interceptor in the ‘Core’ module and interceptor in sub-projects. so Dagger assigns the size of Set to 2.

Here is code to use ‘setOfInterceptorProvider’ field.

     this.provideClientProvider =
            BaseProvidesModule_ProvideClientFactory.create(
                builder.baseProvidesModule, setOfInterceptorProvider);

Then, Class named ‘BaseProvideModule_ProvideClientFactory’ uses ‘setOfInterceptorProvider’ to process binding into ‘provideClient’ methods.

public final class BaseProvidesModule_ProvideClientFactory implements Factory<OkHttpClient> {
    private final BaseProvidesModule module;
    private final Provider<Set<Interceptor>> interceptorsProvider;

    public BaseProvidesModule_ProvideClientFactory(BaseProvidesModule module, Provider<Set<Interceptor>> interceptorsProvider) {
        this.module = module;
        this.interceptorsProvider = interceptorsProvider;
    }

    public OkHttpClient get() {
        return provideInstance(this.module, this.interceptorsProvider);
    }

    public static OkHttpClient provideInstance(BaseProvidesModule module, Provider<Set<Interceptor>> interceptorsProvider) {
        return proxyProvideClient(module, (Set)interceptorsProvider.get());
    }

    public static BaseProvidesModule_ProvideClientFactory create(BaseProvidesModule module, Provider<Set<Interceptor>> interceptorsProvider) {
        return new BaseProvidesModule_ProvideClientFactory(module, interceptorsProvider);
    }

    public static OkHttpClient proxyProvideClient(BaseProvidesModule instance, Set<Interceptor> interceptors) {
        return (OkHttpClient)Preconditions.checkNotNull(instance.provideClient(interceptors), "Cannot return null from a non-@Nullable @Provides method");
    }
}

Conclusion

‘Multibindings’ is a special feature to process case that provides similar types of ‘Dagger’ or ‘Guice’ that based on code generations.

Also, ‘Multi-Module projects’ structure makes ‘Multibindings’ feature more special that they can help to manage the ‘Core’ module without depends on specifics of the project and manage sub-projects only have the project-specific code.