SocketTimeOut exception while using Retrofit - android

I am just trying to do post api call using Retrofit.The server is responding with correct data.I checked with Postman(Chrome). My code is as follows
public class MainActivity extends Activity implements retrofit2.Callback>{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectTimeout(6, TimeUnit.MINUTES)
.readTimeout(6, TimeUnit.MINUTES)
.writeTimeout(6, TimeUnit.MINUTES)
.build();
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://kokanplaces.com/")
.addConverterFactory(GsonConverterFactory.create(gson)).client(okHttpClient)
.build();
// prepare call in Retrofit 2.0
ApiInterface apiService =
ApiClient.getClient().create(ApiInterface.class);
Call<List<CityModel>> call = apiService.getCitiesList();;
//asynchronous call
call.enqueue(this);
}
#Override
public void onResponse(Call<List<CityModel>> call, Response<List<CityModel>> response) {
int code = response.code();
if (code == 200) {
for (CityModel cityModel : response.body()) {
System.out.println(
cityModel.getCityname() + " (" + cityModel.getCityId() + ")");
}
} else {
Toast.makeText(this, "Did not work: " + String.valueOf(code), Toast.LENGTH_LONG).show();
}
}
#Override
public void onFailure(Call<List<CityModel>> call, Throwable t) {
Toast.makeText(this, "failure", Toast.LENGTH_LONG).show();
System.out.println(t.fillInStackTrace());
t.printStackTrace();
}
}
public interface ApiInterface {
#POST("wp-json/getCities")
Call<List<CityModel>> getCitiesList();
}
Every time it is throwing Socket timeout exception.
Any solution will be great help.

I met the problems like you before. I fixed by adding custom OkHttpClient:
Constants.TIMEOUT_CONNECTION = 60;
private OkHttpClient getOkHttpClient() {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.readTimeout(0, TimeUnit.NANOSECONDS)
.connectTimeout(Constants.TIMEOUT_CONNECTION, TimeUnit.SECONDS)
.writeTimeout(Constants.TIMEOUT_CONNECTION, TimeUnit.SECONDS)
// .sslSocketFactory(getSSLSocketFactory())
.build();
return okHttpClient;
}
and retrofitAdapter:
retrofitAdapter = new Retrofit.Builder()
.baseUrl(ConstantApi.BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(getOkHttpClient())
.build();
Remember readTimeout is 0, I am using retrofit 2.1.0. Default timeout of retrofit is 10 seconds. I tried to set readTimeout is 60 seconds but no effect.

Topic tags: Socket closed, socket timeout
Explanation: Retrofit maintains connection which is locking socket.
More: https://github.com/square/okhttp/issues/3146
SOLUTION:
configure connectionPool like in below example:
private OkHttpClient getOkHttpClient() {
final OkHttpClient okHttpClient = new OkHttpClient.Builder()
.connectionPool(new ConnectionPool(0,1,TimeUnit.NANOSECONDS))
.build();
return okHttpClient;
}
Please remember to mark answer as correct :)

Related

How to stop Retrofit from caching old parameter values?

On some APIs on the app I'm working on, the user location is sent as one of the parameters, so their locations can be monitored via web dashboard, like this:
#FormUrlEncoded
#POST("v1/visit")
Call<StartVisitResponse> startVisit(
#Header("Authorization") String token,
#Header("latitude") String latiude,
#Header("longitude") String longitude,
#Field("outlet_id") int outlet_id,
#Field("date") String String visit_date
);
And this is an activity that illustrates the main problem:
class StartVisitActivity implements AppCompactActivity {
ActivityStartVisitBinding uiBinding;
InstantLocate theInstantLocate; // from https://github.com/mukul56/InstantLocate
#Override
protected void onCreate(Bundle savedInstanceState) {
uiBinding.btnGetLocation( v -> {
initLocation();
});
uiBinding.btnStartVisit( v -> {
ApiClient.getClient().create(ApiInterface.class).startVisit(
Helper.getAuthToken(),
""+theInstantLocate.getlatitude(),
""+theInstantLocate.getlongitude(),
outletId,
date).enqueue(new Callback<StartVisitResponse>() {
#Override
public void onResponse(Call<StartVisitResponse> call, Response<StartVisitResponse> response) {
if (response.isSuccessful()) {
} else {
}
}
#Override
public void onFailure(Call<StartVisitResponse> call, Throwable t) {
}
});
})
}
private void initLocation(){
theInstantLocate = new InstantLocate(this);
theInstantLocate.instantLocation();
Toast.makeText(getApplicationContext, "Hi you are going to visit outlet ("+theInstantLocate.getlatitude()+","+theInstantLocate.getlongitude()+").", Toast.LENGTH_SHOW).show();
}
}
First press the "Get Location" button to get our current location, then the "Start Visit" button. While inspecting the web dashboard, we found some users visiting different outlets started at the same position (which actually didn't). Weird.
After about 15 minutes of changing FakeGPS location and calling initLocation() (without quitting the app) repeatedly, both locations are always match. The issue is on startVisit(). For example I changed locations a few time: Tokyo -> Bangkok -> Jakarta. When inspecting the startVisit log, my detection location was still in Tokyo. Seems like Retrofit cached my old position parameters. How to clear those, so the latest values are always used?
For more information, this is my ApiClient class:
public class ApiClient {
public static final String BASE_URL = ".........";
private static Retrofit retrofit = null;
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Interceptor httpClient = chain -> {
Request original = chain.request();
String method = original.method();
HttpUrl.Builder httpBuilder = original.url().newBuilder();
String token = Prefs.getString("TOKEN", null);
Request.Builder requestBuilder;
if (token != null){
requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.method(method, original.body())
.header("Authorization", Helper.getToken())
.header("latitude", Helper.getLatitude())
.header("longitude", Helper.getLongitude());
}else {
requestBuilder = original.newBuilder()
.header("Accept", "application/json")
.header("Content-Type", "application/json")
.method(method, original.body());
}
Request request = requestBuilder.url(httpBuilder.build()).build();
return chain.proceed(request);
};
OkHttpClient client = null;
client = new OkHttpClient.Builder().connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS).addInterceptor(interceptor)
.addInterceptor(new ChuckerInterceptor(BaseApp.getAppContext()))
.addInterceptor(httpClient).build();
if (retrofit==null) {
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

Running test with MockWebServer always call the failure call back (Connection Exception)

I'm running mock web server to test REST API calls, when activity launch and start the mockWebserver and execute the API call I'm getting the connection refused,using OKHTTP v-3.10.0 & Retrofit 2.3.0
ActivityTest Code:
#Rule
public ActivityTestRule<STBUpgradeActivity> mActivityRule = new ActivityTestRule<>(STBUpgradeActivity.class, false, false);
private MockWebServer server;
#Before
public void setUp() throws Exception {
super.setUp();
server = new MockWebServer();
server.start();
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
String serverUrl = server.url("/").toString();
EnvConfig.WEB_API_SIT = serverUrl;
}
#Test
public void testPageContentIsShown() throws Exception {
String fileName = "stb_upgrade_page_content_200.json";
server.enqueue(new MockResponse()
.setResponseCode(200)
.setBody(RestServiceTestHelper.getStringFromFile(getInstrumentation().getContext(), fileName)));
Intent intent = new Intent();
mActivityRule.launchActivity(intent);
onView(withId(R.id.button_upgrade)).check(matches(isDisplayed()));
onView(withText("New residential subscribers and standard install only. If Sports is a part of your package")).check(matches(isDisplayed()));
}
=======
RestAPI client
private Retrofit getClient() {
retrofit = new Retrofit.Builder()
.baseUrl(WEB_API_HOST)
.addConverterFactory(GsonConverterFactory.create())
.client(getHttpClient())
.build();
return retrofit;
}
Change the setUp code to
#Before
public void setUp() throws Exception {
super.setUp();
server = new MockWebServer();
server.start(8080);
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
}
set the Retroft Base URL to
private Retrofit getClient() {
retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/")
.addConverterFactory(GsonConverterFactory.create())
.client(getHttpClient())
.build();
return retrofit;
}

Retrofit2 Tail Recursion Using RxJava / RxAndroid

I am really trying to get a hang of using Retrofit with RxJava / RxAndroid. I've done this using normal Retrofit2 Callback method in a previous app without the use of Reactive Programming and it worked fine. So, here is it. I need to Tail Recall a function meant to fetch all Local Government from the server. The API uses pagination (I have to construct the URL with ?page=1, perPage=2). I've to do this till I've the whole data. So, below is my Rx code
public static Observable<LgaListResponse> getPages(Context acontext) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pageControl = BehaviorSubject.<Integer>create(1);
Observable<LgaListResponse> ret2 = pageControl.asObservable().concatMap(integer -> {
if (integer > 0) {
Log.e(TAG, "Integer: " + integer);
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(integer), String.valueOf(21))
.doOnNext(lgaListResponse -> {
if (lgaListResponse.getMeta().getPage() != lgaListResponse.getMeta().getPageCount()) {
pageControl.onNext(initialPage + 1);
} else {
pageControl.onNext(-1);
}
});
} else {
return Observable.<LgaListResponse>empty().doOnCompleted(pageControl::onCompleted);
}
});
return Observable.defer(() -> ret2);
}
And my ServiceGenerator Class
public class ServiceGenerator {
private static final String TAG = "ServiceGen";
private static OkHttpClient.Builder builder = new OkHttpClient.Builder();
private static Retrofit.Builder retrofitBuilder =
new Retrofit.Builder()
.baseUrl(BuildConfig.HOST)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create(CustomGsonParser.returnCustomParser()));
public static <S> S createService(Class<S> serviceClass, String token) {
builder.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
/*builder.addNetworkInterceptor(new StethoInterceptor());*/
builder.connectTimeout(30000, TimeUnit.SECONDS);
builder.readTimeout(30000, TimeUnit.SECONDS);
if (token != null) {
Interceptor interceptor = chain -> {
Request newRequest = chain.request().newBuilder()
.addHeader("x-mobile", "true")
.addHeader("Authorization", "Bearer " + token).build();
return chain.proceed(newRequest);
};
builder.addInterceptor(interceptor);
}
OkHttpClient client = builder.build();
Retrofit retrofit = retrofitBuilder.client(client).build();
Log.e(TAG, retrofit.baseUrl().toString());
return retrofit.create(serviceClass);
}
public static Retrofit retrofit() {
OkHttpClient client = builder.build();
return retrofitBuilder.client(client).build();
}
public static class CustomGsonParser {
public static Gson returnCustomParser(){
return new GsonBuilder()
.setExclusionStrategies(new ExclusionStrategy() {
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass().equals(RealmObject.class);
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
})
.create();
}
}
}
So, I noticed on the first call, I get a response, but on the second one, the 440Error is thrown. The URL is formed, but the request throws a 400Error. I don't know why it's throwing a 400 everything is working fine if I use POSTMAN to test. And, I tested with my old code too. The Log is too long, so I put it in pastebin LOGS any help thanks. I've written most of this app with RxAndroid / RxJava. Thanks
I suggest you simplify things (and remove recursion). First build up your pages using something like
public static Observable<LgaListResponse> getPages(Context acontext, int initialPage, int perPage) {
String token = PrefUtils.getToken(acontext);
BehaviorSubject<Integer> pagecontrol = BehaviorSubject.<Integer>create(initialPage);
Observable<LgaListResponse> ret2 = pagecontrol.asObservable().concatMap(
new Func1<Integer,Observable<LgaListResponse>>() {
Observable<LgaListResponse> call(Integer pageNumber) {
if (pageNumber > 0) {
return ServiceGenerator.createService(ApiService.class, token)
.getLgas(String.valueOf(aKey), String.valueOf(perPage))
.doOnNext(
new Action1<LgaListResponse>() {
void call(LgaListResponse page) {
if (page.getMeta().getPage() != page.getMeta().getPageCount()) {
pagecontrol.onNext(page.getMeta().getNextPage());
} else {
pagecontrol.onNext(-1);
}
}
}
);
}
else {
return Observable.<LgaListResponse>empty().doOnCompleted(()->pagecontrol.onCompleted());
}
}
}
);
return Observable.defer(
new Func0<Observable<LgaListResponse>() {
Observable<LgaListResponse> call() {
return ret2;
}
}
);
}
then subscribe to the resulting observable. It looks horrible because I've avoided using lambdas but it should work.

How to use setCache for online data OkHttpClient in Android

I want to show 3 fragments in my Activity and load data from json in any fragments! I need to show website data into Recyclerview with OkHTTP v3 library.
I want to show this data for offline, I mean, if user turn off data/wifi show this datas for offline. but i do not want use SQLite Database!
For this idea i use okHttpClient cache , but not found setCache for client and show me this Error : Image link
MainActivity codes:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ArrayList<AndroidVersion> data;
private DataAdapter adapter;
private static Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
initViews();
}
private void initViews() {
recyclerView = (RecyclerView) findViewById(R.id.card_recycler_view);
recyclerView.setHasFixedSize(true);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
recyclerView.setLayoutManager(layoutManager);
loadJSON();
}
private void loadJSON() {
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(REWRITE_CACHE_CONTROL_INTERCEPTOR);
//setup cache
File httpCacheDirectory = new File(context.getCacheDir(), "responses");
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(httpCacheDirectory, cacheSize);
//add cache to the client
client.setCache(cache);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://api.learn2crack.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
RequestInterface request = retrofit.create(RequestInterface.class);
Call<JSONResponse> call = request.getJSON();
call.enqueue(new Callback<JSONResponse>() {
#Override
public void onResponse(Call<JSONResponse> call, Response<JSONResponse> response) {
JSONResponse jsonResponse = response.body();
data = new ArrayList<>(Arrays.asList(jsonResponse.getAndroid()));
adapter = new DataAdapter(data);
recyclerView.setAdapter(adapter);
}
#Override
public void onFailure(Call<JSONResponse> call, Throwable t) {
Log.d("Error", t.getMessage());
}
});
}
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
#Override
public okhttp3.Response intercept(Chain chain) throws IOException {
okhttp3.Response originalResponse = chain.proceed(chain.request());
if (isNetworkAvailable(context)) {
int maxAge = 60; // read from cache for 1 minute
return originalResponse.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.build();
} else {
int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale
return originalResponse.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.build();
}
}
};
public static boolean isNetworkAvailable(Context context) {
ConnectivityManager cm =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
return activeNetwork != null &&
activeNetwork.isConnectedOrConnecting();
}
}
How can i fix this and use okHttpClient cache?
From the screenshot, it is apparent you are using an outdated version of OkHttp. Retrofit 2 requires OkHttp 3. (The latest right now is 3.4.1.)
Also, to set the cache on the client in OkHttp 3, new OkHttpClient.Builder().cache(cache).build().

How to pretty print in Retrofit 2?

I have my retrofit set up with HttpLoggingInterceptor like this:
Gson gson = new GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
.setPrettyPrinting() // Pretty print
.create();
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.client(client)
.build();
On my Gson instance, I did setPrettyPrinting and I still get compact JSON outputs.
Here are my libraries.
compile 'com.google.code.gson:gson:2.5'
compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
compile 'com.squareup.okhttp3:logging-interceptor:3.0.1'
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
compile 'com.squareup.okhttp3:okhttp:3.0.1'
How can I acheive pretty printing using Retrofit 2?
Thanks.
EDIT: Updated my libraries and still didn't work
Inspired by Tanapruk's answer this is what I did to make it work with my versions of retrofit (2.1.0) and okhttp.logging-interceptor (3.8.1).
This version works for printing both JSON objects and arrays.
class ApiLogger : HttpLoggingInterceptor.Logger {
override fun log(message: String) {
val logName = "ApiLogger"
if (message.startsWith("{") || message.startsWith("[")) {
try {
val prettyPrintJson = GsonBuilder().setPrettyPrinting()
.create().toJson(JsonParser().parse(message))
Log.d(logName, prettyPrintJson)
} catch (m: JsonSyntaxException) {
Log.d(logName, message)
}
} else {
Log.d(logName, message)
return
}
}
}
And in the client:
val httpClientBuilder = OkHttpClient.Builder()
val httpLoggingInterceptor = HttpLoggingInterceptor(ApiLogger())
httpLoggingInterceptor.level = Level.BODY
httpClientBuilder.addInterceptor(httpLoggingInterceptor)
create your own custom HttpLogginInterceptor.
public class CustomHttpLogging implements HttpLoggingInterceptor.Logger {
#Override
public void log(String message) {
final String logName = "OkHttp";
if (!message.startsWith("{")) {
Log.d(logName, message);
return;
}
try {
String prettyPrintJson = new GsonBuilder().setPrettyPrinting().create().toJson(new JsonParser().parse(message));
Log.d(logName, prettyPrintJson);
} catch (JsonSyntaxException m) {
Log.d(logName, message);
}
}
}
In your client, add:
OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new CustomHttpLogging())
.build();
Thanks for Tanapruk Tangphianphan's answer.
I improve it for support all Java Platform not only for Android.
Create PrettyLogger class
class PrettyLogger implements HttpLoggingInterceptor.Logger {
private Gson mGson = new GsonBuilder().setPrettyPrinting().create();
private JsonParser mJsonParser = new JsonParser();
#Override
public void log(String message) {
String trimMessage = message.trim();
if ((trimMessage.startsWith("{") && trimMessage.endsWith("}"))
|| (trimMessage.startsWith("[") && trimMessage.endsWith("]"))) {
try {
String prettyJson = mGson.toJson(mJsonParser.parse(message));
Platform.get().log(INFO, prettyJson, null);
} catch (Exception e) {
Platform.get().log(WARN, message, e);
}
} else {
Platform.get().log(INFO, message, null);
}
}
}
Use in Buidler:
OkHttpClient.Builder builder = new OkHttpClient().newBuilder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new PrettyLogger());
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
builder.addInterceptor(loggingInterceptor);
Beta 2 did not always respect custom gson settings. Try upgrading to Beta 3.
From the beta 3 change log --
Fix: The Gson converter now respects settings on the supplied Gson
instance (such as serializeNulls). This requires Gson 2.4 or newer.
You will also need to upgrade to okhttp3 for beta3.
Kotlin version that doesnot use gson:
HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
private fun print(m: String) {
//Crashlytics.log(m)
Log.i("API", m)
}
override fun log(message: String) {
if (message.length > 500)
return print("=== more than 500 characters ===")
if (message.startsWith("{") || message.startsWith("[")) try {
JSONObject(message).toString(4).also(::print)
} catch (e: JSONException) { print(message) }
else print(message)
}
}).also { it.level = HttpLoggingInterceptor.Level.BODY }

Categories

Resources