Show ProgressDialog while loading layout with setContentView
Asked Answered
G

3

12

this is my scenario: I've got a login screen that opens another activity. In the Activity I simply have:

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_details);
}

The layout is kind of heavy, cause it is made of some fragments, and takes about 1.5 seconds to load. Now, how can I display a ProgressDialog while setContentView finishes inflating the layout? I've tried with AsyncTask by putting the setContentView in the doInBackground, but of course that cannot be done, as the UI can be updated from the UI thread only. So I need to call setContentView in the UI thread, but where do I have to show/dismiss the ProgressDialog?

I appreciate your help.

Fra.

EDIT: I followed @JohnBoker's previous suggestion, this is the code I have now:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_empty_layout);
    new ContentSetterTask().execute("");
}

private class ContentSetterTask extends AsyncTask<String, Void, Void> {

    public ProgressDialog prgDlg;

    @Override
    protected void onPreExecute() {
        android.os.Debug.waitForDebugger();
        prgDlg = ProgressDialog.show(MultiPaneActivity.this, "", "Loading...", true);

    }

@Override
protected Void doInBackground(String... args) {
    android.os.Debug.waitForDebugger();
    ViewGroup rootView = (ViewGroup)findViewById(R.id.emptyLayout);
    LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View inflated = inflater.inflate(R.layout.activity_details, rootView);
    return null;
}

@Override
protected void onPostExecute(Void arg) {
    android.os.Debug.waitForDebugger();
    if (prgDlg.isShowing())
        prgDlg.dismiss();
    }
  }
}

The row

View inflated = inflater.inflate(R.layout.activity_details, rootView);

gives me the error:

06-27 16:47:24.010:   
ERROR/AndroidRuntime(8830): Caused by:android.view.ViewRoot$CalledFromWrongThreadException: 
Only the original thread that created a view hierarchy can touch its views.
Goggle answered 27/6, 2011 at 13:24 Comment(11)
What about inflating it manually in the doInBackground method?Diamine
Would be interesting to see your layout file, best to try and optimize that i think. Is the 1.5 seconds on the emulator or on the phone/tablet?Rubrician
Hi, it actually is about 1.5 seconds on the eeePad Transformer. How can I inflate it manually? The layout is rather long to be posted here, but it has about 10-12 linear layouts and one real fragment (for now).Goggle
Do you have one Layout (Relative, Linear, etc) on activity_empty_layout, right? What about inflating activity_details and passing null to root, so, after, you can use addView from the parent layout.Diamine
tried with: View inflated = inflater.inflate(R.layout.activity_details, null); rootView.addView(inflated); I still get the same error.Goggle
Did you tried adding the view on onPostExecute method?Diamine
Yes, I did. This is the basic code: @Override protected void onPostExecute(Void arg) { setContentView(inflated); if (prgDlg.isShowing()) prgDlg.dismiss(); } prgDlg is shown in onPreExecute. Now the layout gets properly inflated, even with setContentView. Problem is the ProgressDialog is frozen, maybe because it is running in the same thread as setContentView. So the problem remains the same.Goggle
the problem is you're inflating the view into your root view inside the background thread. you'll need to inflate then add it to your root view, can you do View inflated = inflater.inflate(R.layout.activity_details, null); then return that view to your onPostExecute where you can add it to your rootView ?Cookie
I have in doInBackground: inflated = inflater.inflate(R.layout.activity_details, null); and in onPostExecute: rootView.addView(inflated); addView, however, messes with the weightSum and the layout weights in my activity_details. setContentView works just the same but has no issues. The problem is the same, ProgressDialog is frozen: it appears before the loading, freezes itself and disappears once the layout has been inflated. This is because the inflation is done on the same thread as the one in which the ProgressDialog is created/destroyed. How can I make the ProgressDialog responsive?Goggle
tried playing with different layouts (Frame or Relative) on activity_details?Diamine
Since there is no way to show a non-blocking screen while inflating the layout, I will definitely change the layout in such a way that it will not require too much time to show up. Thank you anyway guys. Google should really try and focus on this kind of issues (and many other aspects of the Android programming I really don't like).Goggle
G
9

I decided to make a full answer here for future readers.

After several hours spent on the issue, I realized that the issue is I'm trying to do two things:

  1. inflating a layout, which is an operation that NEEDS to be made on the UI thread by design.
  2. showing a Dialog (ProgressDialog, actually, but this does not change the outcome), that can be done from the UI thread only, since Services can't show any Dialog.

So, as both calls are made from the UI thread (onCreate or AsyncTask makes no difference, it's still the UI thread), the first blocks the second one from showing up appropriately. Bottom line is: this problem can't be solved in Android right now. Let's hope we can get some better APIs for interacting with the UI, because the ones we've got kind of suck.

I'm going to solve this problem by changing the layout and making it lighter (if possible!). Thanks everyone!

Goggle answered 28/6, 2011 at 20:36 Comment(1)
You can manually inflate views on a separate thread if you use a HandlerThread. Basically the thread you inflate the views on requires a Looper.Jacquard
C
8

I had the same issue when creating a heavy view, what I did was put a linearlayout only in the xml file and called the setContentView on that, then i created the real view in the asynctask and added the view dymanically to the linearlayout.

This method seems to work and i was able to put a progress dialog up during the process.

Cookie answered 27/6, 2011 at 13:30 Comment(4)
yeah, thats seems like a perfect option!Diamine
you can look at developer.android.com/resources/articles/… at AsyncTask for the threading part, put the progress dialog showing in the onPreExecute, dismiss in onPostExecute. there's an example of inflating a view in #5292828Cookie
I already had the showing and dismissing in pre and post. Now I've added the inflation in doInBackground and that's the (obvious) error I got: 06-27 16:12:35.630: ERROR/AndroidRuntime(8077): Caused by: android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.Goggle
I have added some details to the first post, with the complete code and the error above. It appears I've made something different from what you suggested. :(Goggle
K
0

This is not a perfect solution, but so far the closest one to what I needed: We need some time between setting the progress dialog (or message view or whatever) visible and calling setContentView(), so that the dialog/message is actually displayed.

To achieve that, I start a Thread performing a short sleep(), then calling setContentView() in the UI thread.

Here is an example with a progress dialog:

private ProgressDialog  m_Hourglass = null ;
private Thread          m_LoadingThread = null ;

...

public void onCreate( Bundle savedInstanceState )
{
    super.onCreate( savedInstanceState );

    ...

    m_Hourglass = new ProgressDialog( this );
    m_Hourglass.setMessage( "Loading" );
    m_Hourglass.setIndeterminate( true );
    m_Hourglass.setCancelable( false );
}

public void SetMyView( View vvv )                   // Can be called from buttons.
{
    if( m_LoadingThread != null ) return;           // Avoid multiple press

    m_Hourglass.show();

    ////////////////////////////////////////////////////
    // Load in a thread, just to show the above message
    // before changing layout (because setContentView()
    // freezes the GUI.)
    m_LoadingThread = new Thread()
    {
            @Override
            public void run()
            {
                super.run();
                try                 {   sleep( 20 ); }
                catch (Exception e) {   System.out.println( e ); }
                finally             {   runOnUiThread( new Runnable()
                                        {
                                            public void run() 
                                            {
                                                SetMyViewThreaded();
                                            }
                                        });
                                    }
                m_LoadingThread = null ; // Now we can load again
            }
    };
    m_LoadingThread.start();
}

public void SetMyViewThreaded()
{
    setContentView( R.layout.some_gorgeous_layout );

    /////////////////////////////////////////////////////
    // Catch when to finally hide the progress dialog:
    ImageView iv            = (ImageView) findViewById( R.id.some_view_in_the_layout );
    ViewTreeObserver obs    = iv.getViewTreeObserver();      
    obs.addOnGlobalLayoutListener( new OnGlobalLayoutListener()
    {          
         @Override          
         public void onGlobalLayout()    // The view is now loaded
         {
             m_Hourglass.hide();
         }      
    });

    //////////////////////////////////////////////////////
    // Here we can work on the layout
    ...
}

My main issue with this is the sleep( 20 ), which is an approximation, and may not be enough on all machines. It works for me, but if you don't see the progress dialog, you probably have to increase the sleep duration.

Keto answered 22/9, 2014 at 21:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.