Fixing the NetworkOnMainThread exception in your Android app with AsyncTask

As soon as you start doing long-running operations such as network calls or anything else that can take more than a few seconds in your Android application, you’ll come across one of the following problems:

  • The [Application] not responding (ANR) popup is shown while you run your application, and the The application may be doing too much work on its main thread. warning is shown in your Logcat traces.
  • The NetworkOnMainThreadException exception is launched as soon as you call anything over the network like an API, regardless of the duration of the call.

Why does the Android SDK complains when one of those things happens? Why does code that seems to be correct doesn’t work? Well, if your application stays frozen, your users could think it crashed. A few seconds is an eternity when you don’t have any feedback about what’s going on, and users are probably going to keep pressing buttons as a response, or close your application and never return. So, to prevent this, the Android SDK enforces minimum standards for responsiveness and display errors if those standards are not respected.

To understand why the application freezes, we must take a look at the architecture of the application. By default, your Android application has only one thread, called the main thread. All you code execute on this single thread, and a thread can only do one thing at a time.

So, if you call the network and there is a delay, everything stops while the application waits for an answer, freezing your UI completely. You won’t even be able to display a progress bar because nothing else can run. The timeline looks like:

Fortunately, you can create new threads, which allow you to do more than one thing at once:

For one-time operations that only takes a few seconds, the easiest way to manage this is to use the AsyncTask class available in the Android SDK. It will create a new thread for you that will do a specific task, and then notify you when that task is over so you can update your UI.

Here is an example of an AsycTask that calls the GitHub API to calculate the number of repository that contains the “Android” string. While the query is running, a progress bar is shown in the UI.

private class GithubAndroidRepositoryQueryTask extends AsyncTask<String, Integer, String> {

@Override
protected void onPreExecute() {
   super.onPreExecute();
   // Prepare the UI in the main UI thread before executing the task.
   mGitRepositoryProgressBar.setVisibility(View.VISIBLE);
}

@Override
protected String doInBackground(String... params) {
   // Do the operation on a new thread and returns the result. It calls the GitHub API to 
   // calculate the number of repository that contains the Android string
   return getAndroidRepositoryCountFromGitHub();
}

@Override
protected void onProgressUpdate(Integer... progress) {
   // Could be used to update the main UI thread with the progress of the background
   // operation.
}

@Override
protected void onPostExecute(String result) {
   // Receive the result of the operation done in the new thread in the main UI thread and
   // use it to update the UI.
   super.onPostExecute(result);
   mGitRepositoryProgressBar.setVisibility(View.INVISIBLE);
   mGitRepositoryTextView.setText(result);
}
}

And here is how you execute that task. In that case, it’s launched onCreate event of the Activity so the response is visible immediately in the layout, but it could be launched from anywhere else:

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.activity_main);
   mGitRepositoryTextView = (TextView) this.findViewById(R.id.github_repositories_tv);
   mGitRepositoryProgressBar = (ProgressBar) this.findViewById(R.id.github_repositories_pb);

   // Fires off an AsyncTask to get the number of Android repositories on GitHub and displays
   // it in the application.
   new GithubAndroidRepositoryQueryTask().execute();
}

When you’re working with threads of any kinds, remember that updating the UI should only be done on the main thread. The Android SDK doesn’t support updating the UI from other threads, so you’ll likely have random crashes and weird behavior if you attempt it.

Also, as a caveat, Google doesn’t recommend relying on showing a progress bar exclusively for long-running operations. They believe that users should be able to keep working in the application and that you should manage what’s going on in the backend.

If you have a simple application that has only a few simple cases it may work, but if you have complex interactions between your data you can sink a lot of time trying to make everything work nicely together and handling what happens if an operation fails.

Still, you can start with a simple AsyncTask and optimize as you go along. In most cases, it’s going to be a good starting point, but as soon you need to execute many tasks at once it won’t be enough. When you’re ready to go further with this, you should take a look at libraries such as RxJava to help you manage your tasks.

Your job will never be done

As a software developer, you have to become comfortable with the fact that your work is never truly done. The software you create will always be a work in progress. When you code that last feature or fix that last bug, another one will pop up soon enough.

Also, as you keep learning, you’ll find better patterns that you want to use, or new standards that you want to implement. If you don’t stop yourself, you’ll quickly go down the rabbit hole, only waking up after you’ve sunk many hours to refactor your whole code base. At some point, you have to let it go and ship anyway, because it’s never going to be perfect.

Good software is like a garden. You can pull all the weeds and clean up, but be quick to congratulate yourself because it will stay neat for approximately a day. After this, new weeds will have grown and you’ll have to start over again. Perfection is fleeting in software and in gardening: the best you can do is making sure it’s not overrun by weeds and bugs.

Likewise, there are new languages, frameworks and tools coming out all the time, and you need to keep learning to stay up to date. You don’t have to jump on every bandwagon: many skills can be learned as you need them. On the other hand, changes like new browsers and OS versions will force you to adapt even if you would have preferred working on a new feature instead.

It’s humbling to let go if you’re a recovering perfectionist. When you started out, small problems could be “solved”, and a class could be “completed”, but real life is more complex than this. Life will throw you curveballs, and you won’t be able to reach inbox zero. It won’t come easy at first, but stay mindful that you’re doing your best, but can’t do it all. You’ll learn to be at peace with it, but even this is always a work in progress.

Simple code is beautiful

Despite the large number of languages and tools that will get thrown at you if you mention web development (Ruby! React! Node.js! AngularJS! Gulp! Python!), the simplest web application you can create includes HTML, CSS and a bit of JavaScript for the interactivity. You don’t even need to setup a web server: just open it in your browser or host it with any cheap shared hosting and it just works.

Anyone can maintain this application with just a text editor, a web browser and some experimentation. It’s a great way to learn how the web works, and it is a good solution for a few pages showing static information or a little JavaScript calculator.

Unfortunately, it all goes downhill from there as soon as you need to save the state of the application or introduce some other form of server-side logic. Also, your application will soon turn into an unmanageable mess if you have to update many pages every time there is a small change.

Once you get started on this path and have chosen a web development stack, you’ll end up depending on many tools, technologies and frameworks for your developments process. In many cases, you HAVE to do this: most stacks don’t include everything, and you have to choose libraries according to the needs of your project. Unfortunately, every new bit that you add needs to be updated and can break on you. You need to find a balance between the time saved now by using a tool or automating a process, and the time waster further down the road if it breaks.

It’s easy to add dependencies, but you have to make sure it’s worth it for your project (see the left-pad debacle): a good dependency is something that doesn’t make sense to code yourself. You should never trust logic that is core to your application and that cannot easily be replaced to a third-party tool. That means that using a framework for two-way data binding or even just for handling AJAX calls is the proper thing to do, but a chat application that would outsource the chatting part is taking on a lot of risk.

The same goes for the code itself. When you’re thinking of trying out a fancy new language feature, you need to ask yourself if the immediate productivity gain is worth it. Will it make maintenance and readability harder for everyone working with the code (including you in 6 months)?

You don’t need to use each and every feature of a language. If you come up with a new way to do things at every possible occasion, it’s going to be very hard to introduce new people to your project. Using a small subset of the language makes it easier to understand what’s going on, even if it’s boring. You can experiment in your own learning projects, not in code meant for production.

Likewise, I like to keep my IDE as simple as possible, and work with a small number of tools. Many plugins are a great boost to productivity, but you don’t want to depend on all of them working perfectly to be able to do something. Also, if you deviate too much from the standard working environment for your technology or at your organization, it’s a lot harder to collaborate with other people. A simple environment meant you’re able to start working quickly, without spending hours reinstalling software to setup your environment.

If you’re just working on projects for a few months and handing them off to someone else to maintain, or if you’re just doing small projects to learn, it may not be as obvious, but those are all things that can come back and bite you. Your code will most likely be around for many years, and your tools must last that long or you’ll find yourself scrambling to find an alternative quickly.