Okhttp response callbacks on the main thread

AndroidMultithreadingOkhttp

Android Problem Overview


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.

Android Solutions


Solution 1 - Android

> 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.

Solution 2 - Android

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

            }
        });
 }

Solution 3 - Android

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

Solution 4 - Android

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

Solution 5 - Android

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

                }
            }
        }
    });
 }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMichaelView Question on Stackoverflow
Solution 1 - AndroidJake WhartonView Answer on Stackoverflow
Solution 2 - AndroidMichaelView Answer on Stackoverflow
Solution 3 - AndroidMarcos CasagrandeView Answer on Stackoverflow
Solution 4 - AndroidManish GoyalView Answer on Stackoverflow
Solution 5 - AndroidAdam DavisView Answer on Stackoverflow