Android Dependency Injection with Dagger

Did you ever face condition when building android application and all your component and application layer tightly coupled? And that's make your code to reuse and to test, one available solutions to that problem is dependency injection.

Dependency injection (DI) is a technique widely used in programming and well suited to Android development. By following the principles of DI, you lay the groundwork for good app architecture.

Source

Manual Dependency Injection

According to android official documentation, there is several approach to develop android application with proper dependency injection, including manual dependency injection. Manual dependency injection take benefit of application class that can be accessed by all activities. There is brief example how to use manual dependency injection

// Container of objects shared across the whole app
class AppContainer {

    // Since you want to expose userRepository out of the container, you need to satisfy
    // its dependencies as you did before
    private val retrofit = Retrofit.Builder()
                            .baseUrl("https://example.com")
                            .build()
                            .create(LoginService::class.java)

    private val remoteDataSource = UserRemoteDataSource(retrofit)
    private val localDataSource = UserLocalDataSource()

    // userRepository is not private; it'll be exposed
    val userRepository = UserRepository(localDataSource, remoteDataSource)
}

AppContainer will hold all your dependencies needs and will be shared across the whole app.

// Custom Application class that needs to be specified
// in the AndroidManifest.xml file
class MyApplication : Application() {

    // Instance of AppContainer that will be used by all the Activities of the app
    val appContainer = AppContainer()
}

In our application class, we define instance of app container.

class LoginActivity: Activity() {

    private lateinit var loginViewModel: LoginViewModel

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

        // Gets userRepository from the instance of AppContainer in Application
        val appContainer = (application as MyApplication).appContainer
        loginViewModel = LoginViewModel(appContainer.userRepository)
    }
}

To access the app container from all activities across a whole of app, we can use application that activity already provides.

Dagger

The manual dependency injection sounds simple and straightforward isn't it? But when our app grow and there a lot of dependencies coming, this approach will become boilerplate, Dagger comes to help you!

Component

You can use component interface to let dagger know that our objects need to access dagger graph or on manual dependency injection term is app container.

@Component
interface ApplicationComponent {
    // This tells Dagger that LoginActivity requests injection so the graph needs to
    // satisfy all the dependencies of the fields that LoginActivity is requesting.
    fun inject(activity: LoginActivity)
}

Adding to Dagger Graph

There is two ways to add our dependencies to dagger graph so that dependencies can be accessed on a whole app. First way, if your dependencies is class or object that you own, you can use inject.

// @Inject tells Dagger how to create instances of LoginViewModel
class LoginViewModel @Inject constructor(
    private val userRepository: UserRepository
) { ... }

If you want to add dependencies that the class or object you doesn't own you can create a module and provides an instances of it.

// @Module informs Dagger that this class is a Dagger Module
@Module
class NetworkModule {

    // @Provides tell Dagger how to create instances of the type that this function
    // returns (i.e. LoginRetrofitService).
    // Function parameters are the dependencies of this type.
    @Provides
    fun provideLoginRetrofitService(): LoginRetrofitService {
        // Whenever Dagger needs to provide an instance of type LoginRetrofitService,
        // this code (the one inside the @Provides method) is run.
        return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(LoginService::class.java)
    }
}

Sources:

https://developer.android.com/training/dependency-injection/manual

https://developer.android.com/training/dependency-injection/dagger-android#assisted-injection