android – How to Make Sense of Dagger Hilt Build Output – Migrating from Dagger 2 to Hilt

Since posts are limited to 30,000 characters I will post the AppComponent and Modules here.

App Component:

@Singleton
@Component(modules = [AppModule::class, NetworkService::class, AnalyticsModule::class])
interface AppComponent {

    fun providesNetworkService(): NetworkService

    fun inject(activity: MapActivity)
    fun inject(viewModel: NetworkBaseViewModel)
    fun inject(application: BaseApplication)
    fun inject(contactsDataSourceFactory: ContactsDataSourceFactory)
    fun inject(contactDetailViewModel: ContactDetailViewModel)
    fun inject(contactDetailFragment: ContactDetailFragment)
    fun inject(contactDetailEditFragment: ContactDetailEditFragment)
    fun inject(contactDetailCreateFragment: ContactDetailCreateFragment)
    fun inject(contactsListViewModel: ContactsListViewModel)
    fun inject(activityFeedViewModel: ActivityFeedViewModel)
    fun inject(httpService: BaseHttpService)
    fun inject(httpService: BaseWebService.ClientWrapper)
    fun inject(pdfViewerFragment: PDFViewerFragment)
    fun inject(commuteTimeManager: CommuteTimeManager)
    fun inject(verifyLoginWorker: VerifyLoginWorker)
    fun inject(loginWebFragment: LoginWebFragment)
}

AppModule:

@InstallIn(SingletonComponent::class)
@Module
class AppModule constructor () {
    @Provides
    @Singleton
    fun provideApplication(@ApplicationContext application: Context): BaseApplication {
    return application as BaseApplication
    }

    @Provides
    @Singleton
    internal fun provideContext(@ApplicationContext application: Context): Context = application
}

NetworkService:

@Module
@InstallIn(SingletonComponent::class)
class NetworkService {

    object Keys {
        const val GRAPH_QL_OKHTTP_CLIENT = "graph_ql"
        const val ACTIVITY_FEED_RETROFIT = "activity_feed_retrofit"
        const val ACTIVITY_FEED_OKHTTP = "activity_feed_client"
    }

    private val baseUrl: String = BaseApplication.baseUrl
    private val feedBaseUrl: String = BaseApplication.feedBaseUrl

    lateinit var api: NetworkServiceAPI
    lateinit var feedApi: FeedNetworkServiceAPI
    lateinit var harInterceptor: HarInterceptor

    @Provides
    @Singleton
    internal fun provideAPI(retrofit: Retrofit): NetworkServiceAPI {
        return retrofit.create(NetworkServiceAPI::class.java)
    }

    @Provides
    @Singleton
    internal fun provideFeedAPI(@Named(Keys.ACTIVITY_FEED_RETROFIT) retrofit: Retrofit): FeedNetworkServiceAPI {
        return retrofit.create(FeedNetworkServiceAPI::class.java)
    }

    @Provides
    @Singleton
    internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
        val contentType = "application/json".toMediaType()
        return Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(defaultJson.asConverterFactory(contentType))
//                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(okHttpClient)
                .build()
    }

    @Provides
    @Singleton
    @Named(Keys.ACTIVITY_FEED_RETROFIT)
    internal fun provideFeedRetrofit(@Named(Keys.ACTIVITY_FEED_OKHTTP) okHttpClient: OkHttpClient): Retrofit {
        val contentType = "application/json".toMediaType()
        val converter = defaultJson.asConverterFactory(contentType)
        return Retrofit.Builder()
                .baseUrl(feedBaseUrl)
                .addConverterFactory(converter)
                .client(okHttpClient)
                .build()
    }

    @Provides
    @Singleton
    @Inject
    internal fun provideClient(chuckerInterceptor: ChuckerInterceptor,
    harInterceptor: HarInterceptor): OkHttpClient {
        val builder = OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .addCertificateTransparencyInterceptor()

        builder.addInterceptor(object : Interceptor {
            @Throws(IOException::class)
            override fun intercept(chain: Interceptor.Chain): Response {
                val url = chain.request().url.toString()
                Timber.d("Calling HomeSpotter URL: $url")
                val requestBuilder = chain.request().newBuilder()

                // Add every default header to all connections that are made
                val headers = Brand.getInstance().getHttpHeaders(url)
                for ((key, value) in headers) {
                    requestBuilder.addHeaderRemovingSymbols(key, value)
                }

                return chain.proceed(requestBuilder.build())
            }
        })
                .addNetworkLoggingInterceptor(chuckerInterceptor)
                .addInterceptor(harInterceptor)

        if (DEBUGGABLE) {
            // In Debug, log network requests
            val logging = HttpLoggingInterceptor()
            logging.level = HttpLoggingInterceptor.Level.BASIC
            builder.addInterceptor(logging)
                    .addInterceptor(CurlInterceptor { message -> Timber.d(message) })
        }

        return builder.build()
    }

    @Provides
    @Singleton
    @Inject
    @Named(Keys.ACTIVITY_FEED_OKHTTP)
    internal fun provideActivityFeedClient(
            chuckerInterceptor: ChuckerInterceptor,
            harInterceptor: HarInterceptor): OkHttpClient {
        val builder = OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .addCertificateTransparencyInterceptor()

        builder.addInterceptor(object : Interceptor {
            @Throws(IOException::class)
            override fun intercept(chain: Interceptor.Chain): Response {
                val url = chain.request().url.toString()
                Timber.d("Calling HomeSpotter URL: $url")
                val requestBuilder = chain.request().newBuilder()

                // Add every default header to all connections that are made
                val headers = Brand.getInstance().getHttpHeaders(url)
                val apiToken = BaseApplication.sharedPrefs.getString(AppConstants.API_TOKEN, null)
                apiToken?.let { headers["Api-key"] = it }
                headers.forEach { (key, value) ->
                    requestBuilder.addHeaderRemovingSymbols(key, value)
                }

                return chain.proceed(requestBuilder.build())
            }
        })
                .addNetworkLoggingInterceptor(chuckerInterceptor)
                .addNetworkInterceptor(harInterceptor)

        if (DEBUGGABLE) {
            // In Debug, log network requests
            val logging = HttpLoggingInterceptor()
            logging.level = HttpLoggingInterceptor.Level.BASIC
            builder.addInterceptor(logging)
                    .addInterceptor(CurlInterceptor { message -> Timber.d(message) })
        }

        return builder.build()
    }

    @Provides
    @Singleton
    @Inject
    @Named(Keys.GRAPH_QL_OKHTTP_CLIENT)
    internal fun provideGraphQlClient(
            chuckerInterceptor: ChuckerInterceptor,
            harInterceptor: HarInterceptor): OkHttpClient {
        val builder = OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .readTimeout(20, TimeUnit.SECONDS)
                .writeTimeout(20, TimeUnit.SECONDS)
                .addCertificateTransparencyInterceptor()

        builder.addInterceptor(object : Interceptor {
            @Throws(IOException::class)
            override fun intercept(chain: Interceptor.Chain): Response {
                val url = chain.request().url.toString()
                Timber.d("Calling HomeSpotter URL: $url")
                val requestBuilder = chain.request().newBuilder()

                // Add every default header to all connections that are made
                val headers = Brand.getInstance().getHttpHeaders(url)
                val useSuperToken = false
                val superUserToken = BaseApplication.appResources.getString(R.string.activity_feed_super_user_token)
                val apiToken = if (useSuperToken && DEBUGGABLE && superUserToken.isNotEmpty()) {
                    superUserToken
                } else {
                    BaseApplication.sharedPrefs.getString(AppConstants.API_TOKEN, null)
                }
                if (apiToken != null) headers["Authorization"] = "Bearer $apiToken"
                headers.forEach { (key, value) ->
                    requestBuilder.addHeaderRemovingSymbols(key, value)
                }

                return chain.proceed(requestBuilder.build())
            }
        })

                .addNetworkLoggingInterceptor(chuckerInterceptor)
                .addNetworkInterceptor(harInterceptor)

        if (DEBUGGABLE) {
            // In Debug, log network requests
            val logging = HttpLoggingInterceptor()
            logging.level = HttpLoggingInterceptor.Level.BASIC
            builder.addInterceptor(logging)
                    .addInterceptor(CurlInterceptor { message -> Timber.d(message) })
        }

        return builder.build()
    }

    private fun OkHttpClient.Builder.addCertificateTransparencyInterceptor(): OkHttpClient.Builder = apply {
        addNetworkInterceptor(certificateTransparencyInterceptor())
    }

    private fun OkHttpClient.Builder.addNetworkLoggingInterceptor(interceptor: ChuckerInterceptor): OkHttpClient.Builder = apply {
        if (BaseApplication.sharedPrefs.getBoolean(AppConstants.DEBUG_ENABLE_NETWORK_LOGGING, false)) {
            addNetworkInterceptor(interceptor)
        }
    }

    @Provides
    @Singleton
    @Inject
    fun providesNetworkLoggingInterceptor(context: Context): ChuckerInterceptor {
        return ChuckerInterceptor(context)
    }

    @Provides
    @Singleton
    @Inject
    fun providesHarInterceptor(context: Context): HarInterceptor {
        return HarInterceptor(context)
    }

    @Provides
    @Singleton
    @Inject
    fun providesApolloClient(@Named(Keys.GRAPH_QL_OKHTTP_CLIENT) okHttpClient: OkHttpClient): ApolloClient {
        val apiUrl: String = BaseApplication.apiBaseUrl
        return ApolloClient.builder().serverUrl("$apiUrl/graphql")
                .okHttpClient(okHttpClient).build()
    }

    @Provides
    @Singleton
    internal fun provideGson(): Gson {
        return GsonBuilder()
                .setLenient()
                .serializeNulls()
                .create()
    }

    @Provides
    @Singleton
    @Inject
    fun providesNetworkService(api: NetworkServiceAPI,
                               feedApi: FeedNetworkServiceAPI,
                                harInterceptor: HarInterceptor): NetworkService {
        this.api = api
        this.feedApi = feedApi
        this.harInterceptor = harInterceptor
        return this
    }

    companion object {
        val defaultJson = Json {
            encodeDefaults = true
            ignoreUnknownKeys = true
            isLenient = true
            allowSpecialFloatingPointValues = false
        }
    }

}

fun Request.Builder.addHeaderRemovingSymbols(key: String, value: String?): Request.Builder {
    Timber.d("Header: $key : $value")
    value?.let { return addHeader(key, it.replace(Regex("[^\u0020-\u007e‘’]"), "")) }
    return this
}

Analytics Module:

@Module
@InstallIn(SingletonComponent::class)
class AnalyticsModule {

    @Provides
    @Singleton
    @Inject
    fun providesHsAnalytics(firebaseAnalytics: FirebaseAnalytics?, segmentAnalytics: Analytics?): HsAnalytics {
        return HsAnalytics(firebaseAnalytics, segmentAnalytics)
    }

    @Provides
    @Singleton
    @Inject
    fun providesSegmentAnalytics(application: BaseApplication): Analytics? {
        val segmentKey = application.getString(if (BaseApplication.isReleaseBuild) R.string.segment_release_key else R.string.segment_dev_key)
        if (segmentKey.isNotEmpty()) {
            return Analytics.Builder(application, segmentKey)
                    .trackApplicationLifecycleEvents()
                    .build()
                    .apply { updateIdentity(application) }
        }
        Timber.d("Segment analytics not set-up: Key: $segmentKey")
        return null
    }

    @Provides
    @Singleton
    @Inject
    fun providesFirebaseAnalytics(application: BaseApplication): FirebaseAnalytics? {
        return try {
            FirebaseAnalytics.getInstance(application)
        } catch(e: Exception) {
            Timber.e(e)
            null
        }
    }

}

Leave a Comment