Leak free dependency injection in Android

“paper boat on body of water” by Ahmed zayan on Unsplash

Dependency injection in Android is and always was a special case compared to desktop or server based applications. Neither do Android applications have a single entry point (a main function) nor do we control the lifecycle of its core components like Activities, Fragments or Services. We cannot just inject a single main class and the whole dependency graph is build upon that. Instead injection happens at multiple locations, the already mentioned core components of Android. We should also differentiate between locating (or requesting) a dependency and injecting one. When we explicitly ask for a dependency we locate it. This is what happens at the root level of Android’s components when we apply dependency “injection”. On the other hand when a dependency is provided through a class’s constructor or field implicitly we talk about injection. Consequently dependency injection in Android is a hybrid of the service locator and dependency injection patterns.

Since the lifecycle of Android framework components is managed by the system itself, Android applications are susceptible to memory leaks. When references to components are held incorrectly, the references cannot be garbage collected although according to Android the component is already destroyed and should free its resources. This can easily happen when for example a Context instance is passed around and finally ends up as a reference in a class that for some reason is not eligible for garbage collection. Perhaps because this instance of a class is a singleton object. Memory leaks might not immediately crash an application but will slowly degrade the application’s performance while the app is running or introduce bugs which are hard to reproduce and fix. Memory leaks can be revealed during development with Android’s StrictMode or libraries like LeakCanary. I claim that a memory leak is one of the most common programming errors of novice Android developers.

So what do memory leaks have to do with dependency injection? Since dependencies are injected into classes rather than created manually or passed as function arguments, the source of a dependency might not be obvious immediately. While this sounds like a disadvantage first, dependency injection decouples your classes thereby improving modularisation and testability. The responsibility of your dependencies is delegated to a greater system. Assuming that one of the injected dependencies is a framework component or might have a transitive dependency on one, you could easily forget that this dependency needs to be handled with special care. However when injection of Android framework classes is just an implementation detail of an interface, the interface can stay free of any framework classes thus improving reusability and testability. So should I inject Android classes or not? One solution could be to declare that Android components are not part of the dependency injection and that they cannot be injected. This is okay but I think that this strategy greatly reduces the meaningfulness of the dependency injection pattern. We need a solution that facilitates the injection of Android classes in a leak-free manner. Luckily Katana was designed especially with this in mind!

In contrast to other Kotlin-specific dependency injection solutions for Android Katana does not work with a global singleton state. Without a global state we also don’t have to think about explicitly releasing Android classes when they are part of the injection, thereby minimising the risk of memory leaks. Katana dedicates memory management to the garbage collector, which has been with the JVM, DVM and ART from the very beginning. Katana borrows the concepts of Modules and Components (not to be confused with Android framework components) from Dagger 2.0. Modules declare how dependencies are provided. Components are responsible for the actual injection and also hold singleton instances of dependencies. A Component reference must only be held by a single Android framework class, for instance an Activity. Once the Activity is destroyed and eligible for garbage collection, so is the Component and all its managed dependencies. This is the reason why it’s possible to safely inject Android classes with Katana, even for instance the current Activity!

Enough talk, let’s dive into a bit of code.
First of all we create an application-wide Component. This Component is responsible for providing dependencies that are available throughout the whole runtime of the app. To set up and retain the Component we need a custom Application class:

const val APP_CONTEXT = "APP_CONTEXT"

fun createApplicationModule(app: MyApp) = createModule {
bind<Context>(name = APP_CONTEXT) { singleton { app } }
}
class MyApp : Application() {

override fun onCreate() {
super.onCreate()
        applicationComponent = createComponent(
createApplicationModule(this)
)
}

companion object {
lateinit var applicationComponent: Component
}
}

As you can see we first created a new Module for the Component. This Module declares a named binding of the application Context bound to the constant APP_CONTEXT. The newly created Component can now be accessed via MyApp.applicationComponent throughout the whole application. Imagine the possibilities here. The application Component could for instance also provide injections of Android system services, utility classes and so forth.

Let’s take a look at our MainActivity. For the sake of simplicity, in this tutorial we just want to show a Toast message on the press of a button. The message should be displayed by a helper class Greeter.

class Greeter(private val context: Context) {

fun greet() {
Toast.makeText(context,
R.string.greeting,
Toast.LENGTH_LONG).show()
}
}

Let’s define a new Module for MainActivity:

fun createMainModule(context: Context) = createModule {

bind<Context> { singleton { context } }

bind<Greeter> { factory { Greeter(get()) } }
}

The current Context, which is going to be the Activity, is passed to the function and thus part of the module’s scope. A binding for the Context is declared as well as a binding for Greeter. Pay attention to how the Context is injected into the constructor of Greeter via get().

Now we create the Component in the Activity:

class MainActivity : AppCompatActivity() {

private val component = createComponent(
modules = listOf(createMainModule(this)),
dependsOn = listOf(MyApp.applicationComponent)
)
private val greeter: Greeter by component.inject()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setContentView(R.layout.activity_main)

findViewById<Button>(R.id.button).setOnClickListener {
greeter.greet()
}
}
}

When we create the Component we pass the main Module to the list of modules. Obviously we could pass multiple modules here, separating modules into logical units. We also declare that the Component depends on the application Component. As a result the main Component inherits all dependencies of the application Component. On the next line you can see that the Greeter dependency is located by the component.inject() delegate. Finally greeter is accessed in setOnClickListener.

This very simple example illustrates how Android framework components like Context can be safely injected with Katana. A more complex example can be found here.