Monday, December 23, 2013

Android, except for, NetworkOnMainThreadException's!

I published an app to the app store several months ago. All was working as designed. It's a network-based app with a good deal of remote communication that takes place. Not much came of it as it was just a first stab at Android mobile apps, and these days you really have to get the word out if you want any attention at all, which is a project in itself. I'm proud of the app, and still think it's a neat idea, so I tinker with it from time-to-time.

During development, I was testing mainly on my Motorola Atrix somewhere back around Android 2.something. I've been burned once or twice after upgrades, but usually nothing major. Certainly nothing that required updates throughout the entire code-base.

So, I recently upgraded to a Motorola Atrix HD. Quite a powerful device. Certainly not an iPhone killer (or even a Galaxy killer), but tons of power for the price. Its deficiencies are mostly in the UI, but that's another post. Anyways, I went to the app store to check up on my app. It loaded fine the first time, but the next time I attempted to show it to someone, the app failed. I think the new device is somewhere around 4.something. Oh well, I thought, I guessed it needed a bit of a refresh.

Recently, I sat down to look into the errors. I updated my dev environment, plugged in the phone, and started the app in debug mode. Turns out, the app was failing due to NetworkOnMainThreadException's. Somewhere between 2.x and 4.x, they made it mandatory for network communication to only take place off the main thread. Fine. It was a recommendation back in the 2.x days, but not mandatory, so I heeded the warning only when absolutely needed. My application uses an HTTP library to make REST calls to the server. This way, when I get around to learning to develop a client for iOS, I can just plug it in. Brilliant, right? Maybe. There really is no silver bullet. But it helps if you built it correctly.

The solution, AsyncTask. I actually used it in a few places where I needed asynchronous actions to smooth out usability. It's not the only solution, but it's a pretty good one. The real problem? Finding all the places where the client communicates with the server, and convert it into AsyncTask. This is exacerbated by the instances where I do not need asynchronous communication. 

Here is an example of how to use AsyncTask with a ProgressDialog and everything:

AsyncTask<String, String, String> task = new AsyncTask<String, String, String>() {
   ProgressDialog progressDialog;
   boolean error = false;
   @Override
   protected String doInBackground(String... args) {
      try {
         // Do a bunch of stuff
      } catch(Exception e) {
         Log.e("me", "There was an error doing all that stuff.", e);
         error = true;
      }
      return null;
   }
   @Override
   protected void onPreExecute() {
      progressDialog = ProgressDialog.show(context, "Connecting", "Wait while I do some stuff...", true, false);
   }
   @Override
   protected void onPostExecute(String result) {
      progressDialog.dismiss();
      if (error) {
         Toast.makeText(context, "There was an error doing all that stuff.", Toast.LENGTH_SHORT).show();
         finish();
      }
   }
};
task.execute("");

It's much simpler than it looks. They provide a bunch of hooks to attempt to provide a lot of flexibility. The idiom followed above is to create a new instance of abstract AsyncTask, and to override the abstract methods in-place, rather than to create a sub-class, etc... All the heavy-weight stuff is in the doInBackground() method. ProgressDialog's can be manipulated in onPreExecute() and onPostExecute(), which is good because ProgressDialog's are finicky about who can open and close them.

Well, I hope this helps somebody. As I said, there were other solutions presented by the Google team, but this seems to cover all my needs, for now.


No comments: