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"
}
}
}
}
Related
As I researched and found that , .client(OkHttptvariableName) is used inside builder inorder to print the api response into logcat.
But in my case, I cannot call the .client inbuild method with request.newBuilder.
Following is my code snippets.
Please help me.
val httpLoggingInterceptor = HttpLoggingInterceptor()
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val okHttpClient: OkHttpClient = OkHttpClient.Builder().addInterceptor(httpLoggingInterceptor).build()
when {
UserInfo.loginStatus -> {
request = request.newBuilder()
.header("Authorization", UserInfo.token)
.build()
}
else -> {
request = request.newBuilder()
.build()
}
}
See this example:
val httpClient = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
httpClient.addInterceptor(logging)
}
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient.build())
.build()
}
for more info see: https://futurestud.io/tutorials/retrofit-2-enable-logging-for-development-builds-only
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,
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;
}
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().
I am using retrofit2 in kotlin, and I need to get the content that is a json and this encrypted, I know that to convert json just use the JacksonConverterFactory (until this part was working well) but an encryption was added before that and I do not know how To handle this, do I need to create a converter of my own? Does anyone have a read to tell me?
My current call for retrofit
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()
And i already have my fucntion (working) to decrypt:
CryptAES.decrypt(value))
This can be done by creating an decrypt interceptor:
class DecryptInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response = chain
.run { proceed(request()) }
.let { response ->
return#let if (response.isSuccessful) {
val body = response.body()!!
val contentType = body.contentType()
val charset = contentType?.charset() ?: Charset.defaultCharset()
val buffer = body.source().apply { request(Long.MAX_VALUE) }.buffer()
val bodyContent = buffer.clone().readString(charset)
response.newBuilder()
.body(ResponseBody.create(contentType, bodyContent.let(::decryptBody)))
.build()
} else response
}
private fun decryptBody(content: String): String {
//decryption
return content
}
}
setup:
val httpClient = OkHttpClient().newBuilder()
httpClient.addInterceptor(DecryptInterceptor())
val retrofit = Retrofit.Builder()
.baseUrl("http://100.1.1.100/")
.addConverterFactory(JacksonConverterFactory.create())
.client(httpClient.build())
.build()