Request timed out! But you didn’t expect it, did you? Of course not because while you were writing the app and testing the code you were always on your blazing fast WiFi connection or a 4G LTE network. But in the real world, all your users don’t have access to such a network connection at all times.

Would you want them to suffer?
Would you want your app to behave in a strange manner in that case?
Would you want to create a bad user experience?
Would you want to have an unsatisfied customer?
Do you always test all your features depended on network requests for flaky connections?

If your answer to all the above questions is “No”, then I have a simple solution for you, that’ll make your life a lot easier to test your features for these scenarios and have a failure mechanism in place to be more responsive to the user.


Implementation

The solution is pretty straight forward, create an Interceptor for your network requests and delay or fail them. Let’s look at how to implement this interceptor with OkHttp and provide easy access to it through a UI to all the stakeholders that are responsible or are willing to test your application.

public class NetworkThrottlingInterceptor implements Interceptor {

private static boolean failRequests;

private static final AtomicLong failRequestCount = new AtomicLong(Long.MAX_VALUE);

private static boolean delayAllRequests;

private static long minRequestDelay;

private static long maxRequestDelay;

private final Random random = new Random(4);

@Override
public Response intercept(Chain chain) throws IOException {
if(failRequests) {
long failC = failRequestCount.get();
if (failC > 0) {
failRequestCount.compareAndSet(failC, failC-1);
throw new IOException("FAIL ALL REQUESTS");
}
}
if(delayAllRequests) {
long delay = minRequestDelay;

if(minRequestDelay != maxRequestDelay) {
delay = (long) ((random.nextDouble() * (maxRequestDelay - minRequestDelay)) + minRequestDelay);
}
long end = System.currentTimeMillis() + delay;
long now = System.currentTimeMillis();
while(now < end) {
try {
Thread.sleep(end - now);
} catch (InterruptedException e) {
// Nothing to do here, timing controlled by outer loop.
}
now = System.currentTimeMillis();
}
}
try {
return chain.proceed(chain.request());
} catch (Exception ex) {
if (BuildConfig.DEBUG) {
Request request = chain.request();
Log.e("NETWORK", "Exception during request", ex);
Log.e("NETWORK", "Request was to: " + request.url().toString());
}
throw ex;
}
}

public static void delayAllRequests(long minRequestDelay, long maxRequestDelay) {
if(minRequestDelay == 0 && maxRequestDelay == 0) {
delayAllRequests = false;
} else {
NetworkThrottlingInterceptor.minRequestDelay = minRequestDelay;
NetworkThrottlingInterceptor.maxRequestDelay = maxRequestDelay;
delayAllRequests = true;
}
}

public static void failNextRequests(long failCount) {
if(failCount == 0) {
failRequests = false;
}
else {
failRequestCount.set(failCount);
failRequests = true;
}
}
}

The logic in the interceptor is quite simple, but for verbosity, I’ll still explain it here:

We have two scenarios that we deal with, through this interceptor:

  1. Delay a response for a given network request.
  2. Fail next n network requests.

Scenario 1: Delay a response for a given network request

For this scenario, we set a minimum and maximum value and for a given request we find a random value between this range and ask the thread to sleep for that time.

You can create a different combination of settings that you give access to, through your UI, for example:

  1. Good Network (min = 0, max = 0)
  2. Slow Network (min = 1 second, max =5 seconds)
  3. Very Slow Network (min = 5 seconds, max = 10 seconds)

By default, your app can always be set to work on the Good Network. And then the users can switch to other networks as and when needed.

Scenario 2: Cause next n network requests to fail

For this scenario, we maintain a fail counter that keeps decreasing on each request until it becomes zero. And while this happens we just throw an IO Exception to cause the network request to fail, you can even cause a failure by other means like creating a fake failure response with some status code 5xx.


Interceptor Integration

Injection of this interceptor is quite simple you can add it at the time of configuration of your OkHttp client instance like so.

OkHttpClient okHttpClient = new OkHttpClient.Builder()
.addNetworkInterceptor(new NetworkThrottlingInterceptor())
.build();

Interceptor Exposure

In our app, we do it through a debug screen that contains a simple Spinner widget that holds all these values that we can select and modify the interceptor configuration at runtime, the code for which looks like this

private void setupNetworkThrottler() {
spNetworkInterceptor = findViewById(R.id.sp_network_throttle);
ArrayAdapter<String> networkSpeedTypeAdapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line,
new String[]{"Good network", "Moderate network 1-5s delay", "Poor network 5-10s delay"});
spNetworkInterceptor.setAdapter(networkSpeedTypeAdapter);
spNetworkInterceptor.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0: NetworkThrottlingInterceptor.delayAllRequests(0,0);
break;
case 1: NetworkThrottlingInterceptor.delayAllRequests(1000, 5000);
break;
case 2: NetworkThrottlingInterceptor.delayAllRequests(5000, 10000);
break;
}
}

@Override
public void onNothingSelected(AdapterView<?> parent) {
NetworkThrottlingInterceptor.delayAllRequests(0,0);
}
});
}

If you have other interesting solutions that you can leverage this interceptor for, please share them with me on Twitter where you can find me as @droidchef.

I’d like to give credits to Jonathan Farris, who originally came up with this solution.

Advertisements

This is a trick I learnt very recently from a Senior Android Developer at my company and now I feel miserable about all the time I had spent waiting for Gradle builds to test my changes while writing Android Apps.

Here is a typical scenario every android developer would have come across at least once during their development lifetime. You have a List of items that you want to show in a ListView or RecyclerView.

Below is our beloved onBindViewHolder method that binds your model to your views of the RecyclerView.

    @Overridepublic void onBindViewHolder(ViewHolder holder, final int position) {final String name = values.get(position);        holder.txtHeader.setText(name);        holder.txtFooter.setText("Footer: " + name);    }

Now lets say you wanted to change the text color for every 3rd element in the list. So the code would look something like this

@Override
public void onBindViewHolder(ViewHolder holder, final int position) {

final String name = values.get(position);
holder.txtHeader.setText(name);
if (position % 3 == 0) {
holder.txtHeader.
setTextColor(Color.GREEN);
}
holder.txtFooter.setText("Footer: " + name);
}

Then you’d click on Run and wait for build to finish and see your changes, right?

Copied Image from Anand Shekhar Roy’s post on Speeding up your gradle builds.

Now you’d be thinking what other way could we achieve this?

Welcome Android Studio Debugger, yes we need no external plugin or tool to achieve the above task and more over, we won’t even have to build the project, you heard me, we will by pass Gradle 🙂 Here’s how !

Step 1 — We need to define a Run Config

This run config would allow us to launch our app and attach the debugger from android studio to it, alternatively you can also attach it to an already running process by your hand.

Click on Run -> Edit Configurations

On the Top-Left Corner of the Dialog, Click on the + icon and choose Android App

Now give it a name, I like to call it Run-Only, but you can call it anything.

Choose the module which has your app code, in the below screenshot it is called app.

Important step : 
In Installation Options, Choose Nothing for Deploy.
In Launch Options, Choose Default Activity
In Before Launch, Remove the Gradle-aware Make task.

So the config should look like this screenshot below

Now you can apply and save this config. It should be selected automatically by Android Studio now, if not just select it from the list.

Now apply a Breakpoint some close before the line you’d like to test a change, in our case we’ll put it where we set the text.

Right Click on the Break Point and Uncheck Suspend. Please write back to me on Twitter, if you have never seen this before, I’d very happy to know I showed you something new 🙂

As soon as you uncheck this dialog, you would see this dialog expand with a lot of options.

What we are interested right now is in Evaluate and log. We will write a statement there to test a change in our RecyclerView’s Item. Click on the small blue colored icon next to the dropdown arrow of the Evaluate and log text input box to expand it to a bigger editor and add your testing statement, like this and click Ok and then click on Done.

Now Click on Debug icon with Run-Only Config Selected and see the Magic.

The App Should start from your default activity and you should see the changes applied there, also if you pay close attention to the IDE, on the very bottom you’ll just see one task running that says Launching Activity.

Would love to hear your experiences when you try this trick !! My Twitter Handle is @droidchef