ProgressDialog in Android AsyncTask does not display at the right time
Asked Answered
P

2

2

So I've got these long running server calls over what is basically OData going on in my Android application. The consumer of the calls uses .execute().get() to wait for the response from the network thread (I know the proper way is to make the whole thing asynchronous and callback-based, but the app cannot function in any way without this data, and completely rearchitecting it to work in that way doesn't seem to provide any benefits).

So I found many snippets online where using the ProgressDialog in combination with onPreExecute() and onPostExecute() as shown in the code sample below can be used to show a progress dialog while the AsyncTask executes. I'm using exactly the samples provided, but what happens is that the call starts, it waits for the network transaction, then very quickly flashes and hides the progress dialog. It can sit for whole seconds waiting on the transaction and I know for a fact that it's waiting in the doInBackground(), but the dialog just won't pop up until the very end, making it effectively useless.

In the code below the DoEvents() bit is basically just a very short sleep. I've tried with and without it and there doesn't seem to be a difference, but it seemed worth trying.

class GetFromServerTask extends AsyncTask<String, Void, String>
    {
        private Context context;
        private ProgressDialog dialog;

        public GetFromServerTask(Context ctx) {
            context = ctx;
            dialog = new ProgressDialog(ctx);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            dialog.setMessage("Loading...");
            dialog.show();
            DoEvents();
        }

        @Override
        protected String doInBackground(String... parms) {
            if(InOfflineMode)
                return "notdeserializable";

            String url = parms[0]; 
            HttpURLConnection urlConnection = null;
            try {
                URL typedUrl = new URL(url);
                urlConnection = (HttpURLConnection) typedUrl.openConnection();

                //Add Authorization token
                if(InDebugMode) {
                    urlConnection.addRequestProperty("AuthToken", AuthToken);
                } else {
                    urlConnection.addRequestProperty("Authorization", "Bearer " + AuthToken);
                }
                urlConnection.addRequestProperty("Accept", "application/json");

                DoEvents();
                InputStream in = new BufferedInputStream(urlConnection.getInputStream());
                byte[] contents = new byte[in.available()];

                int bytesRead = 0;
                String strContents = ""; 
                while((bytesRead = in.read(contents)) != -1){ 
                    strContents += new String(contents, 0, bytesRead);
                    DoEvents();
                }

                if(strContents.startsWith("<HTML>"))
                    return "Error: Received unexpected HTML when connecting to service.  Make sure you are not connected to a WIFI that requires authentication.";

                return strContents;
            } catch(UnknownHostException hex) {
                return "Error: Could not find server address.  Make sure you are connected to the internet.  If you just changed connections (ie: turning WIFI on or off) it make take a minute to refresh";
            }
            catch(Exception ex) {
                String msg = "Error: " + ex.getClass().getName() + ": " + ex.getMessage();
                Log.e("TE", msg);
                return msg;
            } finally {
                if(urlConnection != null)
                    urlConnection.disconnect();
            }
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            if(dialog != null && dialog.isShowing())
                dialog.dismiss();
            DoEvents();
        }
    }

I've also tried the slightly different version suggested elsewhere on SO (shown below) with the same exact results:

protected void onPreExecute() {
    dialog=ProgressDialog.show(context, "", "Loading...", true, false);
    super.onPreExecute();
}

I've also tried taking the ProgressDialog out of the AsyncTask all together and showing it "outside" the task, as shown below. In this case it doesn't even appear.

ProgressDialog dialog = ProgressDialog.show(ServerAccessLayer.m_context, "", "Loading...", true, false);
String retVal = new GetFromServerTask(ServerAccessLayer.m_context).execute(url).get();
dialog.dismiss();

return retVal; 
Pore answered 3/6, 2013 at 20:44 Comment(4)
What happens if you follow the fix described here? (#11943633)Manard
@Ken Wolf: Same behavior. Can see the UI response (ie: a spinner loading with data pulled from the server) occurring before the progress dialog quickly pops and then vanishes.Pore
Your fix as shown by taking it outside the task won't work because execute returns immediately. This means it will call dismiss immediately. To do that you would call show before execute and call dismiss in onPostExecute.Antigua
Also, the sleeps won't help because to display the dialog you need to go into the drawing part of the event loop- that won't happen until you finish the onPostExecute or onPreExecute functions.Antigua
A
3

Ok, your problem is your .get(). .get is a blocking call. This means you won't return to the event loop (the code in the Android Framework that calls onCreate, onPause, event handlers, onPostExecute, onPreExecute, etc) until after it returns. If you don't return to the event loop, you won't ever go into the drawing code, and you won't display the progress dialog. If you want to show the dialog, you need to rearchitect your app to actually use the task asynchronously. Side note- if you're calling .get() like that on your UI thread, your entire app will freeze up and look broken. That's why they forced people to not do network IO on the UI thread in the first place.

Antigua answered 3/6, 2013 at 21:41 Comment(1)
Unfortunately it looks like this is the way I have to go. Thanks a ton Alejandro, but due to my architecture this guy is right, my app depends on having data and since there seems to be no way to reliably block for a read without hosing the UI thread this is the way I have to go. I never thought I'd actually be nostalgic for winsock, but I totally am.Pore
S
1

As @Gabe-Sechan says, the .get() is the reason why your UI freezes and your ProgressDialog doesn't show as you want. On the other hand, I disagree with him when he says:

If you want to show the dialog, you need to rearchitect your app to actually use the task asynchronously.

If you simply remove your .get() and leave you dialog showing in the onPreExecute() and dismissing in the onPostExecute(String result), you'll get what you are looking for: a ProgressDialog shown while the task is executed.

UPDATE:

I started a question based on the sense/usefulness of using the get() method of Android's AsyncTask class.

Septempartite answered 3/6, 2013 at 22:31 Comment(3)
The rearchitecture comes in that his app is written to assume that he always has the data from the async task. With the get call he has that. Without it, he doesn't- the app would be active without the data being ready. That may (or may not) require additional changes depending on how his code works.Antigua
What I mean is that, as he wants to make the user wait for the AsyncTask to finish, that's what he achieves simply by removing the get() and using the ProgressDialog with no extra coding nor rearchitecture.Septempartite
Not based on what I read of his comment. Right now he's calling get, then he has a bunch of code after that which uses the result. That won't work if he removes the get, he'll have to fix it.Antigua

© 2022 - 2024 — McMap. All rights reserved.