Moving from AsyncTask to Retrofit - android

I am using Asycntask for handling my service. However, I want to use Retrofit and like to get some advice before moving on. My json services are like following. All of them have a result JSONObject and data(JSONObject or JSONArray). When I look at some tutorials, it says retrofit works with GSON and I have to convert my models to GSON format(http://www.jsonschema2pojo.org/). The thing I want to learn is, should I also add this result part of my services into my model. While using Asynctask, I am parsing result part and if the message is "ok", i start my data parsing. If message is not "ok", then I show an alert dialog with message. Can I get some advice about that?
{
result: {
code: 0,
message: "OK",
dateTime: "20160204135212",
},
movie: [
{
name: "Movie 1",
category: "drama"
},
{
name: "Movie 2"
category: "comedy"
}
]
}

Seems you need to use Interceptors.
Interceptors is the mechanism which allow to you do some work before use response. I mark the line, where you need to add transformation logic.
public static class LoggingInterceptor implements Interceptor {
#Override
public com.squareup.okhttp.Response intercept(Chain chain) throws IOException {
Log.i("LoggingInterceptor", "inside intercept callback");
Request request = chain.request();
long t1 = System.nanoTime();
String requestLog = String.format("Sending request %s on %s%n%s",
request.url(), chain.connection(), request.headers());
if (request.method().compareToIgnoreCase("post") == 0) {
requestLog = "\n" + requestLog + "\n" + bodyToString(request);
}
Log.d("TAG", "request" + "\n" + requestLog);
com.squareup.okhttp.Response response = chain.proceed(request);
long t2 = System.nanoTime();
String responseLog = String.format("Received response for %s in %.1fms%n%s",
response.request().url(), (t2 - t1) / 1e6d, response.headers());
String bodyString = response.body().string();
Log.d("TAG", "response only" + "\n" + bodyString);
Log.d("TAG", "response" + "\n" + responseLog + "\n" + bodyString);
// HERE YOU CAN ADD JSON DATA TO EXISTING RESPONSE
return response.newBuilder()
.body(ResponseBody.create(response.body().contentType(), bodyString))
.build();
}
public static String bodyToString(final Request request) {
try {
final Request copy = request.newBuilder().build();
final Buffer buffer = new Buffer();
copy.body().writeTo(buffer);
return buffer.readUtf8();
} catch (final IOException e) {
return "did not work";
}
}
}

Yes, this response from your service should be a model in your app.
Retrofit will automatically serialize the json to java object in onResponse
#Override
public void onResponse(Response<Result> response){
if(response.isSuccess()){
Result result = response.body();
if(result.getMessage().equals("OK")){
//do something
}else{
//show an alert dialog with message
}
}

Related

Python Requests to Java OkHttp for Android

I have the following code working perfectly in Python:
login_data = {'identifier': 'something#email.com', 'password': 'Password'}
url = "https://www.duolingo.com/2017-06-30/login?fields="
p = requests.post(url, data = json.dumps(login_data))
if p.status_code is 200:
print("SUCCESS")
else:
print("ERROR")
I want to convert it to Java using OkHttp to be able to implement it in Android Studio.
I have written the following code but it gives Status Code: 422 which according to Google means Unprocessable Entity:
OkHttpClient client = new OkHttpClient();
String url = "https://www.duolingo.com/2017-06-30/login?fields=";
String login_data = "{\"identifier\": \"something#email.com\", \"password\": \"Password\"";
RequestBody body = RequestBody.create(login_data, MediaType.parse("application/json"));
Request postRequest = new Request.Builder()
.url(url)
.post(body)
.build();
client.newCall(postRequest).enqueue(new Callback()
{
#Override
public void onFailure(#NotNull Call call, #NotNull IOException e)
{
Log.i("TAG", "ERROR - " + e.getMessage());
}
#Override
public void onResponse(#NotNull Call call, #NotNull Response response) throws IOException
{
if (response.isSuccessful())
{
Log.i("LOG", "SUCCESS - " + response.body().string());
}
else
{
Log.i("LOG", "FAILED. Status Code: " + String.valueOf(response.code));
}
}
});
All help is appreciated!
You have a missing closing bracket in the request body at java implementation.
"{\"identifier\": \"something#email.com\", \"password\": \"Password\"}"

503 HTTP response code with Twitter OAUTH

I try to get authorization token from Twitter (app-only), the code almost fully follows oficial guide, but get 503 Service unavailable code.
#Override
protected Boolean doInBackground(Void... params) {
String cred_enc = tw_cons_key + ":" + tw_priv_cons_key;
cred_enc = Base64.encodeToString(cred_enc.getBytes(), Base64.NO_WRAP);
Request request = new Request.Builder().url("https://api.twitter.com/oauth2/token")
.header("Authorization:", "Basic " + cred_enc)
.header("Content-Type:", "application/x-www-form-urlencoded;charset=UTF-8")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
ResponseBody body = response.body();
if (response.isSuccessful()) {
Headers headers = response.headers();
//response check
for (int i = 0; i < headers.size(); i++) {
System.out.println("Headers: " + i + " " + headers.name(i) + " : " + headers.value(i));
}
System.out.println(body.string());
try {
JSONObject jsonObject = new JSONObject(body.string());
} catch (JSONException e) {
e.printStackTrace();
}
} else {
throw new IOException("Unexpected code" + response);
}
body.close();
}
});
return true;
}
What may be the possible reason?
It required me to write a simple server on localhost to find out what HTTP request was actually generated.
The problem was with colons: .header("Authorization:"resulted in Authorization:: in the network request!
After I removed colons from the header key values, both HttpUrlConnection and OkHttp code variants worked seamlessly.

How to pass body to OKHttp message?

I have found this example below to send HTTP POST message with OKHttp.
I do not understand how to pass a body string to RequestBody. Why it takes two argument?
RequestBody formBody = new FormBody.Builder()
.add("message", "Your message")
.build();
Request request = new Request.Builder()
.url("http://rhcloud.com")
.post(formBody).addHeader("operation", "modifyRecords")
.build();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, Response response) throws IOException {
try (ResponseBody responseBody = response.body()) {
if (!response.isSuccessful())
throw new IOException("Unexpected code " + response);
Headers responseHeaders = response.headers();
for (int i = 0, size = responseHeaders.size(); i < size; i++) {
System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
}
System.out.println(responseBody.string());
}
}
});
}
}
I'm not sure if I understand what you mean quite right, but if you are asking why the FormBody .add() takes two arguments, it's because these are Key-Value-Pairs. The first parameter is the name and the second the value.
Anyway I think this example shows a clearer way how to post a string:
public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.parse("text/x-markdown; charset=utf-8");
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
String postBody = ""
+ "Releases\n"
+ "--------\n"
+ "\n"
+ " * _1.0_ May 6, 2013\n"
+ " * _1.1_ June 15, 2013\n"
+ " * _1.2_ August 11, 2013\n";
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw")
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
.build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}

Retrofit 2 - null response body

I am trying to convert following response with Retrofit 2
{
"errorNumber":4,
"status":0,
"message":"G\u00f6nderilen de\u011ferler kontrol edilmeli",
"validate":[
"Daha \u00f6nceden bu email ile kay\u0131t olunmu\u015f. L\u00fctfen giri\u015f yapmay\u0131 deneyiniz."
]
}
But I am allways getting null response in onResponse method. So I tried to look at error body of the response with response.errorBody.string(). Error body contains exactly same content with raw response.
Here is my service method, Retrofit object and response data declerations:
#FormUrlEncoded
#POST("/Register")
#Headers("Content-Type: application/x-www-form-urlencoded")
Call<RegisterResponse> register(
#Field("fullName") String fullName,
#Field("email") String email,
#Field("password") String password);
public class RegisterResponse {
public int status;
public String message;
public int errorNumber;
public List<String> validate;
}
OkHttpClient client = new OkHttpClient();
client.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
final String content = UtilityMethods.convertResponseToString(response);
Log.d(TAG, lastCalledMethodName + " - " + content);
return response.newBuilder().body(ResponseBody.create(response.body().contentType(), content)).build();
}
});
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
domainSearchWebServices = retrofit.create(DomainSearchWebServices.class);
I have controlled response JSON with jsonschema2pojo to see if I modled my response class wright and it seems OK.
Why Retrofit fails to convert my response?
UPDATE
For now as a work around I am building my response from error body.
I have solved the problem. When I make a bad request (HTTP 400) Retrofit doesn't convert the response. In this case you can access the raw response with response.errorBody.string(). After that you can create a new Gson and convert it manually:
if (response.code() == 400 ) {
Log.d(TAG, "onResponse - Status : " + response.code());
Gson gson = new Gson();
TypeAdapter<RegisterResponse> adapter = gson.getAdapter(RegisterResponse.class);
try {
if (response.errorBody() != null)
registerResponse =
adapter.fromJson(
response.errorBody().string());
} catch (IOException e) {
e.printStackTrace();
}
}

OkHttp how to log request body

I'm using an interceptor, and I would like to log the body of a request I'm making but I can't see any way of doing this.
Is it possible ?
public class LoggingInterceptor implements Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Response response = chain.proceed(request);
long t2 = System.nanoTime();
double time = (t2 - t1) / 1e6d;
if (request.method().equals("GET")) {
Logs.info(String.format("GET " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), response.code(), response.headers(), response.body().charStream()));
} else if (request.method().equals("POST")) {
Logs.info(String.format("POST " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body(), response.code(), response.headers(), response.body().charStream()));
} else if (request.method().equals("PUT")) {
Logs.info(String.format("PUT " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body().toString(), response.code(), response.headers(), response.body().charStream()));
} else if (request.method().equals("DELETE")) {
Logs.info(String.format("DELETE " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITHOUT_BODY, request.url(), time, request.headers(), response.code(), response.headers()));
}
return response;
}
}
and the result :
POST [some url] in 88,7ms
ZoneName: touraine
Source: Android
body: retrofit.client.OkClient$1#1df53f05 <-request.body().toString() gives me this, but I would like the content string
Response: 500
Date: Tue, 24 Feb 2015 10:14:22 GMT
body: [some content]
Nikola's answer did not work for me. My guess is the implementation of ByteString#toString() changed. This solution worked for me:
private static String bodyToString(final Request request){
try {
final Request copy = request.newBuilder().build();
final Buffer buffer = new Buffer();
copy.body().writeTo(buffer);
return buffer.readUtf8();
} catch (final IOException e) {
return "did not work";
}
}
From the documentation of readUtf8():
Removes all bytes from this, decodes them as UTF-8, and returns the string.
which should be what you want.
I tried to comment on the correct answer from #msung, but my reputation isn't high enough.
Here's modification I did to print RequestBody before making it a full request. It works like a charm. Thanks
private static String bodyToString(final RequestBody request){
try {
final RequestBody copy = request;
final Buffer buffer = new Buffer();
copy.writeTo(buffer);
return buffer.readUtf8();
}
catch (final IOException e) {
return "did not work";
}
}
EDIT
Because I see there is still some people interested by this post, here is the final version (until next improvement) of my log interceptor. I hope it will save some of you guys's time.
Please note that this code is using OkHttp 2.2.0 (and Retrofit 1.9.0)
import com.squareup.okhttp.*;
import okio.Buffer;
import java.io.IOException;
public class LoggingInterceptor implements Interceptor {
private static final String F_BREAK = " %n";
private static final String F_URL = " %s";
private static final String F_TIME = " in %.1fms";
private static final String F_HEADERS = "%s";
private static final String F_RESPONSE = F_BREAK + "Response: %d";
private static final String F_BODY = "body: %s";
private static final String F_BREAKER = F_BREAK + "-------------------------------------------" + F_BREAK;
private static final String F_REQUEST_WITHOUT_BODY = F_URL + F_TIME + F_BREAK + F_HEADERS;
private static final String F_RESPONSE_WITHOUT_BODY = F_RESPONSE + F_BREAK + F_HEADERS + F_BREAKER;
private static final String F_REQUEST_WITH_BODY = F_URL + F_TIME + F_BREAK + F_HEADERS + F_BODY + F_BREAK;
private static final String F_RESPONSE_WITH_BODY = F_RESPONSE + F_BREAK + F_HEADERS + F_BODY + F_BREAK + F_BREAKER;
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
long t1 = System.nanoTime();
Response response = chain.proceed(request);
long t2 = System.nanoTime();
MediaType contentType = null;
String bodyString = null;
if (response.body() != null) {
contentType = response.body().contentType();
bodyString = response.body().string();
}
double time = (t2 - t1) / 1e6d;
if (request.method().equals("GET")) {
System.out.println(String.format("GET " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), response.code(), response.headers(), stringifyResponseBody(bodyString)));
} else if (request.method().equals("POST")) {
System.out.println(String.format("POST " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), stringifyRequestBody(request), response.code(), response.headers(), stringifyResponseBody(bodyString)));
} else if (request.method().equals("PUT")) {
System.out.println(String.format("PUT " + F_REQUEST_WITH_BODY + F_RESPONSE_WITH_BODY, request.url(), time, request.headers(), request.body().toString(), response.code(), response.headers(), stringifyResponseBody(bodyString)));
} else if (request.method().equals("DELETE")) {
System.out.println(String.format("DELETE " + F_REQUEST_WITHOUT_BODY + F_RESPONSE_WITHOUT_BODY, request.url(), time, request.headers(), response.code(), response.headers()));
}
if (response.body() != null) {
ResponseBody body = ResponseBody.create(contentType, bodyString);
return response.newBuilder().body(body).build();
} else {
return response;
}
}
private static String stringifyRequestBody(Request request) {
try {
final Request copy = request.newBuilder().build();
final Buffer buffer = new Buffer();
copy.body().writeTo(buffer);
return buffer.readUtf8();
} catch (final IOException e) {
return "did not work";
}
}
public String stringifyResponseBody(String responseBody) {
return responseBody;
}
}
With a current version of OkHttp, you can use the HTTP Logging Interceptor and set the level to BODY
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.setLevel(Level.BODY);
With this you cannot granularily configure the output for different HTTP methods, but it also works for other methods that might have a body.
Here an example showing the output of a PATCH request (minimally redacted):
--> PATCH https://hostname/api/something/123456 HTTP/1.1
Content-Type: application/json-patch+json; charset=utf-8
Content-Length: 49
Authorization: Basic YWRtaW46c2VjcmV0Cg==
Accept: application/json
[ { "op": "add", "path": "/path", "value": true }]
--> END PATCH (xx-byte body)
As you can see, this also prints out the headers and as the documentation states, you should really take care:
The logs generated by this interceptor when using the HEADERS or BODY levels have the potential to leak sensitive information such as "Authorization" or "Cookie" headers and the contents of request and response bodies. This data should only be logged in a controlled way or in a non-production environment.
You can redact headers that may contain sensitive information by calling redactHeader().
logging.redactHeader("Authorization");
logging.redactHeader("Cookie");
Version that handles requests with or without a body:
private String stringifyRequestBody(Request request) {
if (request.body() != null) {
try {
final Request copy = request.newBuilder().build();
final Buffer buffer = new Buffer();
copy.body().writeTo(buffer);
return buffer.readUtf8();
} catch (final IOException e) {
Log.w(TAG, "Failed to stringify request body: " + e.getMessage());
}
}
return "";
}
Kotlin version :
val buf = okio.Buffer()
requestBody.writeTo(buf)
Log.d("AppXMLPostReq", "reqBody = ${buf.readUtf8()}")
Create a Separate new class and implement Intercepter.
override fun intercept(chain: Interceptor.Chain): Response {
val request: Request = chain.request()
var logInfo = ""
val requestBody=loggerUtil.getRequestBody
return response
}
yourOkHttpClient.addInterceptor(yourInstance)
GetRequestBody
var requestContent = ""
val requestBody = request.body
val buffer = Buffer()
if (requestBody != null) {
requestBody.writeTo(buffer)
}
val contentType = requestBody?.contentType()
val charset: Charset =
contentType?.charset(StandardCharsets.UTF_8) ?:StandardCharsets.UTF_8
if (buffer.isProbablyUtf8()) {
requestContent = buffer.readString(charset)
}
Extension to find Whether buffer data is UT8 format
fun Buffer.isProbablyUtf8(): Boolean {
try {
val prefix = Buffer()
val byteCount = size.coerceAtMost(64)
copyTo(prefix, 0, byteCount)
for (i in 0 until 16) {
if (prefix.exhausted()) {
break
}
val codePoint = prefix.readUtf8CodePoint()
if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) {
return false
}
}
return true
} catch (_: EOFException) {
return false // Truncated UTF-8 sequence.
}
}
Are we supposed to call .close() on the buffer object ? or use the try-with-resources statement ?
private String getBodyAsString(Request request) throws IOException {
try(var buffer = new Buffer()) {
request.body().writeTo(buffer);
return buffer.readUtf8();
}
}

Categories

Resources