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.
Related
I am using retrofit for api calls in which I want to pass dynamic URL.
My Object is as below I want to pass URL_BASE as dynamic value from preferencesDataStore
#Module
#InstallIn(SingletonComponent::class)
object CoreModule {
private const val URL_BASE = ""
#Singleton
#Provides
fun provideRetrofitInstance(userPreferences: UserPreferences): ApiService {
return Retrofit.Builder()
.baseUrl(URL_BASE)
.client(OkHttpClient.Builder().also { client ->
if (BuildConfig.DEBUG) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BODY
client.addInterceptor(logger)
}
}.build())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
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()
}
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 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.
Help solve the problem!(((I have 3 modules for DI. There is a retrofit object in natworkModule, all viewModels in viewModelModule, and all requests to the server in respositoryModule. I did everything according to the documentation, but I cannot find this error in Google. Thank you in advance!!! Sorry for my english!)
class App : Application(){
override fun onCreate() {
super.onCreate()
startKoin(this, listOf(natworkModule, viewModelModule,repositoryModule))
}
}
var natworkModule = module {
single { createOkHttpClient() }
single { createApiService<ApiService>(get () ,getProperty(SERVER_URL))
}
}
const val SERVER_URL = "https://api.github.com/"
fun createOkHttpClient() : OkHttpClient{
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BASIC
return OkHttpClient.Builder()
.connectTimeout(60L, TimeUnit.SECONDS)
.readTimeout(60L, TimeUnit.SECONDS)
.addInterceptor(httpLoggingInterceptor).build()
}
inline fun <reified T> createApiService(okHttpClient: OkHttpClient, url: String): T {
val retrofit = Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory()).build()
return retrofit.create(T::class.java)
}
var repositoryModule = module {
factory<TestRepository> {
TestRepositoryImpl(get())
}
}
var viewModelModule = module {
viewModel {
TestViewModel(get())
}
}
Problem was in this constant value ->
SERVER_URL = "https://api.github.com/"
Koin could not find it. Therefore there was an exception. Thanks to all!!!