-1

EDIT 1:

This is the code that I have right now, trying to offload the heavy work on a separate thread.

protected override void OnCreate(Android.OS.Bundle bundle)
{
    // Set our view from the "main" layout resource
    SetContentView(Resource.Layout.Main);
}
protected override void OnResume()
{
   base.OnResume();

ThreadPool.QueueUserWorkItem(_ => BuildInterfaceForConnectedUser());
}

private void BuildInterfaceForConnectedUser()
{
    RunOnUiThread(() => { UpdateInterface();});
}

The ability to display a progress bar while background work is in progress has been asked many times, like here or here.

My problem is that I'm building my main activity content dynamically based on incoming data. I'm downloading data during a splash activity so this is easy to run it as a background task (because no UI access in involved). On the other end, building the content could take several seconds and I'd like to inform the user of the progress.

Basically, there is a lot of work in OnResume(). Pseudo code:

protected override void OnResume()
{
     base.OnResume();

     UpdateInterface(); // Build the content dynamically
}

During all this time the main activity is not showing.

How can I make the main activity to be displayed blurred in the background with a progress bar while the final content is built ?

It is started like the following from a "Login" Activity:

Intent intent = new Intent(this, typeof(MainActivity));
if(Intent.Extras != null) intent.PutExtras(Intent.Extras);
StartActivity(intent);

All the samples I've seen so far are not working because they rely on a button click where the activity is already built.

  • why don't you download the contents after starting main activity. i.e show splash screen, start main activity and at the same time it starts loading content while showing a progress bar from main activity and it will be easy to blur as you are already in this activity – ruben Nov 27 '18 at 08:50
  • I may be wrong but you need some kind of event to trigger the whole process once the Activity UI is already displayed. Otherwise because the UI thread is blocked, the old activity remains displayed. –  Nov 27 '18 at 09:48
  • Generally to download data that is network operation you need to do in a separate thread. You can use asynctask in main activity or framework like volley – ruben Nov 27 '18 at 09:59

2 Answers2

1

Basically, there is a lot of work in OnResume().

There should not be a lot of work in onResume(). Start this work on a seperate thread and then inform the UI thread about the results.

In Java/Kotlin this could be easily done with AsyncTask or you could use a traditional Thread+ Handler's.

Edit:

An equivalent approach using Java and AsyncTask.

public class Activity extends Activity{ 
 public void onCreate(){
   super(/*...*/);
   setContentView(splash_layout_id);
 }

 public void onResume(){
   super(/*...*/);
   task.execute();
 }

private AsynkTask task = new AsynkTask(){

   void doInBackground(){
     // this runs in the background
     Object data;

     while(data = fetchingData() != null){
      // this calls onProgressUpdate()
      publishProgress(data);
     }
   }

   void onProgressUpdate(Object progress) {
     // this runs on UI thread    
     updateUiRelativeToProgress(progress);
   }

   void onPostExecute(Object data) {
     // this runs on UI thread and called when doInBackground() returns 
     updateUi(data);
   }
 };

} 
Themelis
  • 4,048
  • 2
  • 21
  • 45
  • I've tried to use an async task but it makes no difference, the UI is not displayed. A progress bar is not necessarily what I need. I've seen applications using some kind of animation on UI elements beeing refreshed. –  Nov 27 '18 at 10:06
  • Sorry I am not getting the not displayed part so just to clarify... You've `setContentView()` in `onCreated()`, you've started your `AsyncTask`and while it's doing its work you have implemented `onProgressUpdate()` and still nothing is displayed? – Themelis Nov 27 '18 at 10:11
  • I hope these apis are kind of same in `C#` because I have no idea about Xamarin. – Themelis Nov 27 '18 at 10:12
  • The fact is this, you have something that takes a lot of work in the Activity and that work involves updating the UI. Where this involves a database, network, files or whatever, it should be done in its own thread. – Themelis Nov 27 '18 at 10:18
  • in Xamarin, I've set SetContentView(Resource.Layout.Main) during OnCreate(). The async task is started during OnResume. See my updated question. –  Nov 27 '18 at 10:23
  • Thanks! `BuildInterfaceForConnectedUser()` is supposed to happen in the background? Because I see that you use a thread from a pool, but the code is run in the UI thread. – Themelis Nov 27 '18 at 10:33
  • Anyway you're right, at least the `Resource.Layout.Main` should be shown no matter what... – Themelis Nov 27 '18 at 10:34
  • I need to update the user interface so the code needs to be enclosed in RunOnUiThread. The unmodified main layout is not displayed. Too bad. –  Nov 27 '18 at 10:39
  • Yes I asked that because I have no idea about `UpdateInterface()`s implementation. Some people write an `UpdateInterface()`but the method does more from what its name intent, like background tasks. – Themelis Nov 27 '18 at 10:42
  • I think I may need to implement a [Skeleton Screen](https://blog.iamsuleiman.com/stop-using-loading-spinner-theres-something-better/) –  Nov 27 '18 at 10:44
  • Whatever you do, you need to update your UI *while the data is being downloading* and not after the data is downloaded. Is there something that *reports back to your UI* when some data is fetched while the thread is still fetching other data? – Themelis Nov 27 '18 at 10:47
  • In `AsyncTask`'s case this is done by calling its `onProgressUpdate()`, which lets you run code on the UI thread *while the asynctask is still working in the background* (not after it's finished). – Themelis Nov 27 '18 at 10:49
  • I took this way to let the activity to be displayed then updated. Thanks a lot. –  Nov 28 '18 at 15:04
0

How I usually do this is quite simple actually.

Since ProgressDialog is deprecated in API- 26, I tend to use ProgressBar to do this instead

  1. Define a Global field as follows:

    ProgressBar progressBar;
    
  2. Add a ProgressBar to your View via code into the TopMost layout which should always be a RelativeLayout, Something like the following :

      RelativeLayout layout = *Assignment*; // Assign your main relative layout here.
      RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(100, 100);
      relativeParams.AddRule(LayoutRules.CenterInParent);
      progressbar= GetProgressBar(this)
      layout.addView(progressbar,params);
    

    Where GetProgressBar is a generic static method something like this:

      public static ProgressBar GetProgressBar(Activity _activity)
        {
            var progressBar = new ProgressBar(_activity, null, Android.Resource.Attribute.ProgressBarStyleLarge);              
            progressBar.Visibility=ViewStates.Visible;  //To show ProgressBar
            progressBar.Visibility=ViewStates.Gone;     // To Hide ProgressBar
            return progressBar;
        }
    
  3. Then Once you have done this disable the entire view using this:

    Window.SetFlags(WindowManagerFlags.NotTouchable, WindowManagerFlags.NotTouchable);
    
  4. Create an async Task that you want to run when your UpdateInterface method is called;

    private async void DoATask()
    {
       progressBar.Visibility=ViewStates.Visible; 
       await UpdateInterface();
       progressBar.Visibility=ViewStates.Gone; 
    }
    

    Note: UpdateInterface() method should be a System.Threading.Tasks.Task and not a Void for this to work because void cannot have a definition for awaiter to it.

  5. In your OnResume or OnCreateRun this in UIThread

     protected override void OnResume()
     {
        base.OnResume();
         _activity.RunOnUiThread(DoATask);
     }
    

Hope this helps and works fine for you revert in case of queries

FreakyAli
  • 13,349
  • 3
  • 23
  • 63
  • Why a TopMost RelativeLayout and not LinearLayout ? –  Nov 28 '18 at 08:54
  • Because if you use a topmost linear layout you cannot use place a view inside it that is aligned to the center of your screen like a loader usually is – FreakyAli Nov 28 '18 at 09:01