How to pretty print in Retrofit 2? - android

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 }

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;
}
}

RxJava2 with Retrofit2-unable to get logs using HttpLoggingInterceptor

I have integrated RxJava2 with Retrofit2 and I am unable to get logs for api calls using HttpLoggingInterceptor.
I am using below dependencies-
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'io.reactivex.rxjava2:rxjava:2.0.2'
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.9.1'
and using below code
public class RxJavaRetofit {
private ApiMethods api;
public RxJavaRetofit() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(AppUrls.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
api = retrofit.create(ApiMethods.class);
}
public void getSocialFeed(Context context, SocialRequest socialRequest, final mevorun.mevolife.com.mevorun.listeners.onSocialServiceResponseHandler onServiceResponseHandler) {
Utils.showDialog(context);
api.getSocialData(socialRequest).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer<Social>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(Social value) {
try {
onServiceResponseHandler.onServerResponse(value.getResponse(),"Feed");
Utils.hideDialog();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Utils.hideDialog();
}
});
}
In my Logcat I have selected Debug and searching HttpLoggingInterceptor but there is no log for that keyword.I tried in Verbose also but its not showing logs for api call.How can i get logs for my api call?
This almost a comment not really an answer.
Everything looks ok with your code. The interceptor logs with the tag OkHttp. Searching for that should yield the logs you are looking for.

How to get list of things in Retrofit

There's something about Retrofit that I'm not getting. I'm following examples on the web, but can't get it to even compile. I am able to access the data from the RESTful service via old school (i.e. HttpGet/HttpResoponse) so I know the service works. It returns a bunch of EmployeeData (as oppose to just one EmployeeData)
In the app gradle file:
dependencies {
...
compile 'com.google.code.gson:gson:2.7'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
...
}
The url for the RESTful service endpoint is:
https://com.somewhere/PhoneDirectoryService/api/Employees/
I have defined a string for the base url:
<string name="https_phone_directory_service_baseurl">https://com.somewherePhoneDirectoryService/api/</string>
Interface
public interface EmployeesService {
#GET("Employees.json") // the string in the GET is end part of the endpoint url
public Call<List<EmployeeEndpointResponse>> listEmployees();
}
Response
public class EmployeeEndpointResponse {
private List<EmployeeData> employees; // EmployeeData is a POJO
// public constructor is necessary for collections
public EmployeeEndpointResponse() {
employees = new ArrayList<EmployeeData>();
}
public static EmployeeEndpointResponse parseJSON(String response) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
Type collectionType = new TypeToken<List<EmployeeData>>(){}.getType();
Gson gson = gsonBuilder.create();
EmployeeEndpointResponse employeeEndpointResponse = gson.fromJson(response, EmployeeEndpointResponse.class);
return employeeEndpointResponse;
}
}
Get the data
public static boolean getEmployeeData(Context context) {
Resources resources = context.getResources();
URI uri = null;
try {
uri = new URI(resources.getString(R.string.https_phone_directory_service_baseurl));
}
catch (URISyntaxException exception) {
Log.e("getEmployeeData", exception.toString());
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(uri.toString())
.addConverterFactory(GsonConverterFactory.create())
.build();
Call<List<EmployeeEndpointResponse>> call = service.listEmployees();
EmployeesService service = retrofit.create(EmployeesService.class);
// this does not compile
// error: <anonymous com.somewhere.utilities.Utilities$1> is not
// abstract and does not override abstract method
// onFailure(Call<List<EmployeeEndpointResponse>>,Throwable) in Callback
call.enqueue(new Callback<List<EmployeeEndpointResponse>>() {
#Override
public void onResponse(Response<List<EmployeeEndpointResponse>> response) {
List<EmployeeEndpointResponse> myList = response.body();
// Successfull request, do something with the retrieved messages
}
#Override
public void onFailure(Throwable t) {
// Failed request.
}
});
// so I tried this which gives compile error
// retrofit2.Callback<java.util.List
// <com.somewhere.gson.EmployeeEndpointResponse>>)
// in Call cannot be applied to anonymous retrofit2.Callback
// <com.somewhere.gson.EmployeeEndpointResponse>)
call.enqueue(new Callback<EmployeeEndpointResponse>() {
#Override
public void onResponse(Call<EmployeeEndpointResponse> call, Response<EmployeeEndpointResponse> response) {
// handle response here
EmployeeEndpointResponse employeeEndpointResponse = (EmployeeEndpointResponse)response.body();
}
#Override
public void onFailure(Call<EmployeeEndpointResponse> call, Throwable t) {
}
});
}
What do I need to do?
Data looks like:
[
{"Name":"Smith, Ken J.",
"Cell":"",
"EmailAddress":"Ken.Smith#somewhere.com",
"Location":"West",
"Phone":"555-555-5555",
"Address":"PO Box 555 5555 Del Norte",
"City":"Jackson",
"State":"WY",
"Zip":"85555",
"Latitude":42.24976,
"Longitude":-107.82171},
{"Name":"Cox, Daniel B.",
"Cell":"",
"EmailAddress":"daniel.cox#somewhere.com",
"Location":"West",
"Phone":"(555) 555-5516",
etc ...}
]
Add the dependency
dependencies {
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okhttp3:logging-interceptor:3.2.0'
}
Generate the http client
private static OkHttpClient getOkHttpClient(){
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(HttpLoggingInterceptor.Level.NONE);
OkHttpClient okClient = new OkHttpClient.Builder()
.addInterceptor(logging)
.build();
return okClient;
}
Getting the data
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(YOUR_URL)
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build();
return retrofit;
EmployeesService service = retrofit.create(EmployeesService.class);
Call<EmployeeEndpointResponse> call = service.listEmployees();
call.enqueue(new Callback<EmployeeEndpointResponse>() {
#Override
public void onResponse(Call<EmployeeEndpointResponse> call, Response<EmployeeEndpointResponse> response) {
EmployeeEndpointResponse employeeEndpointResponse = response.body();
//manage your response
}
#Override
public void onFailure(Call<EmployeeEndpointResponse> call, Throwable t) {
}
});
Parsing JSON
Your POJO doesn't need any aditional methods to parse JSON. Just generate the code with GSON anotations with http://www.jsonschema2pojo.org/

SocketTimeOut exception while using Retrofit

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 :)

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.

Categories

Resources