2

I have an app in which the app flow is different from Android standard:

  • Standard: Pressing a button on activity A and showing loading on this activity while loading the data and building the views of activity B and only then show the next activity. (Call AsyncTask on activity A, when finished send data and call activity B)

  • What I need: Press the button on activity A, go to activity B, and while showing loading, update the view of Activity B behind the loading. (Move from activity A to B and there call AsyncTask while showing loading. When AsyncTask is finished update the view while the loading is on front thus showing the view update process)

Problem: This will make the loading icon stop.. and sometimes show the unresponsive dialog (due to the long taking of the operation) and using the UI thread to change a view that is being built.

I've read some questions about the same matter: Android: how to update UI dynamically?

But none has provided a generic answer.

If you answer is "that's not the recommended behaviour" i'll agree... but these are requirements so i'll really appreciate some help :-D

Community
  • 1
  • 1
neteinstein
  • 17,529
  • 11
  • 93
  • 123

4 Answers4

4

I first did this already a year ago in a project. I got this very same problem because I've tried to use an icon, and there were so much to be updated that the icon freezed sometimes, so I ended up using a Handler paired with AsyncTask. It's actually a very common pattern in pre-setup activities.

Those are the steps:

  1. Create a loading dialog, posting it to a Handler object. You could use something like this (a Loading dialog instead of a Toast, obviously).
  2. Start your AsyncTask doing your computations.
  3. Whenever the parts of the computation are ready, you publishProgress. Give flags to the code in order to tell it exactly which parts of the UI need to be build. You'll see the UI being build behind while your loading dialog is displaying and spinning.
  4. When everything is done, you dismiss the dialog in onPostExecute.

Below is a sample code that does exactly what you describe:

private void settingsAndPlaceTaskStart() {

    // loading dialog
    startLoading();

    new AsyncTask<Context, Integer, Void>() {
        @Override
        protected Void doInBackground(Context... context) {

            // check and prepare settings integrity
            Settings st = new Settings(context[0], false);
            st.checkPreferencesIntegrity();

            // set text for each view / paired object
            // this will take a long time
            do {
                // ... removed for clarity
                int code = ...;
                publishProgress(mObjects.getViewByCode(code));    
                while (mObjects.getNext());
            return null;
        }

        @Override
        protected void onPostExecute(Void void) {
            // finish stuff
        }

        @Override
        protected void onProgressUpdate(Integer... message) {
            prepareView(message[0]);
        }

    }.execute(getActivity());
}

Here the Loading does not suffer from any stutter while the progress is spinning in the dialog. The dialog stays in the foreground until the onPostExecute. You should dismiss the dialog in there, following the end of the task.

Community
  • 1
  • 1
davidcesarino
  • 16,160
  • 16
  • 68
  • 109
  • 1
    As you can see, this is actually from production code. I removed a few things for clarity and left others to show you the tasks taking a long time, doing stuff *AND* updating *lots* of parts of the UI. By the way, this should be all contained within the changing activity, because you can't manipulate UI elements of an activity (in this case, an stopped one) from another one (this in the foreground). – davidcesarino Mar 04 '12 at 14:41
  • You're welcome. Btw, there is a missing closing bracket on the do loop... an accident, I'm sure you noticed anyway... – davidcesarino Mar 08 '12 at 14:01
2

Let me make sure I have this right: Activity A is your first, B your second. You want a button on A that triggers 2 things: an async data load and a push of B on the Activity stack. This is all fine.

In "What I Need" you use the verb phrase "update the view in background". That is not possible - you can only update a view on the main UI thread. Also, once A's onPause() is called (triggered by the push of B), you really should consider A to be unavailable for processing. What you can do is load the data in the background, while B does its thing, and then A, in its onResume() can update its UI appropriately.

If I'm wrong and you have gotten A to do some UI work while B is presented, then good on you, but if your processing as a chunk is taking too long, then I guess you'll have to break it up into smaller chunks and schedule them bit by bit, as to not interrupt with the user's experience of B.

UPDATE

I'm not sure how you will accomplish working with the UI resources of Activity Y while within the confines of Activity X, especially if Y has not yet been onCreate()d. I don't know if it's possible to share layout resources between Activities, though it may be.

The link you posted above gets to it - I think the answer regarding using the Handler class is part of your solution. Also, read about Looper and Designing for Responsiveness. I think, in short, you will want to implement a Handler on your main thread which allows you to update the UI in little 100-200ms chunks (as suggested in Responsiveness), and, with high granularity, send messages into that Handler as you process little bits of data on a background thread. For example, if you're updating a ListView, you should be able to inflate one row at a time. Likewise if you're adding images to a GridView. Also, consider pre-loading resources, but not showing them, so that you can spend less time inflating when time is short.

This is as far as my thinking takes me at the moment, but I'm marking this as a favorite, because I think I'll have to do something similar in a few weeks. Keep us posted on what works for you. Good luck!

QED
  • 9,803
  • 7
  • 50
  • 87
2

A. Look to android market app (I'm not sure how it works in "play") - when you click on app - empty activity with only cycle bar is shown, and any other data shows after full downloading. Do you want the same behavior? If yes - the following will work:

You layout for B activity:

<RelativeLayout>
    <common_layout_of_your_view_that_covers_all_space />
    <layout_for_progress />
</RelativeLayout>

layout_for_progress can be any layout, and if you want to make it transparent - put special background.

You activity B than just start thread for background data loading:

onCreate(...) {
    //call super
    //set content view

    new Thread(new Runnable() {
        public void run() {
            while(!downloadComplete) {
                //ask some portion of data
                ActivityB.this.runOnUIThread(new Runnable(){
                    public void run() {
                        //update your main view
                    }
                });
            }
            findViewById(R.id.layout_for_progress).setVisibility(View.GONE);
        }
    }).start();
}
Jin35
  • 8,602
  • 3
  • 32
  • 52
1

You could start an asyncTask in onCreate and have it do all of your computations. At the same time you could display a loading bar. Then in onPostExecute you could update the views with the information you collected and shut off the progress bar

ByteMe
  • 1,436
  • 12
  • 15
  • This would be a nice approach.. the problem is, if you have many inflates of views onPostExecute / uiThread the loading icon will stop. I have to only create the entire screen AFTER the data is received as I only know what it will contain AFTER the data arrives. – neteinstein Feb 29 '12 at 19:26
  • so your problem is that inflating all of your views takes too long? – ByteMe Feb 29 '12 at 22:24
  • yes, building the screen takes to long... there's got to be a way to do this right. – neteinstein Mar 01 '12 at 00:07