2022-07-28

"Lateinit property `app` has not been initialized" Error after Hilt Migration

I'm migrating our codebase from Dagger 2 to Hilt. I'm kind of a noob. I've gotten everything to build and compile. There is a crasher on app launch with this error:

Logcat

2022-07-27 11:31:26.297 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.appname.company, PID: 17599
    java.lang.RuntimeException: Unable to create application com.appname.BaseApplication: kotlin.UninitializedPropertyAccessException: lateinit property app has not been initialized
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6767)
        at android.app.ActivityThread.access$1500(ActivityThread.java:256)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2091)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7870)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
     Caused by: kotlin.UninitializedPropertyAccessException: lateinit property app has not been initialized
        at com.appname.BaseApplication$Companion.getApp(BaseApplication.kt:746)
        at com.appname.BaseApplication$Companion$sharedPrefs$2.invoke(BaseApplication.kt:839)
        at com.appname.BaseApplication$Companion$sharedPrefs$2.invoke(BaseApplication.kt:838)
        at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
        at com.appname.BaseApplication$Companion.getSharedPrefs(BaseApplication.kt:838)
        at com.appname.BaseApplication$Companion.getOverrideUrl(BaseApplication.kt:894)
        at com.appname.BaseApplication$Companion.getBaseUrl(BaseApplication.kt:908)
        at com.appname.db.network.NetworkService.<init>(NetworkService.kt:47)
        at com.appname.DaggerBaseApplication_HiltComponents_SingletonC$Builder.build(DaggerBaseApplication_HiltComponents_SingletonC.java:308)
        at com.appname.Hilt_BaseApplication$1.get(Hilt_BaseApplication.java:22)
        at dagger.hilt.android.internal.managers.ApplicationComponentManager.generatedComponent(ApplicationComponentManager.java:40)
        at com.appname.Hilt_BaseApplication.generatedComponent(Hilt_BaseApplication.java:33)
        at com.appname.Hilt_BaseApplication.onCreate(Hilt_BaseApplication.java:41)
        at com.appname.BaseApplication.onCreate(BaseApplication.kt:182)
        at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1223)

The property app in BaseApplication is referenced all over the codebase.

BaseApplication.kt

@HiltAndroidApp
open class BaseApplication : Application() {
    ....

    // (Injections in case they are relevant) //

    @Inject
    lateinit var hsAnalytics: HsAnalytics

    ....

    @Inject @field:Named(NetworkService.Keys.GRAPH_QL_OKHTTP_CLIENT)
    lateinit var okHttpClient: OkHttpClient

    @Inject
    lateinit var apolloClient: ApolloClient
    ....
    
    // onCreate()
    override fun onCreate() {
        super.onCreate()

        // Assign static "app" variable before ANYTHING else. <- This note was in the code base from an old dev that is no longer around
        app = this
    
        ....
    }

        ....
    companion object {
        ....
        // TODO: Should consider whether or not these should be static <- Notes from old dev who isn't around anymore
        //<editor-fold desc="Utility methods to help non-context classes retrieve context-related objects">
        @JvmStatic
        lateinit var app: BaseApplication
            private set
        ....
    }
}

AppModule.kt

@Module
@InstallIn(SingletonComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideApplication(@ApplicationContext application: BaseApplication): BaseApplication {
        return application as BaseApplication
    }

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

Any idea why app suddenly isn't being instantiated?

Update - NetworkService.kt

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

    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 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 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 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(@ApplicationContext context: Context): ChuckerInterceptor {
        return ChuckerInterceptor(context)
    }

    @Provides
    @Singleton
    @Inject
    fun providesHarInterceptor(@ApplicationContext 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
}


No comments:

Post a Comment