Okhttp response callbacks on the main thread - android

I have created a helper class to handle all of my http calls in my app. It is a simple singleton wrapper for okhttp that looks like this (I have omitted some unimportant parts):
public class HttpUtil {
private OkHttpClient client;
private Request.Builder builder;
...
public void get(String url, HttpCallback cb) {
call("GET", url, cb);
}
public void post(String url, HttpCallback cb) {
call("POST", url, cb);
}
private void call(String method, String url, final HttpCallback cb) {
Request request = builder.url(url).method(method, method.equals("GET") ? null : new RequestBody() {
// don't care much about request body
#Override
public MediaType contentType() {
return null;
}
#Override
public void writeTo(BufferedSink sink) throws IOException {
}
}).build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Request request, Throwable throwable) {
cb.onFailure(null, throwable);
}
#Override
public void onResponse(Response response) throws IOException {
if (!response.isSuccessful()) {
cb.onFailure(response, null);
return;
}
cb.onSuccess(response);
}
});
}
public interface HttpCallback {
/**
* called when the server response was not 2xx or when an exception was thrown in the process
* #param response - in case of server error (4xx, 5xx) this contains the server response
* in case of IO exception this is null
* #param throwable - contains the exception. in case of server error (4xx, 5xx) this is null
*/
public void onFailure(Response response, Throwable throwable);
/**
* contains the server response
* #param response
*/
public void onSuccess(Response response);
}
}
Then, in my main activity, I use this helper class :
HttpUtil.get(url, new HttpUtil.HttpCallback() {
#Override
public void onFailure(Response response, Throwable throwable) {
// handle failure
}
#Override
public void onSuccess(Response response) {
// <-------- Do some view manipulation here
}
});
onSuccess throws an exception when the code runs :
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the
original thread that created a view hierarchy can touch its views.
From my understanding, Okhttp callbacks run on the main thread so why do I get this error ?
** Just as a side note, I have created HttpCallback interface to wrap Okhttp's Callback class because I wanted to change the behaviour of onResponse and onFailure so I could unite the logic of handling failed responses due to i/o exception and failed responses due to server problems.
Thanks.

From my understanding, Okhttp callbacks run on the main thread so why do I get this error ?
This is not true. Callbacks run on a background thread. If you want to immediately process something in the UI you will need to post to the main thread.
Since you already have a wrapper around the callback you can do this internally in your helper so that all HttpCallback methods are invoked on the main thread for convenience.

As Jake Wharton suggested, I had to run the callbacks on the main thread explicitly.
So I wrapped the calls to the callbacks with Runnable like this:
private void call(String method, String url, final HttpCallback cb) {
...
client.newCall(request).enqueue(new Callback() {
Handler mainHandler = new Handler(context.getMainLooper());
#Override
public void onFailure(Request request,final Throwable throwable) {
mainHandler.post(new Runnable() {
#Override
public void run() {
cb.onFailure(null, throwable);
}
});
}
#Override
public void onResponse(final Response response) throws IOException {
mainHandler.post(new Runnable() {
#Override
public void run() {
if (!response.isSuccessful()) {
cb.onFailure(response, null);
return;
}
cb.onSuccess(response);
}
});
}
});
}

I know it's an old question, but recently I encountered the same issue. If you need to update any view, you will need to use runOnUiThread() or post the result back on the main thread.
HttpUtil.get(url, new Callback() { //okhttp3.Callback
#Override
public void onFailure(Call call, IOException e) { /* Handle error **/ }
#Override
public void onResponse(Call call, Response response) throws IOException {
String myResponse = response.body().string();
//Do something with response
//...
MyActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
//Handle UI here
findViewById(R.id.loading).setVisibility(View.GONE);
}
});
}
});

According to Retrofit documentation Callback methods are executed on UI thread by default until you provide a callback executor to Retrofit OR when using custom method return types using CallAdapterFactory

Michael's above response is a pretty good solution for having your callback happen in a calling Activity and I used it as a guide for my solution that I will outline below (solution in Kotlin);
Interface for HttpCallback
interface HttpCallback {
fun onResponse(response: Response)
fun onFailure(call: Call, e: IOException)
}
Implementation of the Interface. I pass the calling activity as a weak reference to perform UI updates.
class ImageUploadCallback(activity: WeakReference<Activity>) : HttpCallback {
val _activity = activity
override fun onResponse(response: Response) {
if(_activity.get() != null){
val activty = _activity.get()!!
activty.runOnUiThread(Runnable {
//Update the UI to your hearts content
});
}
}
override fun onFailure(call: Call, e: IOException) {
//Your implemtnation here
}
}
Calling HttpProxy class from the activity, passing the callback as a parameter
HttpProxy.UploadImage(
imageToUpload.name,
imageToUpload,
MEDIA_TYPE!!,
//our callback
ImageUploadCallback(WeakReference(this))
And finally, the code in the HttpProxy class that takes in the callback;
fun UploadImage(filename: String, sourceImageFile: File, mediaType: MediaType, callback: HttpCallback) {
//building up our request...and then calling
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback.onFailure(call, e)
}
override fun onResponse(call: Call, response: Response) {
response.use {
try{
callback.onResponse(response)
}
catch(e: Exception){
}
}
}
});
}

What i would do is extend okhttp callback and call failure / response on main thread so caller don't have to care about posting them on main thread.
Call call;
call.enqueue(new OkHttpCallbackOnMain() {
#Override
void onCallFailure(Call call, IOException e) {
// update UI
}
#Override
void onCallResponse(Call call, Response response) {
// update UI
}
});
abstract class OkHttpCallbackOnMain implements Callback {
abstract void onCallFailure(Call call, IOException e);
abstract void onCallResponse(Call call, Response response));
#Override
public void onFailure(#NonNull Call call, #NonNull IOException e) {
getMainHandler().post(() -> onCallFailure(call, e));
}
#Override
public void onResponse(#NonNull Call call, #NonNull Response response) {
getMainHandler().post(() -> onCallResponse(call, response));
}
private Handler getMainHandler() {
return new Handler(Looper.getMainLooper());
}
}

Related

How to stop enqueued call after first execution?

I'm very new to android development. Trying to connect some site and get data from it. I have this function called only from onCreate in the main activity. Every time I turn virtual Android phone left or right I see new "run()" strings in EditText and requests in Wireshark. How to stop that properly?
Tried call.cancel() and mClient.dispatcher().cancelAll() inside OnResponse
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
}
retrofit supports enqueue canceling, and it works great.
And i think if you will try to run this code - your client enqueues would be stoped
protected void Load(String url) {
Request request = new Request.Builder()
.url(url)
.build();
Call<Response> mCall = mClient.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
if (call.isCanceled()) {
Log.e(TAG, "request was cancelled");
}
else {
Log.e(TAG, "other larger issue, i.e. no network connection?");
}
}
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
});
mCall.cancel();
}
I don't know you project structure and what kind of patterns you using(MVP, MVVM or else), but in simple, this code can be improved by returning Call
protected void Load(String url): Call<Response>
And then you can hadle you request status, and if it longer than 5 seconds for example, you call call.cancel() and request is stopping.
onCreate is called every time configuration changes (for example you rotate your phone/emulator). For more info: https://developer.android.com/guide/components/activities/activity-lifecycle
You can save your response to prevent new request on every onCreate. Something like this:
MainActivity {
private Response savedResponse;
onCreate() {
if (savedResponse == null) {
Load(url)
}
}
}
and in your onResponse save the response:
#Override
public void onResponse(Call call, final Response response) throws IOException {
if (response.isSuccessful()) {
savedResponse = response; // <----
mHandler.post(new Runnable() {
#Override
public void run() {
mEdit.setText(mEdit.getText() + "run()\n");
}
});
}
}
However, correct way would be to separete network calls/requests from activity lifecycle and load data somewhere else (Service, WorkManager, ...)

getting String response from OKhttp

I want to get string JSON response to the main activity after making the connection I know it is running in different thread so please help me how I can get it and then return it from this method;
public class MakeNetworkConnection {
private String mBaseUrl;
private String mApiKey;
private String mContentType;
private String mJsonResponce;
public MakeNetworkConnection(String baseUrl, String apiKey, String contentType) {
mBaseUrl = baseUrl;
mApiKey = apiKey;
mContentType = contentType;
}
public String startNetworkConnection() throws IOException {
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder().url("http://content.guardianapis.com/sections?api-key=1123456").build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
mJsonResponce=response.body().string();
}
}
});
return mJsonResponce;
}
}
The problem here is about understanding how asynchronous tasks work. When you are calling client.newCall(request).enqueue(new Callback(), it will run in the background thread (off the main thread) and control will pass to the next line which is return mJsonResponce; And thus, it will always return null.
What you should do is to pass a callback method which will be called when the response is successful. You can create an interface to return the result:
public interface NetworkCallback {
void onSuccess(String repsonse);
void onFailure();
}
Pass an object of this interface while making the network request and call appropriate method when network request finishes.
One more thing you will have take care is that OkHttp doesn't return the response on the main thread so you will have to return the response on UI/main thread if you are going to update any UI. Something like this will work.
#Override
public void onResponse(Call call, Response response) throws IOException {
if(response.isSuccessful()){
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
//return response from here to update any UI
networkCallback.onSuccess(response.body().string());
}
});
}
}
Instead of using calls asynchronously, you could use it synchronously with execute().
public String startNetworkConnection() throws IOException {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("http://content.guardianapis.com/sections?api-key=1123456")
.build();
return client.newCall(request)
.execute()
.body()
.string();
}
after advice from Rohit Arya i did the following:
public class OkHttpUtil {
public interface OKHttpNetwork{
void onSuccess(String body);
void onFailure();
}
public void startConnection(String url, final OKHttpNetwork okHttpCallBack) throws IOException {
OkHttpClient client=new OkHttpClient();
Request request=new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
#Override
public void onFailure(Call call, IOException e) {
}
#Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()){
okHttpCallBack.onSuccess(response.body().string());
}
}
});
}
}
in the MainActvity i did the following :
OkHttpUtil okHttpUtil=new OkHttpUtil();
try {
okHttpUtil.startConnection("http://content.guardianapis.com/sections?api-key=8161f1e9-248b-4bde-be68-637dd91e92dd"
, new OkHttpUtil.OKHttpNetwork() {
#Override
public void onSuccess(String body) {
final String jsonResponse=body;
runOnUiThread(new Runnable() {
#Override
public void run() {
//show the response body
Toast.makeText(MainActivity.this,jsonResponse, Toast.LENGTH_SHORT).show();
}
});
}
#Override
public void onFailure() {
//do something
}
});
} catch (IOException e) {
e.printStackTrace();
}
Change this line
mJsonResponce=response.body().toString();
To
mJsonResponce=response.body().string();
Hope help you.

Retrofit Add custom object in Error callback. onFailure

hi i am using retrofit my callback is as follow
#Override
public void onResponse(final Call<T> call, Response<T> response) {
if (response.isSuccessful()) {
passing this to my view
} else {
// as this failed other then 200 retroCallback.onFailure(call, new Throwable(""));
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
}
so in this how i can pass my ErrorBean instead of Throwable anyway we can pass custom model in onFailure ? as my server giving me response in some formate i want to pass that format .. i am using retrofit 2.1.0
You can subclass Throwable and pass additional object using composition.
public class ErrorBean extends Throwable {
public ErrorPayload payload = null;
public ErrorBean(ErrorPayload payload) {
this.payload = payload;
}
}
Then, in onError:
#Override
public void onFailure(Call<T> call, Throwable t) {
retroCallback.onFailure(call, t);
if (t instanceof ErrorBean) {
// do your stuff here
((ErrorBean)t).payload.text;
}
}
AFAIK,, Retrofit's onFailure is used for handling errors like no internet connection.
To handle the error response from your Server, Error response, I mean response from Server with 4xx status code but with some JSON response for client to handle it.
Say, you are getting this error structure from Server:
{
statusCode: 409,
message: "Email address already registered"
}
This error will be captured in onResponse(...). To handle this, create your
public class ErrorBean {
private int statusCode;
private String message;
public ErrorBean() {
}
public int status() {
return statusCode;
}
public String message() {
return message;
}
}
Create a simple ErrorHandler util:
public class ErrorUtils {
public static ErrorBean parseError(Response<?> response) {
Converter<ResponseBody, ErrorBean> converter =
ServiceGenerator.retrofit()
.responseBodyConverter(ErrorBean.class, new Annotation[0]);
ErrorBean error;
try {
error = converter.convert(response.errorBody());
} catch (IOException e) {
return new ErrorBean();
}
return error;
}
}
And finally,
...
call.enqueue(new Callback<SuccessResponse>() {
#Override
public void onResponse(Call<SuccessResponse> call, Response<SuccessResponse> response) {
if (response.isSuccessful()) {
// use response data and do some fancy stuff :)
} else {
// parse the response body …
ErrorBean error = ErrorUtils.parseError(response);
// … and use it to show error information
// … or just log the issue like we’re doing :)
Log.d("error message", error.message());
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
// there is more than just a failing request (like: no internet connection)
}
});
Hope you got the point..!!!

How to do unit testing for retrofit2 callbacks?

I want to do an unit test that verifies if function1() or function2() were called. I haven't work with callbacks before, can you give me any idea about how to do it?
public void sendData(HttpService service, Document userData) {
Call<String> call = service.updateDocument(getId(), userData);
call.enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
function1(response.code());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
function2();
}
});
}
I couldn't try, but it should work. Maybe you have to fix generic type
casting errors like mock(Call.class);.
#Test
public void should_test_on_response(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Response response = null;
invocation.getArgumentAt(0, Callback.class).onResponse(onResponseCall, response);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function1
}
#Test
public void should_test_on_failure(){
Call<String> onResponseCall = mock(Call.class);
doAnswer(invocation -> {
Exception ex = new RuntimeException();
invocation.getArgumentAt(0, Callback.class).onFailure(onResponseCall, ex);
return null;
}).when(onResponseCall).enqueue(any(Callback.class));
sendData(....);
// verify function2
}

Trying to implement the okHttp Call & Callback scenario. Upon completion there is no onPostExecute to modify the UI

Previously I would simply pass the response.body().string() to OnPostExecute() method of an asyncTask to update TextViews of the UI. okHttp doesn't seem to have this option. Where is the equivalent or how to achieve this?
//Call to server to echo MySQL query
private void usersInZone() {
if (!isNetworkAvailable()) return;
rotate(true);
OkHttpClient client = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add("userId", String.valueOf(userId))
.add("channel", String.valueOf(subport + port))
.build();
Request request = new Request.Builder()
.url(SITE_URL + "inZone.php")
.post(formBody)
.build();
Call users = client.newCall(request);
users.enqueue(caller);
}
//okHttp Callback to parse echo response
caller = new Callback() {
#Override
public void onFailure(Call call, IOException e) {
if (RS != null) RS.makePoor(true);
}
#Override
public void onResponse(Call call, Response response) throws IOException {
LOG.l(String.valueOf(call));
if (response.isSuccessful()) {
try {
JSONObject data = new JSONObject(response.body().string());
updateFields(getString(R.string.inzone) + data.getInt("total") + "]", String.valueOf(data.getInt("zone")));
} catch (JSONException e) {
LOG.l("JSONException" + e);
}
} else if (RS != null) RS.makePoor(true);
}
};
//UI textViews i'm needing to update with the echoed results
private void updateFields(String total, String channelTotal){
subtitle.setText(total);
tvusercount.setText(channelTotal);
}
Where is the equivalent or how to achieve this?
OkHttp's onResponse() is not called on main thread. If you want to update UI in onResponse you must delegate this task to UI thread. Using Handler and posting Runnable is one way of doing so.
The callback is not called on the main thread. If you need to update the UI, a simple way to do that is to create a class that implemented Callback and post the items to the UI like so:
public abstract class UIMainCallback implements Callback {
private static final Handler mUIHandler = new Handler(Looper.getMainLooper());
abstract void failure(Request request, IOException e);
abstract void response(Response response);
#Override
public void onFailure(final Request request, final IOException e) {
mUIHandler.post(new Runnable() {
#Override
public void run() {
failure(request, e);
}
});
}
#Override
public void onResponse(final Response response) throws IOException {
mUIHandler.post(new Runnable() {
#Override
public void run() {
response(response);
}
});
}
}
Then your Caller can just implement the UIMainCallback instead of the interface.

Categories

Resources