google / dagger

A fast dependency injector for Android and Java.
https://dagger.dev
Apache License 2.0
17.42k stars 2.01k forks source link

[KSP][HILT][Android] Issue fetching data from DataStore, but no issue with SharedPreferences #4093

Closed Cuyer closed 12 months ago

Cuyer commented 12 months ago

Hi,

My problem is that when using DataStore, the data is saved correctly and updated correctly every time, but it is read incorrectly. ( I open applicationB from applicationA and I am passing data with intent) When I launch the application for the first time, I do not have access to the data I wanted to transfer. Only when the application is relaunched, the data is returned from the DataStore, but it is old data.

This problem does not occur when using SharedPreferences.

I assume it's Dagger's fault because the DataStore and SharedPreferences write the data correctly, but the DataStore can't read it fast enough. DataStore is a relatively new library, so maybe this isn't handled correctly.

I've tried to restrict the scope so I've added @InstallIn(ViewModelComponent::class) but it didn't work. I've tried fetching the data about ipAdress in Application() class, but it didn't work either

Below is my RetrofitModule in which I try to get ipAddress from DataStore:

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

    @Provides
    @Singleton
    fun provideNetworkJson(): Json = Json {
        ignoreUnknownKeys = true
    }

    @Provides
    @Singleton
    fun provideRetrofitApi(
        networkJson: Json,
        dataStore: PreferencesDataSource
    ): RetrofitApi {
        val refreshTokenInterceptor = createRefreshTokenInterceptor(dataStore)

        return Retrofit.Builder()
            .baseUrl(createUrl(dataStore))
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType())
            )
            .client(okHttpClient(refreshTokenInterceptor))
            .build()
            .create(RetrofitApi::class.java)
    }

    @Provides
    @Singleton
    fun providePlanogramApi(
        networkJson: Json,
        dataStore: PreferencesDataSource
    ): PlanogramApi {
        val refreshTokenInterceptor = createRefreshTokenInterceptor(dataStore)

        return Retrofit.Builder()
            .baseUrl(BuildConfig.PLANOGRAM_BACKEND_URL)
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType())
            )
            .client(okHttpClient(refreshTokenInterceptor))
            .build()
            .create(PlanogramApi::class.java)
    }

    private fun okHttpClient(refreshTokenInterceptor: Interceptor): OkHttpClient {
        return OkHttpClient().newBuilder()
            .addInterceptor(refreshTokenInterceptor)
            .addInterceptor(HttpLoggingInterceptor().apply {
                if (shouldLog()) {
                    level = HttpLoggingInterceptor.Level.BODY
                }
            })
            .build()
    }

    private fun createRefreshTokenInterceptor(dataStore: PreferencesDataSource): Interceptor {
        return Interceptor { chain ->
            val originalRequest = chain.request()
            val refreshToken = runBlocking {
                dataStore.userData.firstOrNull()?.token
            }
            val modifiedRequest = originalRequest.newBuilder()
                .header("Authorization", "Bearer $refreshToken")
                .build()
            chain.proceed(modifiedRequest)
        }
    }

    private fun shouldLog(): Boolean {
        return when (BuildConfig.FLAVOR) {
            "prod" -> false
            else -> true
        }
    }

    private fun createUrl(dataStore: PreferencesDataSource): String {
        val ipAddress = runBlocking {
            dataStore.userData.firstOrNull()?.serverIp
        }
        return when (ipAddress) {
            "" -> String.format(SERVER_URL_TEMPLATE, BuildConfig.BACKEND_URL)
            else -> String.format(SERVER_URL_TEMPLATE, ipAddress)
        }
    }

    companion object {
        private const val SERVER_URL_TEMPLATE = "http://%s:1010"
    }

}

DataStore Module:

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

    @Provides
    @Singleton
    fun provideUserPreferencesSerializer() : UserPreferencesSerializer {
        return UserPreferencesSerializer()
    }
    @Provides
    @Singleton
    fun provideUserPreferencesDataStore(
        @ApplicationContext context: Context,
        @Dispatcher(IO) ioDispatcher: CoroutineDispatcher,
        @ApplicationScope scope: CoroutineScope,
        userPreferencesSerializer: UserPreferencesSerializer,
    ) : DataStore<UserData> =
        DataStoreFactory.create(
            serializer = userPreferencesSerializer,
            scope = CoroutineScope(scope.coroutineContext + ioDispatcher)
        ) {
            context.dataStoreFile("user_preferences.json")
        }
}
bcorso commented 12 months ago

I assume it's Dagger's fault because the DataStore and SharedPreferences write the data correctly, but the DataStore can't read it fast enough. DataStore is a relatively new library, so maybe this isn't handled correctly.

Can you provide more details about why you think this is an issue in the Dagger library? The Dagger library itself doesn't know anything about DataStore or SharedPreferences, so with the current information it's not clear how it's related. Fwiw, this sounds more like a StackOverflow kind of question.