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.
Related
I have weather api to parse data for 10 days
All it's good , but I have problem with retrofit now , I have app crashes , my URL(with API) have / in the end.
But still don't working.
Also I have dependency injection for retrofit.
Goal is to get data from api.
Hope , that you will help me to resolve this problem.
package const
const val BASE_URL = "https://api.weatherapi.com/v1/forecast" +
".json?key=a9f9d57b6e064f16b28141346231001&q=London&days=10&aqi=no&alerts=no/" // error here
const val apikey = "a9f9d57b6e064f16b28141346231001"
const val WeatherDays = 10
interface WeatherServiceAPI {
#GET("forecast.json")
suspend fun Weatherday(
#Query("days") days : Int
) : WeatherResponse
#GET("forecast.json")
suspend fun searchcitybycoord(#Query("lat")lat:String) : List<WeatherLocationDTO>
#GET("forecast.json")
suspend fun searchingbyCity(#Query("q") name: String) : List<WeatherLocationDTO>
companion object{
operator fun invoke(
connectivityInterceptor: Interceptor
):WeatherServiceAPI{
val requestInterceptor = Interceptor{
chain -> val url = chain.request()
.url
.newBuilder()
.addQueryParameter("key", apikey)
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return#Interceptor chain.proceed(request)
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.addInterceptor(connectivityInterceptor)
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://api.weatherapi.com/v1/") // error line
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WeatherServiceAPI::class.java)
}
}
}
#Provides
#Singleton
fun providerepository(api:WeatherServiceAPI):ForecastRepository{
return ForecastRepositoryImpl(api)
}
#Provides
#Singleton
fun provideWeatherApiService(retrofit: Retrofit) =
retrofit.create(WeatherServiceAPI::class.java)
#Provides
#Singleton
fun provideRetrofit ( okHttpClient: OkHttpClient) = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
#Provides
#Singleton
fun provideOkhttpClient(interceptor: Interceptor): OkHttpClient {
val httpBuilder = OkHttpClient.Builder().addInterceptor(interceptor)
return httpBuilder.build()
}
#Provides
#Singleton
fun provideinterceptor():Interceptor{
return Interceptor {
val request =it.request().newBuilder()
val actualRequest = request.build()
it.proceed(actualRequest)
}
}
Given what else you have in the code, your base URL should be https://api.weatherapi.com/v1/.
forecast.json comes from the #GET annotations, and the query parameters will need to come from #Query-annotated parameters to your Retrofit interface functions.
I have a network module class that provides an ApiService instance.
There is an Authenticator class which refreshes access token when expired.
The authenticator requires ApiService instance for making API calls.
This causes a cyclic dependency. How to avoid this?
Now I'm creating a new ApiService inside TokenExpiryAuthenticator class to make API calls, to break the cyclic dependency.
How to properly inject ApiService into TokenExpiryAuthenticator without causing cyclic dependency?
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
#Provides
#Singleton
#Named("Other")
fun provideRetrofitWithoutInterceptor(#Named("Other") client: OkHttpClient, gson: Gson): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
#Provides
#Singleton
fun provideRetrofit(client: OkHttpClient, gson: Gson): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
}
#Provides
#Singleton
fun providesOkHttpClient(httpLoggingInterceptor: HttpLoggingInterceptor, supportInterceptor: SupportInterceptor, tokenExpiryAuthenticator: TokenExpiryAuthenticator): OkHttpClient {
return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.callTimeout(1, TimeUnit.MINUTES)
.addInterceptor(httpLoggingInterceptor)
.addInterceptor(supportInterceptor)
.authenticator(tokenExpiryAuthenticator)
.build()
}
#Named("Other")
#Provides
#Singleton
fun providesOkHttpClientWithoutInterceptor(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient {
return OkHttpClient.Builder().writeTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.callTimeout(1, TimeUnit.MINUTES)
.addInterceptor(httpLoggingInterceptor)
.build()
}
#Provides
#Singleton
fun providesHttpLoggingInterceptor(): HttpLoggingInterceptor {
return if (BuildConfig.DEBUG)
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
else
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.NONE
}
}
#Provides
#Singleton
fun providesGson(): Gson {
return GsonBuilder().create()
}
#Provides
#Singleton
fun providesRestApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
#Provides
#Singleton
#Named("Other")
fun providesRestApiServiceWithoutInterceptor(#Named("Other") retrofit: Retrofit): ApiService{
return retrofit.create(ApiService::class.java)
}
}
You could split your ApiService and create a new AuthenticationApi which only includes the endpoints to authenticate or refresh your access tokens. This AuthenticationApi is created with an OkHttp instance without your Authenticator.
That way your Authenticator only needs reference to this slim Retrofit api.
This also guarantees that you don't get HTTP 401 authentication loops in case of wrong credentials when using your authentication endpoints.
Simplest possible solution is to inject the dependency lazily.
class TokenExpiryAuthenticator #Inject constructor(
private val api: Lazy<ApiService>,
private val persistence: TokenPersistenceRepository
) : Authenticator {
override fun authenticate(route: Route?, response: Response): Request? {
// If HTTP 401 error was raised while trying to refresh token it means
// the token is invalid (or expired). Sign out user and do not
// proceed with further requests.
if (response.request.url.encodedPath == REFRESH_TOKEN_PATH) {
// Perform any cleanup needed.
// Return null so no further requests will be performed.
return null
}
// Request failed for old token. It's about time to refresh it.
if (response.request.header(AUTH_HEADER) != null) {
// Refresh access token using your lazily injected service.
persistence
.retrieveRefreshToken()
.flatMap(api.get()::refreshToken)
.flatMapCompletable {
Completable.concatArray(
persistence.saveAccessToken(it.accessToken),
persistence.saveRefreshToken(it.refreshToken),
)
}
.blockingAwait()
// You can use blocking await as the [authenticate] method is
// run on I/O thread anyway.
}
// Load recently refreshed access token.
val token = (...)
// Format authorization header value.
val header = (...)
// Modify and proceed with a request with refreshed access token.
return response.request.newBuilder()
.removeHeader(AUTH_HEADER)
.addHeader(AUTH_HEADER, header)
.build()
}
}
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()
}
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.
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.