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()
}
Related
Hilt network module is setup like below:
#Module
#InstallIn(SingletonComponent::class)
class NetworkModule {
private val TIMEOUT_UNIT = TimeUnit.MILLISECONDS
private val TIMEOUT_MILLIS: Long = 10000
#Provides
fun provideApi(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
#Singleton
#Provides
fun provideRetrofit(okHttpClient: OkHttpClient, converter: Converter.Factory): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.client(okHttpClient)
.addConverterFactory(converter)
.build()
#Provides
fun provideOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient {
val builder = OkHttpClient.Builder()
builder.retryOnConnectionFailure(true)
builder.connectTimeout(3 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.readTimeout(6 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.writeTimeout(12 * TIMEOUT_MILLIS, TIMEOUT_UNIT)
builder.addInterceptor(loggingInterceptor)
if (condition == true) {
builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved("localhost", HTTP_PROXY_PORT)))
}
return builder.build()
}
#Provides
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
if (BuildConfig::DEBUG.get()) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
#Provides
fun provideConverterFactory(): Converter.Factory {
return GsonConverterFactory.create()
}
#Provides
fun providesGson(): Gson {
return Gson()
}
}
I want to customize the retrofit instance at runtime with or without the proxy according to some condition. How do I do that?
Koin had unloadModule option for reinitializing modules, does Hilt has any similar feature or behaviour?
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 want to refresh my token using an intercepter but my interceptor needs an API service to make API calls. I am stuck in a dependency cycle.
Here is my ApplicationModule class:
#Module
#InstallIn(ApplicationComponent::class)
class ApplicationModule {
#Provides
fun providerBaseUrl() = AppConstants.BASE_URL
#Provides
#Singleton
fun provideOkHttpClient(authInterceptor: AuthInterceptor,
networkInterceptor: NetworkInterceptor) = if (BuildConfig.DEBUG) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(loggingInterceptor)
.addInterceptor(networkInterceptor)
// .addInterceptor(refreshTokenInterceptor) // I want to put my interceptor here
.connectTimeout(2, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.writeTimeout(2, TimeUnit.MINUTES)
.build()
} else OkHttpClient
.Builder()
.connectTimeout(2, TimeUnit.MINUTES)
.readTimeout(2, TimeUnit.MINUTES)
.writeTimeout(2, TimeUnit.MINUTES)
.addInterceptor(authInterceptor)
.addInterceptor(networkInterceptor)
.build()
#Provides
#Singleton
fun provideRetrofit(
okHttpClient: OkHttpClient,
BASE_URL: String
): Retrofit =
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create())
.baseUrl(BASE_URL)
.client(okHttpClient)
.build()
#Provides
#Singleton
fun provideApiService(retrofit: Retrofit): WebApi = retrofit.create(WebApi::class.java)
#Provides
#Singleton // this is my provider
fun provideRefreshTokenService(webApi: WebApi): RefreshTokenInterceptor {
return RefreshTokenInterceptor(webApi)
}
#Provides
fun provideAuthInterceptor(): AuthInterceptor {
return AuthInterceptor()
}
#Provides
fun provideNetWorkInterceptor(): NetworkInterceptor {
return NetworkInterceptor()
}
}
And this is my RefreshTokenInterceptor:
class RefreshTokenInterceptor #Inject constructor(webApi: WebApi) : Interceptor {
var api = webApi
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
val updatedToken = getUpdatedToken(webApi = api)
requestBuilder.header("Authorization", updatedToken)
return chain.proceed(requestBuilder.build())
}
private fun getUpdatedToken(webApi: WebApi): String {
GlobalScope.launch {
val authTokenResponse = webApi.refreshToken()
val newToken = "${authTokenResponse.body()!!.data.token}"
SharedPref.getInstance(AppController.applicationContext()).setUserToken(newToken)
}
return SharedPref.getInstance(AppController.applicationContext()).getUserToken
}
}
I was facing a similar issue and found a solution. You can wrap the api instance in your Token Interceptor with Lazy interface. This will solve your cyclic dependency issue.
Here is a code snippet.
class RefreshTokenInterceptor #Inject constructor(private val webApi: Lazy<WebApi>) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val requestBuilder = chain.request().newBuilder()
val updatedToken = getUpdatedToken(webApi = api.get())
requestBuilder.header("Authorization", updatedToken)
return chain.proceed(requestBuilder.build())
}
private fun getUpdatedToken(webApi: WebApi): String {
GlobalScope.launch {
val authTokenResponse = webApi.refreshToken()
val newToken = "${authTokenResponse.body()!!.data.token}"
SharedPref.getInstance(AppController.applicationContext()).setUserToken(newToken)
}
return SharedPref.getInstance(AppController.applicationContext()).getUserToken
}
}
Look at this example:
#Provides
#Singleton
fun provideDatabase(
#ApplicationContext context: Context,
#RoomCreateCallback callback: RoomDatabase.Callback
) = Room.databaseBuilder(context, TaskDatabase::class.java, Constants.TaskKeys.TASK_DATABASE)
.fallbackToDestructiveMigration()
.addCallback(callback)
.build()
#Provides
fun provideTaskDao(taskDatabase: TaskDatabase) = taskDatabase.taskDao()
#Provides
#Singleton
#RoomCreateCallback
fun provideDatabaseCreateCallback(
taskDatabase: Provider<TaskDatabase>,
#ApplicationScope applicationScope: CoroutineScope
) = object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
val dao = taskDatabase.get().taskDao()
applicationScope.launch {
dao.insert(Task("First task"))
dao.insert(Task("Second task"))
dao.insert(Task("Third task", important = true))
dao.insert(Task("Fourth task", completed = true))
dao.insert(Task("Fifth task"))
dao.insert(Task("Sixth task", completed = true))
dao.insert(Task("Seventh task"))
dao.insert(Task("Eighth task"))
}
}
}
I need RoomDatabase.Callback when I create RoomDatabase, but I also need RoomDatabase when executing this callback. In provideDatabaseCreateCallback() function I wrapped RoomDatabase inside Provider<>.
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.