Dagger Dagger is quite popular in Android development. It is a dependency injection framework that provides fully static and compile-time generated code. It solves many of the development and performance problems encountered in reflectivity based implementations.

To give you a better understanding of how Dagger works, we’ve released a new tutorial in 2019. This article focuses on how to use Dagger in Kotlin, including best practices for optimizing build times and some problems you might encounter.

The Dagger is implemented using Java’s annotation model, and while Kotlin’s annotations are not written in a way that corresponds to Java’s, this article will focus on the differences and how you can easily use the Dagger with Kotlin.

The writing of this article was inspired by some of the suggestions in the Dagger Issue, which directly represent best practices and some pain points for using Dagger in Kotlin. Thanks to all of the issue contributors.

Improve build efficiency

In order to shorten build time, support for Gradle’s Incremental Annotation Processing has been added to The Dagger in v2.18. This feature is enabled by default in the Dagger V2.24 release. If you are using an earlier version, you need to add the following lines of code to activate this feature.

In addition, you can configure the Dagger not to format the generated code. This option was added in the Dagger version V2.18 and is the default behavior in v2.23 (formatting code is no longer generated). If you are using a later version, you can also add the following code to disable the formatting code to reduce build time.

Add the following compile parameters to build.gradle to improve the Dagger’s performance at build time:

allprojects {
    afterEvaluate {
        extensions.findByName('kapt')? .arguments { arg("dagger.formatGeneratedSource"."disabled")
            arg("dagger.gradle.incremental"."enabled")}}}Copy the code

Also, if you are using a Kotlin DSL script file, you need to include the following in your build.gradle.kts file:

kapt {
    arguments {
        arg("dagger.formatGeneratedSource"."disabled")
        arg("dagger.gradle.incremental"."enabled")}}Copy the code

Use Qualifier as the field property

When you add an annotation to a Property in Kotlin, it is not clear whether Java will eventually get the annotation in the property’s field or method. Adding the field: prefix before an annotation ensures that the qualifier is applied in the right place (see the official documentation for more details).

The correct way for ✅ to apply a qualifier to an injected field is as follows:

@Inject @field:MinimumBalance lateinit var minimumBalance: BigDecimal
Copy the code

❌ the following is not correct:

@Inject @MinimumBalance lateinit var minimumBalance: BigDecimal 
// @minimumBalance is ignored
Copy the code

Forgetting to add field: If there is an instance in the Dagger that does not match this type, this may result in injection into the wrong object.

This problem has been fixed in the Dagger version V2.25. If you were using that version, you would have had problems writing this way before, but now you won’t.

@Inject @MinimumBalance lateinit var minimumBalance: BigDecimal 
// Fixed: @minimumBalance is no longer ignored
Copy the code

Use the static @Provides method to improve performance

The Dagger generated code will perform better if you use the static @Provides method. To do this, use the object in Kotlin instead of the class, and add the @jVMStatic annotation before the method. This is a best practice you should follow whenever possible.

@Module
object NetworkModule {

    @JvmStatic
    @Provides
    fun provideOkHttpClient(a): OkHttpClient {
        return OkHttpClient.Builder().build()
    }
}
Copy the code

If you want to use abstract methods, you need to add @jVMStatic to the Companion Object and add the @Module annotation.

@Module
abstract class NetworkModule {

    @Binds abstract fun provideService(retrofitService: RetrofitService): Service

    @Module
    companion object {
    
        @JvmStatic
        @Provides
        fun provideOkHttpClient(a): OkHttpClient {
            return return OkHttpClient.Builder().build()
        }
    }
}
Copy the code

Alternatively, you can extract the object module code and include it in the abstract module:

@Module(includes = [OkHttpClientModule::java])
abstract class NetworkModule {

    @Binds abstract fun provideService(retrofitService: RetrofitService): Service

}

@Module
object OkHttpClientModule {

    @JvmStatic
    @Provides
    fun provideOkHttpClient(a): OkHttpClient {
        return OkHttpClient.Builder().build()
    }
}
Copy the code

In the Dagger V2.25 version, you no longer need to use @jVMstatic to mark the @Provides function; the Dagger recognizes it correctly.

Generic injection

Kotlin uses wildcards to compile generics so that the Kotlin API can be used with Java. When a parameter or field has a generic type, it is automatically generated in Java code. For example, Kotlin’s code List argument would appear in Java as List
.

This feature causes problems in the Dagger, however, because it expects the type to be a complete (also called invariant) match. Using @jVMSuppresswildCards will ensure that the Dagger sees no wildcard type.

This is a common problem you encounter when using the Dagger multiple binding feature, such as:

class MyVMFactory @Inject constructor(
  private val vmMap: Map<String, @JvmSuppressWildcards 
Provider<ViewModel>>

) { 
    ... 
}
Copy the code

In the Dagger v2.25 version, you will no longer need to use @jVMSuppresswildCards and the Dagger will recognize it correctly.

Inline method body

The Dagger determines the type configured by the @Provides method by checking the return value type. Return types are optional in Kotlin functions, and even ides sometimes suggest that you refactor your code to use inline method bodies to hide declarations of return value types.

Bugs can occur if the inferred type is different from what you expect. Let’s look at some examples:

If you want to add a specific type to the Dagger, using inline would be the best choice. Let’s look at another way to achieve the same effect in Kotlin:

@Provides 
fun provideNetworkPrinter(a) = NetworkPrinter()

@Provides 
fun provideNetworkPrinter(a): NetworkPrinter = NetworkPrinter()

@Provides 
fun provideNetworkPrinter(a): NetworkPrinter {
  return NetworkPrinter()
}
Copy the code

If you need to provide an implementation of the interface, you must display the specified return type. Failure to do so can cause problems:

@Provides
/ / configuration Printer
fun providePrinter(a): Printer = NetworkPrinter()

@Provides
// configure NetworkPrinter, not a normal Printer
fun providePrinter(a) = NetworkPrinter()
Copy the code

Dagger Dagger Is basically Kotlin compatible, but you still have to take care to make sure the code doesn’t go wrong: Use @Field: to qualify field properties, inline method bodies, and annotated @JVMSuppresswildCards when you inject the collection.

This Dagger optimization comes with no additional cost, and following best practices such as enabling incremental annotation processing, disabling formatting Settings, and using the static @Provides method can shorten the build time of the project.