Tech & Engineering Blog

Conditionally Retry Asynchronous Network Calls on Android Using Retrofit 2

Johnny Tordgeman

May 8, 2018

Categories: Engineering, Technology and Engineering

Conditionally Retry Asynchronous Network Calls on Android Using Retrofit 2

Developing a mobile application comes with its own set of issues that you need to take care of. One of those issues is the possibility of a lousy network connection. An unstable network can cause your app’s API calls to fail, resulting in a poor user experience if not handled right.

One way to handle such case is to use a Retrofit/OKHTTP feature called Interceptors to retry the failed request. There is one catch though  — Interceptors are designed to be as simple and logic-less as possible, and if you do decide to add more logic to an Interceptor, and make a call to a server to get some data to decide what to do next based on that — you would have to do it synchronously.

Since one of the SDKs I use is callback based and cannot be called synchronously, I had to come up with a different approach. While searching the usual StackOverflow threads looking for a direction, I came across this awesome post by Pallav Ahooja called Easily Retrying Network Requests on Android With Retrofit 2.

The post demonstrates how to achieve a retry using a new object called RetryableCallback which implements OKHTTP’s callback object and add some logic to it to handle retry. This approach works great if you want to base your retry on counters, meaning if a request failed retry it X times. I needed to base my retry logic on data from a callback based function and as such i needed to make some changes to the solution, which I’m going to list right here…

The APIHelper class

The first class i created is APIHelper, which exposes two functions:

  • enqueueWithRetry — replaces Retrofit’s enqueue function for async methods.
  • isCallSucess — the logic that determines if a server call was successful or not.

The code for this class is as follows:

<span class="token keyword">package</span> <span class="token namespace">retro<span class="token punctuation">.</span>sampleapp<span class="token punctuation">.</span>com</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token namespace">android<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Log</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Call</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Callback</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Response</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">APIHelper</span> <span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> <span class="token keyword">void</span> <span class="token function">enqueueWithRetry</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token keyword">final</span> <span class="token class-name">Callback</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> callback<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        call<span class="token punctuation">.</span><span class="token function">enqueue</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RetryableCallback</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>call<span class="token punctuation">)</span> <span class="token punctuation">{</span>

            <span class="token annotation punctuation">@Override</span>
            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFinalResponse</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Response</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"APIHelper"</span><span class="token punctuation">,</span> <span class="token string">"reached onFinalResponse"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                callback<span class="token punctuation">.</span><span class="token function">onResponse</span><span class="token punctuation">(</span>call<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>

            <span class="token annotation punctuation">@Override</span>
            <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFinalFailure</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Throwable</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"APIHelper"</span><span class="token punctuation">,</span> <span class="token string">"reached onFinalFailure"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                callback<span class="token punctuation">.</span><span class="token function">onFailure</span><span class="token punctuation">(</span>call<span class="token punctuation">,</span> t<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">boolean</span> <span class="token function">isCallSuccess</span><span class="token punctuation">(</span><span class="token class-name">Response</span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">int</span> code <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token function">code</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">return</span> <span class="token punctuation">(</span>code <span class="token operator">>=</span> <span class="token number">200</span> <span class="token operator">&&</span> code <span class="token operator"><</span> <span class="token number">400</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

Looking at the function, we can see the real magic happens inside the RetryableCallback class which is in charge of handling the call and its responses.

The RetryableCallback class

The heart of this method is the RetryableCallback class, which its implementation is as follows:

<span class="token keyword">package</span> <span class="token namespace">retro<span class="token punctuation">.</span>sampleapp<span class="token punctuation">.</span>com</span><span class="token punctuation">;</span>

<span class="token keyword">import</span> <span class="token namespace">android<span class="token punctuation">.</span>util<span class="token punctuation">.</span></span><span class="token class-name">Log</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">java<span class="token punctuation">.</span>io<span class="token punctuation">.</span></span><span class="token class-name">IOException</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Callback</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Call</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> <span class="token namespace">retrofit2<span class="token punctuation">.</span></span><span class="token class-name">Response</span><span class="token punctuation">;</span>

<span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">RetryableCallback</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> <span class="token keyword">implements</span> <span class="token class-name">Callback</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> <span class="token punctuation">{</span>

    <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> TAG <span class="token operator">=</span> <span class="token class-name">RetryableCallback</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">.</span><span class="token function">getSimpleName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">;</span>

    <span class="token keyword">public</span> <span class="token class-name">RetryableCallback</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>call <span class="token operator">=</span> call<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onResponse</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Response</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token class-name">APIHelper</span><span class="token punctuation">.</span><span class="token function">isCallSuccess</span><span class="token punctuation">(</span>response<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>response<span class="token punctuation">.</span><span class="token function">code</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">403</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"RetryableCallback"</span><span class="token punctuation">,</span> <span class="token string">"reached 403, check condition and retry if needed"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                <span class="token keyword">try</span><span class="token punctuation">{</span>
                    authHandler<span class="token punctuation">.</span><span class="token function">getUpdatedToken</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ResponseCallback</span><span class="token punctuation">(</span>newToken<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// an example of an async call with callback</span>
                        <span class="token annotation punctuation">@Override</span>
                        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onSuccess</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
                            <span class="token function">retryCall</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// if the response succeeded retry the original call</span>
                        <span class="token punctuation">}</span>
                        <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFailure</span><span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                            <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"RetryableCallback"</span><span class="token punctuation">,</span> <span class="token string">"token refresh failed :("</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
                        <span class="token punctuation">}</span>
                    <span class="token punctuation">}</span>
                <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">IOException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                    <span class="token comment">// do something with the exception</span>
                <span class="token punctuation">}</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
            <span class="token function">onFinalResponse</span><span class="token punctuation">(</span>call<span class="token punctuation">,</span> response<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// no need to do any retry, pass the response and the call to the final callback</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>

    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFailure</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Throwable</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"RetryableCallback"</span><span class="token punctuation">,</span> t<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>t<span class="token punctuation">.</span><span class="token function">getMessage</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">contains</span><span class="token punctuation">(</span><span class="token string">"Server"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// if error contains some keyword, retry the request as well. This is just an example to show you can call retry from either success or failure.</span>
            <span class="token function">retryCall</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span>
            <span class="token function">onFinalFailure</span><span class="token punctuation">(</span>call<span class="token punctuation">,</span> t<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// if not, finish the call as a failure</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFinalResponse</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Response</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// to be overriden by calling class</span>

    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFinalFailure</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">T</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Throwable</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// to be overriden by calling class</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">retryCall</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        call<span class="token punctuation">.</span><span class="token function">clone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">enqueue</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// clone the original call and enqueue it for retry</span>
    <span class="token punctuation">}</span>

<span class="token punctuation">}</span>

Once everything is in place, you can use the new API using the following code snippet:

<span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AppVersion</span><span class="token punctuation">></span></span> appVersionCall <span class="token operator">=</span> apiInterface<span class="token punctuation">.</span><span class="token function">getVersion</span><span class="token punctuation">(</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// a sample call using Retrofit.</span>
<span class="token class-name">APIHelper</span><span class="token punctuation">.</span><span class="token function">enqueueWithRetry</span><span class="token punctuation">(</span>appVersionCall<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">Callback</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AppVersion</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onResponse</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AppVersion</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Response</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AppVersion</span><span class="token punctuation">></span></span> response<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"callbackResponse"</span><span class="token punctuation">,</span> <span class="token string">"Yay it worked! response is: "</span> <span class="token operator">+</span> response<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token annotation punctuation">@Override</span>
    <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">onFailure</span><span class="token punctuation">(</span><span class="token class-name">Call</span><span class="token generics"><span class="token punctuation"><</span><span class="token class-name">AppVersion</span><span class="token punctuation">></span></span> call<span class="token punctuation">,</span> <span class="token class-name">Throwable</span> t<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token class-name">Log</span><span class="token punctuation">.</span><span class="token function">d</span><span class="token punctuation">(</span><span class="token string">"callbackResponse"</span><span class="token punctuation">,</span> <span class="token string">"oh now, an error :("</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

And there you have it. A relatively simple way of performing retry for an asynchronous Retrofit call based on pre-defined conditions. The RetryableCallback class can be even further enhanced by adding time outs, secondary rules etc. making your request retry logic smarter and more efficient 🙂

Special thanks to Guy Bary and Yaron Schwimmer for all their help with this post!

Spread the Word