I'm using retrofit in my app and everything is ok except one thing. After disabling internet connection, enabling it back and making request to api (okhttp logger says that GET request was handled), response comes in a few minutes. And when i make next request, response comes immediately.
Also, for the slow response, okhttp logger says that it has been handled in ~50 milliseconds, but prints that log after a few minutes too.
In project i'm using koin, but i doubt it is the root of the problem. Anyway i defined retrofit, okhttpclient and interceptors as single.
And maybe it'll help, if i set read and write timeouts to, for example, 5 seconds in okhttp client i'll get timeout exception.
So what's happening and how to handle with that problem?
Retrofit:
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client) // my OkHttpClient instance
.build()
OkHttpClient:
OkHttpClient.Builder()
.addInterceptor(networkConnectionInterceptor)
.addInterceptor(loggingInterceptor)
.writeTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES)
.build()
NetworkConnectionInterceptor and NoConnectivityException classes:
class NetworkConnectionInterceptor(private val context: Context) : Interceptor {
#Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
if (!isConnected) {
throw NoConnectivityException()
}
val builder: Request.Builder = chain.request().newBuilder()
return chain.proceed(builder.build())
}
val isConnected: Boolean
get() {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val netInfo = connectivityManager.activeNetworkInfo
return netInfo != null && netInfo.isConnected
}
}
class NoConnectivityException : IOException() {
override val message: String
get() = "No Internet Connection"
}
Related
I'm trying to create an Interceptor in the event that the API I'm using goes down, which has happened when I tried to make an API call on Postman only for it to return a 504 error.
This is the OkHttpClient I have for now. I set it to 5 seconds only for testing purposes.
val client = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.addInterceptor(object : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
val response = chain.proceed(chain.request())
when (response.code()) {
504 -> {
//Show Bad Request Error Message
}
}
return response
}
})
.build()
searchRetrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(URL)
.client(client)
.build()
Later in the code, I use Retrofit's execute() method to make a synchronous call to the API. The execute() line and val response = chain.proceed(chain.request()) crashes my app if the API service is down or if it's taking too long to retrieve results. I get the java.net.SocketTimeoutException error.
What can I do to prevent my app from crashing when the API service I'm using is down? What can I add to the Interceptor or should I surround my execute() call in a try catch statement?
Proper solution would be to use enqueue instead of execute. synchronous network calls are almost always a bad idea, because you don't want to block the calling thread. to use enqueue you should do
call.enqueue(object : Callback<SomeResponse> {
override fun onFailure(call: Call<SomeResponse>?, t: Throwable?) {
// This code will be called when your network call fails with some exception
// SocketTimeOutException etc
}
override fun onResponse(call: Call<SomeResponse>?, response: Response<SomeResponse>?) {
// This code will be called when response is received from network
// response can be anything 200, 504 etc
}
})
If you must use execute then at the very least you will have to enclose your execute call in try catch
try{
call.execute()
}
catch(e: Exception){
// Process any received exception (SocketTimeOutEtc)
}
I have a basic retrofit setup in kotlin.
val BASE_URL: String = "http://10.0.2.2:5000/"
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
private val client: OkHttpClient = OkHttpClient.Builder().apply {
this.addInterceptor(interceptor)
}.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.client(client)
.build()
val service: Api by lazy {
retrofit.create(Api::class.java)
}
I want to check if the server I'm fetching my data from is running - if its not I want to fall back on the local DB for basic functionality. I tried something similar at first but there's a couple of things that are wrong with this approach. First of all the request timeout period is 10 seconds long, which is a little bit more than you'd want it to be for an app. Second, well, it doesn't really work, it'll still throw an exception if the server is offline.
fun serverReachable(): Boolean {
return try {
GlobalScope.async {
// call whatever api function here
}
true
} catch (e: Exception) {
false
}
}
Is there are quick and dirty version of checking if the server is up?
I am using RxAndroid + Retrofit to make http request. Code looks like below:
Interceptor headerInterceptor = getHeaderInterceptor();
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.callTimeout(5, TimeUnit.SECONDS)
.addInterceptor(headerInterceptor)
.addInterceptor(httpLoggingInterceptor)
.build();
Gson gson = new GsonBuilder()
.setLenient()
.create();
sRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
Use it like this:
ApiProvider.provideApi(MyApi.class)
.submit(id, mRequest)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
response -> {
Log.w("tag", "success");
},
throwable -> {
Log.w("tag", "error");
}
);
I set connectTimeout / readTimeout / writeTimeout to be 60 seconds, and set callTimeout to be 5 seconds.
I know this configuration may be not reasonable but I just want to get a timeout exception after 5 seconds, and the Log.w("tag", "error"); could be called.
However, I found this line will never be called for my testing. And if I set connectionTimeout to 1 second, then this line will be called immediately.
So what I should do if I want callTimeout to trigger the log error line?
As for as I can see, I think your problem maybe isn't from how many seconds you set for callTimeout(5, TimeUnit.SECONDS), I think your maybe Rx stream have already throw some errors, so stream just break you can get any response from here. However, you reset time seconds to 1s, and then you restart app, this time stream not break and you get error.
So simple re-test it again to make sure your stream won't break even before enter this subscribe.
And I have been test with some stupid implementations:
class MainActivity : AppCompatActivity() {
val delayInterceptor = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
Thread.sleep(6000L)
return chain.proceed(chain.request())
}
}
val client: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.callTimeout(5, TimeUnit.SECONDS)
.addInterceptor(delayInterceptor)
.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://en.wikipedia.org/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
data class Wiki(
#SerializedName("type")
val type: String
)
interface WikiService {
#GET("api/rest_v1/page/random/summary")
fun getRandomSummary(): Single<Wiki>
}
#SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
retrofit.create(WikiService::class.java)
.getRandomSummary()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.d("tag", "success")
}, {
Log.e("tag", "error")
})
}
}
I finally find the the cause.
I was using com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory, which had been deprecated per its readme: https://github.com/JakeWharton/retrofit2-rxjava2-adapter
This is now DEPRECATED!
Retrofit 2.2 and newer have a first-party call adapter for RxJava 2: https://github.com/square/retrofit/tree/master/retrofit-adapters/rxjava2
After switching to retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory, everything start working nicely.
And BTW for any guy who might be interested in what the differences are between them two? Attach the key info I found below:
In API declaration I have this code:
#FormUrlEncoded
#POST("loginByPass/")
fun loginByPassword(#Field("login") login: String,
#Field("password") password: String,
#Field("phone") phone: String) : Observable<AuthResponse>
Retrofit object I create this way:
class API {
companion object {
fun getRetrofitAPI() : IAPI {
val interceptor = HttpLoggingInterceptor()
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.addNetworkInterceptor(interceptor)
.build()
val retrofit = Retrofit.Builder()
.baseUrl(SERVER_ADDRESS)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
return retrofit.create(IAPI::class.java)
}
}
}
And request:
api.loginByPassword(login, password, "")
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe({
Log.w("USER_DB", "CODE: " + it.code)
}, {
this.sayError(it.localizedMessage)
}).dispose()
And I have no response at all! In logs it seems like I haven't sent any request. Also I have seen sent packages - from emulator was sent nothing. I'm calling to server IP address by HTTP.
Could you give me piece of advice, what's the problem?
According to #DrawnRaccoon 's answer, .dispose() destroys the request, so I should use CompositeDisposable to collect all requests and dispose them in onDestroy() method.
I am using paging library version 2.1.0 in with java. How I can manage false status or empty array from API. I tried finding some solution but didn't get any.
If you're using Retrofit, you could use Interceptor to intercept error code.
Below is the code to handle response code error 401. Similarly, you can handle any response code.
var retrofit:Retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.build()
private fun getOkHttpClient() : OkHttpClient{
val okHttpCLient = OkHttpClient.Builder()
.addInterceptor(object : Interceptor{
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val response = chain.proceed(request)
if(response.code() == 401){
Log.e(TAG,"Un-Authorized user")
}
return response
}
})
return okHttpCLient.build()
}
//OkHttp Library in Gradle file:
implementation 'com.squareup.okhttp3:okhttp:3.12.0'