Dagger2 / Modules / Muselee

Muselee 6: Core Module

Muselee is a demo app which allows the user to browse popular music artists. It is not intended to be a fully-featured user app, but a vehicle to explore good app architecture, how to implement current best-practice, and explore how the two often go hand in hand. Moreover it will be used to explore how implementing some specific patterns can help to keep our app both maintainable, and easy to extend.

In the last article we looked at the app module and saw how minimalistic it is – it is essentially just the application level components., which makes a certain amount of sense! Next we’ll look at the core module which, as we have already discussed, needs to be kept fairly lean as well. We need to be disciplined in including only those components that really are common to all of the others. One example of this is downloadable font definitions. Downloadable fonts may be used by many components and are likely to be a part of an overall design style for the entire app. Also they are unlikely to change that often, so they are a good candidate for inclusion within the core module. They could not go in the app module because then they would not be available to the feature modules which do not have a dependency on the app module. I won’t bother showing these files here because they are fairly standard stuff – they are in the res folder of the app source if you want to take a look at them.

The next thing that we’re going to include in the core module is a utility class which will perform connectivity checking.. We’ll keep this really simple for now – we just want to know whether or not we have an active network connection, and we don’t require any logic which may depend upon the quality of the connection:

class ConnectivityChecker @Inject constructor(private val connectivityManager: ConnectivityManager) {

    val isConnected: Boolean
        get() = connectivityManager.activeNetworkInfo?.isConnected ?: false

}

This has a dependency upon ConnectivityManager, but this is already provided by the app module in the ApplicationModule which we looked at in the last article. This shows some of the versatility that dependency injection gives us. Although the core module does not have a dependency upon the app module, it can still use components which the app module adds to the dependency graph.

Next we’ll take a look at the DI for some basic network functionality:

@Module
object CoreNetworkModule {

    @Provides
    @JvmStatic
    internal fun providesLoggingInterceptor(): HttpLoggingInterceptor? =
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            }
        } else null

    @Provides
    @JvmStatic
    internal fun providesOkHttpClientBuilder(
        loggingInterceptor: HttpLoggingInterceptor?
    ): OkHttpClient.Builder =
        OkHttpClient.Builder()
            .apply {
                loggingInterceptor?.also {
                    addInterceptor(it)
                }
            }
}

Many feature modules will require an OkHttpClient instance upon which to build network calls. By providing these from the core module we are able to apply consistent logging behaviour throughout the app. We’ll see some further examples of how this approach can benefit us later in the series.

The final major piece of functionality that we’ll add to the core module is the ability to instantiate Jetpack ViewModel instances dynamically. This requires a little bit of infrastructure:

@Singleton
class ViewModelFactory @Inject constructor(
    private val viewModelProviders: Map, @JvmSuppressWildcards Provider>
) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun  create(modelClass: Class): T {
        val provider = viewModelProviders[modelClass]
            ?: viewModelProviders.entries.first { modelClass.isAssignableFrom(it.key) }.value

        return provider.get() as T
    }
}

This is the ViewModelFactory that we looked at in this article, so I won’t give a fully detailed explanation here. it provides us with a mechanism for obtaining ViewModel implementations of specific types. This is made available as a ViewModelProvider.Factory instance which is available to the AndroidInjector component:

@Module
abstract class BaseViewModule {

    @Singleton
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory) : ViewModelProvider.Factory
}

So this allows us to provide specific ViewModel implementations to the AndroidInjector, but we still require a mechanism to actually provide these specific ViewModel implementations. We do this through an annotation:

@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass)

We don’t actually define any ViewModel implementations within the core module so we can see how this all works together, but we’ll look at that in the next article.

Next we’ll add is a really simple interface which will come in really handy later on:

interface DataProvider {

    fun requestData(callback: (items: List) -> Unit)
}

This requests data of a specific type, and passes it on to a callback function. We’ll explore the full scope of what this seemingly very simple interface offers us later on in the series.

Finally we’ll add another interface which will come in handy later on:

interface DataMapper {
    fun map(source: S): R
}

This interface defines a behaviour for mapping an object of one type in to another.

So while we have a project that compiles, it still really doesn’t do very much. However that will change in the next article where we’ll add out first feature module which will display a list of the top artists obtained from the last.fm API.

The source code for this article is available here.

© 2019, Mark Allison. All rights reserved.

Copyright © 2019 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.