Search code examples
androidkotlindependency-injectiondagger-2dagger

JWT Token Wont Update when using dagger 2?


I have problem using dagger 2 for updating token in runtime.

So here is the scenario:

I have a screen to Change Password. when i succeed update password, the current jwt token would be invalid, and i need to store new token from update token response, i store that token in SharedPreferences. but the problem is when i store the token. it updated in sharedprefernces, but wont update value in DaggerGraph where i build Retrofit instance (Authorization header).

Below is my code :

AppComponent.kt

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

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance context: Context): AppComponent
    }

    fun inject(activity: SplashActivity)

    fun inject(activity: LoginActivity)

    fun inject(activity: MainActivity)

    fun inject(activity: ChangePasswordActivity)
}

AppModule.kt

@Module
class AppModule {

    @Singleton
    @Provides
    fun provideAuthInterceptor(sharedPreferencesSources: SharedPreferencesSources): Interceptor {
        return AuthInterceptor(sharedPreferencesSources.tokenApi())
    }

    @Singleton
    @Provides
    fun provideApiService(
        authInterceptor: Interceptor
    ): SharedProductClient {
        return Network.retrofitClient(authInterceptor = authInterceptor)
            .create(SharedProductClient::class.java)
    }

    @Singleton
    @Provides
    fun provideAppRepository(apiService: SharedProductClient): AppRepository {
        return AppRepositoryImpl(apiService)
    }

    @Singleton
    @Provides
    fun provideAppUseCase(appRepository: AppRepository): AppUseCase {
        return AppUseCase(appRepository)
    }
   
    @Singleton
    @Provides
    fun provideAppScheduler(): SchedulerProvider = AppSchedulerProvider()
}

StorageModule.kt

@Module
class StorageModule {

    @Singleton
    @Provides
    fun provideSharedPreferences(context: Context): SharedPreferences {
        return context.getSharedPreferences(SharedPrefName, Context.MODE_PRIVATE)
    }

    @Singleton
    @Provides
    fun provideSharedPreferencesSource(sharedPrefInstance: SharedPreferences): SharedPreferencesSources {
        return SharedPreferencesSourcesImpl(sharedPrefInstance)
    }

    companion object {
        const val SharedPrefName = "share_product_prefs"
    }
}

AuthInterceptor.kt

class AuthInterceptor constructor(
    private val token: String
) : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response = chain.run {
        proceed(
            request()
                .newBuilder()
                .addHeader("Accept", "application/json")
                .addHeader("Authorization", "Bearer $token")
                .build()
        )
    }
}

Any suggestion would be really help me. Thanks!


Solution

  • This is because you only pass a String instance of the token when creating the AuthInterceptor.

    You should provide a way (e.g. an interface) of dynamically obtaining the token from the SharedPreferences when needed.


    This is one way of doing this:

    1. Change the token:String to a function type in your AuthInterceptor constructor (and use it when needed):
    class AuthInterceptor constructor(
        private val tokenProvider: () -> String
    ) : Interceptor {
    
        override fun intercept(chain: Interceptor.Chain): Response = chain.run {
            proceed(
                request()
                    .newBuilder()
                    .addHeader("Accept", "application/json")
                    .addHeader("Authorization", "Bearer ${tokenProvider.invoke()}")
                    .build()
            )
        }
    }
    
    1. When creating your AuthInteceptor build your lambda to dynamically refer to SharedPreferences
    @Module
    class AppModule {
    
        @Singleton
        @Provides
        fun provideAuthInterceptor(sharedPreferencesSources: SharedPreferencesSources): Interceptor {
            return AuthInterceptor(){ sharedPreferencesSources.tokenApi() }
        }
    
        //...
    }
    

    This way the tokenProvider will be invoked (SharedPreferences will be accessed) every time you make an api call, instead of just a single time when creating the AuthInterceptor.