Shared Preference Always Get Old Value In Retrofit Interceptor - android

I am new in dagger hilt. So I just created a module that life as same as Application (SingletonComponent) like this.
#Module
#InstallIn(SingletonComponent::class)
object SharedPrefModule {
#Provides
fun provideSharedPref(#ApplicationContext context: Context) : SharedPrefs{
return SharedPrefs(context)
}
}
And then using the SharedPref in Network Module like this. (See the prefs parameter)
#Module
#InstallIn(SingletonComponent::class)
object NetworkModule {
#Singleton
#Provides
fun provideRetrofit(okHttp: OkHttpClient) : Retrofit {
return Retrofit.Builder().apply {
addConverterFactory(GsonConverterFactory.create())
addCallAdapterFactory(RxJava2CallAdapterFactory.create())
client(okHttp)
baseUrl(BuildConfig.API_BASE_URL)
}.build()
}
#Singleton
#Provides
fun provideOkHttp(pref: SharedPrefs) : OkHttpClient {
return OkHttpClient.Builder().apply {
connectTimeout(60, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(60, TimeUnit.SECONDS)
addInterceptor(RequestInterceptor(pref))
}.build()
}
}
Inside the RequestInterceptor is like this:
class RequestInterceptor(private val pref: SharedPrefs) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = pref.getToken()
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", token)
.build()
return chain.proceed(newRequest)
}
}
I start the app without login (it means the prefs.getToken()) will return empty. But the problem is, even i have logged in and the token successfully saved, the pref.getToken() still return empty. I think there is a problem with instance-ing the sharedPrefs, since it singleton.
But how do I refresh the shared preference instance, so the Interceptor will always get the updated value of shared pref?
If I want to get new value of the shared pref so the Interceptor can work, I need to close the app and then swipe/clear from task manager

I Fixed by adding this in NetworkModule
#Provides
fun provideRequestInterceptor(prefs: SharedPrefs) : RequestInterceptor {
return RequestInterceptor(prefs)
}
and the RequestInterceptor like this:
class RequestInterceptor constructor(private val pref: SharedPrefs) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = pref.getToken()
val newRequest = chain.request().newBuilder()
.addHeader("Authorization", token)
.build()
return chain.proceed(newRequest)
}
}
And then when providing theh okHttpClient, I change the function like this:
#Singleton
#Provides
fun provideOkHttp(requestInterceptor: RequestInterceptor) : OkHttpClient {
return OkHttpClient.Builder().apply {
connectTimeout(60, TimeUnit.SECONDS)
readTimeout(60, TimeUnit.SECONDS)
writeTimeout(60, TimeUnit.SECONDS)
addInterceptor(requestInterceptor)
}.build()
}

Related

How can i reinitialise the Singleton Component in Hilt dagger?

I have a retrofit module in my project, Before login i want to use retrofit without headers, But after login i want to use retrofit with headers using Hilt Dagger. How can i do this?
#Module
#InstallIn(SingletonComponent::class)
object RetrofitDi {
#Provides
fun getBasePath(): String {
return "http://abcd.com/"
}
#Provides
fun providesLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
}
#Provides
fun providesOkHttpClients(#ApplicationContext context: Context, sharedPreference: SharedPreference, httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
val okhttpClient = OkHttpClient.Builder()
okhttpClient.addInterceptor(httpLoggingInterceptor)
okhttpClient.callTimeout(60, TimeUnit.SECONDS)
okhttpClient.connectTimeout(60, TimeUnit.SECONDS)
okhttpClient.writeTimeout(60, TimeUnit.SECONDS)
okhttpClient.readTimeout(60, TimeUnit.SECONDS)
val token = sharedPreference.getStringData(SharedPreference.AUTH_KEY)
val user_id = sharedPreference.getStringData(SharedPreference.USER_ID)
if (BuildConfig.DEBUG) {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS)
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
okhttpClient.addInterceptor(interceptor)
okhttpClient.addInterceptor(Interceptor { chain ->
val response = chain.proceed(chain.request())
if (!response.isSuccessful) {
when (response.code) {
CommonUtils.ALREADY_LOGGED_IN -> {
sharedPreference.setBoolean(SharedPreference.IS_LOGGED_IN, false)
sharedPreference.clear()
context.getCacheDir().delete()
val intent = Intent(context, LoginActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
}
response
})
}
okhttpClient.addInterceptor(Interceptor { chain: Interceptor.Chain ->
val builder1 = chain.request().newBuilder()
var request: Request? = null
if (!token.isEmpty()) {
builder1.addHeader("auth_key", "" + token)
}
if (!user_id.isEmpty()) {
builder1.addHeader("user_id", "" + user_id)
}
request = builder1.build()
chain.proceed(request)
})
return okhttpClient.build()
}
#Provides
fun providesGSONConvertorFactory(): Converter.Factory {
return GsonConverterFactory.create()
}
#Provides
fun providesRetrofit(baseUrl: String, convertor: Converter.Factory, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl).addConverterFactory(convertor).client(okHttpClient).build()
}
#Provides
fun providesApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
Singleton instance created once in hilt-dagger but token and user_id will be available after login. After login i will need new okhttpclient. I did it without DI. But don't know how to deal with hilt-dagger.
Better way to do this is to have your API service methods annotated with #Headers("Token-required") for the APIs which requires token. Then in your interceptor method check for this header as:
if (request.header("Token-required") != null) {
request = request.newBuilder()
.addHeader("token", "your token value")
.build()
}

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!
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:
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()
)
}
}
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.

Re-initialization an object provided by Dagger 2

I have an interface called LoginService which is used with Retrofit. There was no accessToken before the user login. When the user has logged in, the LoginService Instance should be updated with acessToken so that the user could log out from the app. The problem is that LoginService class is not updated even though it is not declared as #Singleton. If the user has closed the app and reopen it again, the LoginService got updated and therefore he could log out of the app. How can I reinitialize the LoginService instance as soon as the accessToken has been updated?
The key here is not the reinitialize the instance but to create a separate service whenever the user logs in.
you need to have two different interfaces one for API's pre-login and other for post login.
So one would be Authapi.kt and the other would be Api.kt
So first we need to create OkHttpBuilder for each service,
#Provides
#Singleton
#Named(NetModule.NO_AUTH_CLIENT)
fun provideNoAuthOkHttpClient(
okHttpClientBuilder: OkHttpClient.Builder
): OkHttpClient {
return okHttpClientBuilder.build()
}
#Provides
#Singleton
#Named(NetModule.AUTH_CLIENT)
fun provideAuthOkHttpClient(
okHttpClientBuilder: OkHttpClient.Builder,
tokenInterceptor: NetworkInterceptor
): OkHttpClient {
return okHttpClientBuilder.addInterceptor(tokenInterceptor).build()
}
Network Interceptor is the class where you update your access token for API.
NetworkInterceptor.kt
class NetworkInterceptor #Inject constructor(
val context: Context,
serverBaseUrl: String,
val moshi: Moshi,
val preferences: PreferenceUtility
) : Interceptor {
private fun Request.Builder.setDefaultHeaders(): Request.Builder {
addHeader("App_version_code", BuildConfig.VERSION_CODE.toString())
addHeader("App_version_name", BuildConfig.VERSION_NAME)
addHeader("Mobile_model", Build.MODEL.toString())
addHeader("OS_version", Build.VERSION.SDK_INT.toString())
addHeader("OS_version_release", Build.VERSION.RELEASE.toString())
if (preferences.customerId != -1L)
addHeader("Client_id", preferences.customerId.toString())
return this
}
private fun makeRequestWithAuthTokenAndTimeStamp(request: Request) = request.newBuilder()
.setDefaultHeaders()
.apply {
val oldHeader = request.header("Authorization")
if (oldHeader.isNullOrBlank()) {
val token = if (preferences.authToken.isBlank() || !preferences.customerAuthenticated) BuildConfig.ANONYMOUS_TOKEN else preferences.authToken
addHeader("Authorization", "Bearer $token")
}
}
.url(request.url())
.method(request.method(), request.body())
.build()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// ADD THE TOKEN'S OVER HERE
var response =
chain.proceed(makeRequestWithAuthTokenAndTimeStamp(request))
//You can also add refersh logic here.
return response
}
}
And you NetworkModule.kt will be
#Provides
#Singleton
#Named(NetModule.NO_AUTH_CLIENT)
fun provideNoAuthInterceptorRetrofit(
moshi: Moshi,
#Named(NetModule.NO_AUTH_CLIENT) okHttpClient: OkHttpClient,
debugPreferenceUtility: DebugPreferenceUtility
): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(debugPreferenceUtility.serverBaseUrl)
.client(okHttpClient)
.build()
}
#Provides
#Singleton
#Named(NetModule.AUTH_CLIENT)
fun provideAuthInterceptorRetrofit(
moshi: Moshi,
#Named(NetModule.AUTH_CLIENT) okHttpClient: OkHttpClient,
debugPreferenceUtility: DebugPreferenceUtility
): Retrofit {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(debugPreferenceUtility.serverBaseUrl)
.client(okHttpClient)
.build()
}
#Provides
#Singleton
fun provideApi(#Named(AUTH_CLIENT) retrofit: Retrofit) = retrofit.create(Api::class.java)
#Provides
#Singleton
fun provideAuthApi(#Named(NO_AUTH_CLIENT) retrofit: Retrofit) = retrofit.create(AuthApi::class.java)
So the API's that will be used pre-login will be placed inside AuthApi.kt
like send OTP etc and all the rest API will be placed inside Api.kt and Network Interceptor will take care of adding token.

SharedPreferences instance inside NetworkModule

I am facing with SharedPreferences problem. I would like to know how I can call SharedPreferences inside Retrofit. I mean, I have this following file :
#Module
class NetworkModule {
#Provides
internal fun provideGson(): Gson {
return GsonBuilder().create()
}
#Provides
internal fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
#Provides
internal fun provideRetrofit(gson: Gson, okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
}
}
And in my Presenter, I have this following code:
override fun getSavedToken() {
mToken = mSharedPreferences.getString(TOKEN, TOKEN_UNAVAILABLE)
}
...
inner class GetAccessTokenSubscriber : ResourceObserver<AccessTokenBean>() {
override fun onNext(#NonNull accessToken: AccessTokenBean) {
mSharedPreferences.edit().putString(TOKEN, accessToken.token).apply()
getInformation()
}
override fun onError(#NonNull e: Throwable) {
mView?.displayError()
}
override fun onComplete() {
// Nothing to do
}
}
Currently, to set the token I put the Bearer $token in my repository / service
// Repository
val newToken = "Bearer $token"
return mService.getInfos(newToken)
// Service
fun getInfos(#Header("Authorization") token: String
I would like to know how I can put the Bearer + token inside my NetworkModule file?
Thank you for your time.
If you want to place the value on the interceptor, just call your SharedPreferences instance on the interceptor provider:
#Provides
internal fun provideOkHttpClient(sharedPrefs: SharedPrefs): OkHttpClient {
return OkHttpClient.Builder().addInterceptor { chain ->
val original = chain.request()
val requestBuilder = original.newBuilder().addHeader("Accept", "application/json")
val request = requestBuilder.method(original.method(), original.body()).build()
chain.proceed(request)
}.build()
}
Now dagger will look for that, but it won't find it, giving you an error. In that case, if you network module is also a singleton too just add a includes = [PreferencesModule::class], if not, you may need to set the current component dependent on the Singleton where you preferences module is located.

Dagger2 generating multiple instances of retrofit interceptor

Dagger 2 is generating multiple instances of retrofit interceptor despite marking it as singleton in dagger module. Now the problem is that AuthorizationInterceptor constructor gets called twice which I don't understand why and because of that the headers that I set after getting result from login API get sets to a different instance of Interceptor and while making call to some other API which requires authorizationToken the token is unset.
Here is my ApiModule
#Module
open class ApiModule {
#Provides
#Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
#Provides
#Singleton
fun provideHeaderInterceptor(): Interceptor {
return AuthorizationInterceptor()
}
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor, headerInterceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
#Provides
#Singleton
fun provideMoshi(): Moshi {
return Moshi.Builder()
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, moshi: Moshi, apiConfig: ApiConfig): Retrofit {
return Retrofit.Builder()
.baseUrl(apiConfig.baseUrl)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
#Provides
#Singleton
fun provideFrappApi(retrofit: Retrofit): FrappApi {
return retrofit.create(FrappApi::class.java)
}
Here is my AuthorizationInterceptor class
#Singleton
class AuthorizationInterceptor #Inject constructor() : Interceptor {
override fun intercept(chain: Interceptor.Chain?): Response {
val request = chain?.request()
val requestBuilder = request?.newBuilder()
if (request?.header("No-Authorization") == null && authorization.isNotEmpty()) {
requestBuilder?.addHeader("Authorization", authorization)
}
return chain?.proceed(requestBuilder!!.build())!!
}
private var authorization: String = ""
fun setSessionToken(sessionToken: String) {
this.authorization = sessionToken
}
}
You dont need to make a provide method if you do a constructor injection.
Remove the provideHeaderInterceptor method, then update the provideHttpClient method like below,
#Provides
#Singleton
fun provideHttpClient(interceptor: HttpLoggingInterceptor,
headerInterceptor: AuthorizationInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(headerInterceptor)
.build()
}
Or if you dont like the solution above, you can remove the #Singleton and #Inject in your AuthorizationInterceptor class.

Categories

Resources