Dynamic urls with Koin with Retrofit also call the old url - android

I'm working on a solution that needs to make recurring calls to an api every 10 seconds. However, I need to dynamically change the URL pointing to another service. That is, the new loop that will start will make the call to this new url base. I am using Koin as a DI. Here is an example of my code:
This is my dataModule koin
single<Retrofit>() {
Retrofit.Builder()
.client(httpClient)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl(OLD_BASE)
.build()
}
single<ApiService>() {
get<Retrofit>().create(ApiService::class.java)
}
{ single<OkHttpClient>(named(WITH_AUTH)) {
OkHttpClient.Builder()
.callTimeout(30, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(get<HttpLoggingInterceptor>(named(DATA_INTERCEPTOR)))
.addInterceptor(get<AuthInterceptor>(named(AUTH_INTERCEPTOR)))
.authenticator(get<AccessTokenAuthenticator>(named(AUTH_AUTHENTICATOR)))
.build()
}
single(named(DATA_INTERCEPTOR)) {
HttpLoggingInterceptor().apply {
level =
if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.BASIC
}
}
}
single<AuthInterceptor>(named(AUTH_INTERCEPTOR)) {
AuthInterceptor(
get(), get()
)
}
And this is my interceptor :
class AuthInterceptor(
private val tokenRepository: TokenRepository,
private val envRepository: EnvRepository
) : Interceptor {
#Volatile
private var host: HttpUrl? = null
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val token = tokenRepository.getToken(TokenRepository.AUTH_TOKEN).blockingGet()
//val authenticationRequest = request(originalRequest, token)
host = envRepository.getEnvBaseUrl().toHttpUrlOrNull()
host?.let {
val newUrl = chain.request().url.newBuilder()
.scheme(it.scheme)
.host(it.toUrl().toURI().host)
.port(it.port)
.build()
request = chain.request().newBuilder()
.url(newUrl)
.build()
}
val authRequest = request(request, token) ?: request
return chain.proceed(authRequest)
}
private fun request(originalRequest: Request?, token: String?): Request? {
return if (!token.isNullOrEmpty()) {
originalRequest?.newBuilder()?.addHeader("Authorization", "Bearer $token")?.build()
} else {
originalRequest
}
}
}
The problem is that my interceptor works well, but each time before calling the new URL it also calls the old one. And I have no idea how to prevent it from calling the old URL in the loop. SO I have something like this in my debuger htts:
call old url
call olrd url
call new url
call new url
call old url
call old url
call new url
call new url
I hope I have been clear
Thanks,

Related

Make retrofit fetch new data from server only if localy cached data is older than 5 minutes

I have to make my retrofit client fetch new data from the server only if the locally cached data is older than 5 minutes or if it doesn't exist
private fun initRetrofit(){
val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(NewsService::class.java)
val call = service.getCurrentNews(
"bbc-news",
"top",
"75702474c08c4c0c96c4081147233679"
)
call.enqueue(object : Callback<NewsResponse> {
override fun onResponse(call: Call<NewsResponse>, response: Response<NewsResponse>) {
if (response.isSuccessful){
val body = response.body()
addDataSet(body!!.articles)
}
}
override fun onFailure(call: Call<NewsResponse>, t: Throwable) {
val alertDialogBuilder = AlertDialog.Builder(this#MainActivity)
alertDialogBuilder.setTitle("Greška")
alertDialogBuilder.setMessage("Ups, došlo je do pogreške.")
alertDialogBuilder.setPositiveButton("U REDU"){ _, _ -> }
alertDialogBuilder.setCancelable(false)
alertDialogBuilder.show()
}
} )
}
It is shown above how I am currently using a retrofit. I've used okhttpclient and interceptors before, but I'm not sure how exactly I should do it.
I made up with this now, but it's not working as it shoud.
private fun retrofit(okHttpClient: OkHttpClient) = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
private fun okHttp(cache: Cache): OkHttpClient {
return OkHttpClient.Builder()
.cache(cache)
.addNetworkInterceptor(CacheInterceptor())
.build()
}
private fun httpCache(application: Application): Cache {
return Cache(application.applicationContext.cacheDir, CACHE_SIZE)
}
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val request = chain.request()
val originalResponse = chain.proceed(request)
val shouldUseCache = request.header(CACHE_CONTROL_HEADER) != CACHE_CONTROL_NO_CACHE
if(!shouldUseCache) return originalResponse
val cacheControl = CacheControl.Builder()
.maxAge(5, TimeUnit.MINUTES)
.build()
return originalResponse.newBuilder()
.header(CACHE_CONTROL_HEADER, cacheControl.toString())
.build()
}
}
A with all this, I just build retrofit with:
val retrofit = retrofit(okHttp(httpCache(application )))
Sometimes it works fine, sometimes gets data but still call onFailure(), it seems that call was enqueued twice, and sometimes just throw onFailure(). I'm am not sure if he is using local cache or is he sending requests every time.
You need cache interceptor like this:
public class CacheInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
CacheControl cacheControl = new CacheControl.Builder()
.maxAge(5, TimeUnit.MINUTES) // 5 minutes cache
.build();
return response.newBuilder()
.removeHeader("Pragma")
.removeHeader("Cache-Control")
.header("Cache-Control", cacheControl.toString())
.build();
}
}
Add this interceptor with Cache to your OkHttpClient like this:
File httpCacheDirectory = new File(applicationContext.getCacheDir(), "http-cache");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new CacheInterceptor())
.cache(cache)
.build();
In your business logic, you will want to check your local cache for the existence of the data or if there is data if it has been 5 minutes since it has been updated. If the criteria is met to make the request with retrofit; then the request should be made, the cache updated with the timestamp the data was saved in the cache.
If the data is in the cache and less than 5 minutes, then the cached data is returned.
This is a great read to get you started https://developer.android.com/jetpack/guide#overview

Retrofit 2 Authenticator and Interceptor doesn't get called

i'm trying to send an authorization to the server in the headers of any request i tried at first using the Interceptor and then when i was searching and i found the authenticator and i gave it a try but it doesn't get called and i still get 401 in the responses.
this is my code :
public static ElasticApiRetrofitServiceClient getElasticApiRetrofitServiceClient() {
if (elasticApiRetrofitServiceClient == null) {
OkHttpClient client = new OkHttpClient();
client.newBuilder()
.connectTimeout(Const.TIMEOUT, TimeUnit.SECONDS)
.readTimeout(Const.TIMEOUT, TimeUnit.SECONDS)
.authenticator(new MyInterceptor())
.addInterceptor(new MyInterceptor()).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ELASTIC_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
elasticApiRetrofitServiceClient = retrofit.create(ElasticApiRetrofitServiceClient.class);
}
return elasticApiRetrofitServiceClient;
}
and this is my Interceptor/Authenticator
class MyInterceptor : Interceptor, Authenticator {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request();
val newRequest = originalRequest . newBuilder ()
.header("Authorization", "SOME_TOKEN")
.build();
return chain.proceed(newRequest);
}
#Throws(IOException::class)
override fun authenticate (route: Route?, response: Response?): Request? {
var requestAvailable: Request? = null
try {
requestAvailable = response?.request()?.newBuilder()
?.addHeader("Authorization", "SOME_TOKEN")
?.build()
return requestAvailable
} catch (ex: Exception) { }
return requestAvailable
}
}
the problem is i debugged multiple times and it the interceptor/authenticator never get called.
You're using newBuilder method on OkHttpClient which will create a new builder and you're not using that builder but you instead are using the old builder.
public static ElasticApiRetrofitServiceClient getElasticApiRetrofitServiceClient() {
if (elasticApiRetrofitServiceClient == null) {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(Const.TIMEOUT, TimeUnit.SECONDS)
.readTimeout(Const.TIMEOUT, TimeUnit.SECONDS)
.authenticator(new MyInterceptor())
.addInterceptor(new MyInterceptor()).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(ELASTIC_BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
elasticApiRetrofitServiceClient = retrofit.create(ElasticApiRetrofitServiceClient.class);
}
return elasticApiRetrofitServiceClient;
}

Retrofit2: Add post parameter into interceptor

Inside my Android kotlin app i'm calling some apis by using retrofit2 like
#FormUrlEncoded
#POST("something/some")
fun callMyApi(
#Field("myField") myField: String
): Deferred<MyResponseClass>
Now i need to add some common post params to all my api request (and keep the specific ones for each call, in this case i need to keep "myField"), so i'm using an interceptor:
val requestInterceptor = Interceptor { chain ->
val newRequest = chain.request()
.newBuilder()
.post(
FormBody.Builder()
.add("common1Key", "common1")
.add("common2Key", "common2")
.add("common3Key", "common3")
.build()
)
.build()
return#Interceptor chain.proceed(newRequest)
}
But this implementation fails because the interceptor seems to overwrite myField.
How can i fix it?
We can create Interceptor by using two or more common query parameter.
val requestInterceptor = Interceptor { chain ->
val url = chain.request()
.url()
.newBuilder()
.addQueryParameter("common1key", "common1")
.addQueryParameter("common2key", "common2")
.addQueryParameter("common3key", "common3")
.build()
val request = chain.request()
.newBuilder()
.url(url)
.build()
return#Interceptor chain.proceed(request)
}
I have added Interceptor for post form body.
interface PostWebApiService {
#POST("posts")
#FormUrlEncoded
fun savePost(
#Field("title") title: String
): Deferred<Post>
companion object {
operator fun invoke(): PostWebApiService {
val requestInterceptor = Interceptor { chain ->
var request = chain.request()
val requestBuilder = request.newBuilder()
val formBody = FormBody.Builder()
.add("body", "Body")
.add("userId", "12")
.build()
var postBodyString = bodyToString(request.body())
val concat = if (postBodyString.isNotEmpty()) "&" else ""
postBodyString = postBodyString + concat + bodyToString(formBody)
request = requestBuilder.post(
RequestBody.create(
MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"),
postBodyString
)
)
.build()
return#Interceptor chain.proceed(request)
}
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(requestInterceptor)
.build()
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("http://jsonplaceholder.typicode.com/")
.addCallAdapterFactory(CoroutineCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(PostWebApiService::class.java)
}
fun bodyToString(request: RequestBody?): String {
try {
var buffer = Buffer()
request?.writeTo(buffer)
return buffer.readUtf8()
} catch (e: IOException) {
return "error"
}
}
}
}

How to fetch data from network and not from http cache?

Here is my code , I have added CacheHeaderInterceptor but one of the requests
for some cases needs to do force call from network instead of retrieving cache response
but as I have added CacheHeaderInterceptor it never called after first call.
but I need to have check and based on that check fetch from network or retrieve cache response
#Singleton
#Provides
fun httpClient(context: Context, #Named(“UserPreferences”) preferences: SharedPreferences): OkHttpClient {
val appCacheDir = context.cacheDir
val httpCacheDir = File(appCacheDir, HTTP_CACHE_DIRNAME)
if (!httpCacheDir.exists()) {
httpCacheDir.mkdirs()
}
val builder = OkHttpClient.Builder()
val authInterceptor = LegacyAuthInterceptor(preferences, userAuthRelay)
builder.addNetworkInterceptor(authInterceptor)
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(StethoInterceptor())
}
builder.addNetworkInterceptor(CacheHeaderInterceptor(isStoreUpdatedRelay))
return builder
.cache(Cache(httpCacheDir, MAX_HTTP_CACHE_SIZE))
.connectTimeout(30, SECONDS)
.writeTimeout(30, SECONDS)
.readTimeout(30, SECONDS)
.retryOnConnectionFailure(true)
.build()
}
class CacheHeaderInterceptor(private val isUpdatedRelay: BehaviorRelay<Boolean>) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
chain.request().headers().get(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY)
?: return chain.proceed(chain.request())
val maxAge = chain.request().headers().values(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY).firstOrNull()?.toLongOrNull()
val modifiedRequest = chain.request().newBuilder().removeHeader(CustomCacheHeader.CUSTOM_CACHE_HEADER_KEY).build()
val originalResponse = chain.proceed(modifiedRequest)
return when {
isUpdatedRelay.value -> {
val modifiedResponse = originalResponse.newBuilder()
.addHeader("Cache-Control", "no-cache")
.build()
isStoreUpdatedRelay.accept(false)
modifiedResponse
}
maxAge != null -> {
// Add Cache-Control to the response.
val modifiedResponse = originalResponse.newBuilder()
.removeHeader("Cache-Control")
.removeHeader("Pragma")
.addHeader("Cache-Control", "max-age=$maxAge")
.build()
modifiedResponse
}
else -> // Missing max-age, proceed with original response.
originalResponse
}
}
}
I found the solution for my question
adding addInterceptor()
There is addNetworkInterceptor() and addInterceptor().

OkHttp token interceptor retries forever

I'm making an OkHttp interceptor so it retry to sign in when any end point returns a 401 error, but the interceptor is looping forever.
I've also tried to add a counter, but the counter resets itself every time.
Here's my code:
object Service {
private fun getOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(getLoggingInterceptor())
.addInterceptor(NetworkInterceptor())
.build()
}
private fun getRetrofit(): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(getOkHttpClient())
.baseUrl(getBaseUrl())
.build()
}
}
class NetworkInterceptor: Interceptor {
var counter = 0
override fun intercept(chain: Interceptor.Chain): Response? {
val originalResponse = chain.proceed(chain.request())
if (!originalResponse.isSuccessful && originalResponse.code() == 401) {
Log.e("NetworkInterceptor", "Network error 401. Counter = $counter")
counter++
val refreshedToken = refreshToken()
Log.e("NetworkInterceptor", "refreshedToken = $refreshedToken")
}
return originalResponse
}
private fun refreshToken(): String {
val context = MyApp.appContext
val preferencesUtil = SharePreferencesUtils(context)
val username = preferencesUtil.getUsername()
val password = preferencesUtil.getPassword()
val login = AuthService().loginSync(username, password).execute()
return login.body()?.access_token!!
}
}
I have tried this with an Auth call being an RxJava Single, and a regular synchronous Call<>
In each case, the call happens forever, the 401 gets returned forever, and the counter always stays at 0.
Any ideas on what I'm missing or doing wrong?
Thank you very much!
Add as a NetworkInterceptor using
addNetworkInterceptor(NetworkInterceptor)
refer to this link for further clarification: https://github.com/square/okhttp/wiki/Interceptors

Categories

Resources