24

I've looked into other questions, blogs and the documention and can't seem to find the right answer to my needs. I have two activities, A and B. When I start activity B (from A) I want it to open instantly and then load all the content while showing a progress bar, instead of only opening the activity when the content is loaded, making it seem like it froze for two seconds. An exemple would be the Youtube app or the Play Store.

That's what i got:

Button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent goB = new intent(ActivityA.this, ActivityB.class);
            startActivity(goB);
        }
    });

This is the activity I'm loading:

public class ActivityB extends AppCompatActivity implements OnDateSelectedListener, OnMonthChangedListener {

    private static final DateFormat FORMATTER = SimpleDateFormat.getDateInstance();

    @Bind(R.id.calendarView) MaterialCalendarView widget;
    @Bind(R.id.textView) TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_c_calendar);
        ButterKnife.bind(this);
        widget.setOnDateChangedListener(this);
        widget.setOnMonthChangedListener(this);
        textView.setText(getSelectedDatesString());

    }

    @Override
    public void onDateSelected(@NonNull MaterialCalendarView widget, @Nullable CalendarDay date, boolean selected) {
        textView.setText(getSelectedDatesString());
    }

    private String getSelectedDatesString() {
        CalendarDay date = widget.getSelectedDate();
        if (date == null) {
            return "No Selection";
        }
        return FORMATTER.format(date.getDate());
    }

    @Override
    public void onMonthChanged(MaterialCalendarView widget, CalendarDay date) {

    }

}

I'm not an expert, so detailed explanations will be welcomed.

Note: What I'm loading in the activity is this calendar: https://github.com/prolificinteractive/material-calendarview

Question: How to load setContentView() on the background?

Update: I followed Hitesh Sahu's advice and now I only have one activity with one container that gets replaced for each fragment, I'm assuming the way to load the xml content in the background will be the same for a fragment and an activity, but if there is any difference please do mention.

Rafael
  • 6,091
  • 5
  • 54
  • 79
John Sardinha
  • 3,566
  • 6
  • 25
  • 55
  • "When I start activity B (from A) I want it to start instantly and then load all the content while showing a progress bar, instead of having a delay after clicking the button making it seem like activity A froze for a second." – John Sardinha Apr 03 '16 at 12:48
  • when u start an activity, it will start instantly by default – Mohd Asif Ahmed Apr 03 '16 at 12:49
  • That's not what's happening by default here, my second activity has a heavy library implemented in it, when I click the button to start that activity it takes about one second to open. I want it to open instantly and then load all the content instead of only opening it when the content is loaded – John Sardinha Apr 03 '16 at 12:54
  • 1
    if you are doing background task then use AsyncTask Class in your B Activity – Mohd Asif Ahmed Apr 03 '16 at 13:04
  • Can you help me doing that ? – John Sardinha Apr 03 '16 at 18:27
  • 1
    Can you please tell us which library you are using and if it is open sourced? Sometimes when using a library, you need to initialise it and the place you initialise it can have an impact on the loading time – Simon Apr 26 '16 at 09:29

9 Answers9

9

Possible solution

  • From "I am not an expert" my guess is you might be creating new activity on every screen switching and possibly not clearing older one or reusing existing activity. Again this is my guess I have not seen your code but I have seen developers doing this kind of mistakes .Soon your App's performance will degrade over time and your app will end up eating memory.

    How to fix this :- Replace activities with fragment switching fragment is relatively faster than switching activities plus you can reuse them (more about this).

  • You might be doing some heavy task on Main Ui thread look for choreographer logs like - skipped n number of frames app may be doing too much work on main thread

    How to fix this:- already explained in other answers.

Community
  • 1
  • 1
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
  • 2
    Yea you guessed correctly, This is probably one of those basic optimization things that no one even mentions because it's too obvious so thanks for doing so. – John Sardinha Apr 28 '16 at 12:00
5

You need to use AsyncTask for loading data and RelativeLayout for displaying Rounding Circle in ActivityB.

First initialize variable

private RelativeLayout mRelativeLayout;

and in onCreate of your activity execute AsyncTask like this

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mRelativeLayout = (RelativeLayout) findViewbyID(R.id.loadingCircle);

    new PrepareData().execute();
}

put this AsyncTask class in ActivityB

private class PrepareData extends AsyncTask<Void, Void, Void> {

    protected void onPreExecute(Void param) {
        // THIS WILL DISPLAY THE PROGRESS CIRCLE
        mRelativeLayout.setVisibility(View.VISIBLE)


    }

    protected Void doInBackground(Void... param) {

        // PUT YOUR CODE HERE TO LOAD DATA

        return null;
    }

    protected void onPostExecute(Void param) {
        // THIS WILL DISMISS CIRCLE
        mRelativeLayout.setVisibility(View.GONE)
}

and in XML include this layout

In order to get the ProgressBar without dialog is to use this

<RelativeLayout
    android:id="@+id/loadingCircle"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone"
    android:gravity="center" >

<ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:indeterminate="true" />
</RelativeLayout>

and set the visibility of the this RelativeLayout in onPreExecute and onPostExecute respectively like I have shown in example.

UPDATE:

you need to update your Ui in UiThread like this

runOnUiThread(new Runnable() {
 @Override
 public void run() {

 //put the code here that is giving exception

    }
});
Atiq
  • 14,435
  • 6
  • 54
  • 69
  • Could you update it for a ProgressBar? Or is it used in the same way? – John Sardinha Apr 26 '16 at 11:33
  • I have already included method for `ProgressDialog` it will display progress dialog (Moving circle ) on activity start and will dismiss it as soon as you finish loading data, you need to put the code of loading data in `doInBackground` method as I mentioned above – Atiq Apr 26 '16 at 11:39
  • Yes I know, I mean ProgressBar, as for a circular progress bar outside of a Dialog, that is simply in the activity, you can see exactly what I mean by opening the youtube app or the Instagram app – John Sardinha Apr 26 '16 at 11:47
  • This almost worked, I moved everything from `onCreate` into `doInBackground` including the `setContentView(...)` since the thing that's expensive is the calendar loading, it instantly opened the activity but as the calendar was ready to be shown it crashed. Here's the error: `java.lang.RuntimeException: An error occured while executing doInBackground()` and `Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.` – John Sardinha Apr 30 '16 at 09:58
  • No don't move `setContentView(R.layout.activity_main);` in do in background, just try as I have shown you sample – Atiq Apr 30 '16 at 10:06
  • I did, it only opens de activity after it's loaded. It's the inflation of the calendar that takes time – John Sardinha Apr 30 '16 at 10:09
  • see my update, you need to do some debugging pal, to check which part of `AsyncTask` is working and which is not – Atiq Apr 30 '16 at 10:11
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/110709/discussion-between-anders-and-kurlicue). – Atiq Apr 30 '16 at 10:23
5

Your activity B should have AsyncTask for loading data and then displaying it. Also you can load some default data in view inside of OnPreExecute()

@Override
protected void onCreate(Bundle savedInstanceState) {
    setupViews();
}

private void setupViews(){
    someView1 = (TextView) findViewById(R.id.some_view_id1);
    someView2 = (TextView) findViewById(R.id.some_view_id2);
    someView3 = (ImageView) findViewById(R.id.some_view_id3);
    progressBar = (ProgressBar) findViewById(R.id.progress_bar_id);
    new LoadDataForActivity().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

private class LoadDataForActivity extends AsyncTask<Void, Void, Void> {

String data1;
String data2;
Bitmap data3;

    @Override
    protected void onPreExecute() {
        progressBar.setVisibility(View.VISIBLE);
    }
    @Override
    protected Void doInBackground(Void... params) {
        data1 = loadData1();
        data2 = loadData2();
        data3 = loadData3();
    }

    @Override
    protected void onPostExecute(Void result) {
        someView1.setText(data1);
        someView2.setText(data2);
        someView3.setImageBitmap(data3);
        progressBar.setVisibility(View.GONE);
    }

}
Wackaloon
  • 2,285
  • 1
  • 16
  • 33
  • It isns't working, I added `Looper.prepare();` and returning null to `doInbackground` and it doesn't get called, I placed a toast in it to check – John Sardinha Apr 30 '16 at 12:31
  • Try to post AsyncTask execution after OnCreate methor, for example runOnUiThread(new Runnable() { @Override public void run() { new LoadDataForActivity().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);}) – Wackaloon May 05 '16 at 11:35
4

You could use an AsyncTask.

Eg : Add this in your activity page(outside onCreate()).

class OnLoading extends AsyncTask<Void,Void,Void>{

ProgressDialog pd;
@Override
protected void onPreExecute() {
    super.onPreExecute();
    pd=new ProgressDialog(Main2Activity.this);
    pd.setTitle("Title");
    pd.setMessage("Message");
    pd.setCancelable(false);
    pd.show();
}

@Override
protected Void doInBackground(Void... params) {
    //
    //Do all your loading stuff here
    //
    return null;
}

@Override
protected void onPostExecute(Void aVoid) {
    super.onPostExecute(aVoid);
    pd.dismiss();
}

@Override
protected void onProgressUpdate(Void... values) {
    super.onProgressUpdate(values);
}
}    

This class is executed by calling

new OnLoading().execute();

On executing this code the first function executed will be onPreExecute->doInBackGround->onPostExecute.. Progress update can be used to show some progress from the doInBackGround function by calling

publishProgress(value);

Here first the ProgressDialog will be shown. Provide the code to load the datas etc in the doInBackground. As the name implies anything you write in this function will be executed in the background. After this function completes execution, the progress dialog will be dismissed in the onPostExecute().

Akhil Soman
  • 2,077
  • 2
  • 17
  • 30
1

Just put this code:

Intent goB = new intent(ActivityA.this, ActivityB.class);
startActivity(goB);

In A's onCreate. Then create a ProgressDialog like the one shown here: http://www.tutorialspoint.com/android/android_progressbar.htm

This will show the progress of B loading. You'll call

progress.setProgress(int)

in B's onCreate, after doing something.

For example:

public class B extends Activity {
    public void onCreate(Bundle bundle) {
        progress=new ProgressDialog(this);
        progress.setMessage("Downloading Music");
        progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        progress.setIndeterminate(true);
        progress.setProgress(0);
        progress.show();

        //do something
        progress.setProgress(1);
        //do something
        progress.setProgress(2);
        //do something different
        progress.setProgress(3);
    }
    .....

EDIT

Here is how you can do it using a ProgressBar:

  • add a ProgressBar to B layout
  • get a reference to this ProgressBar in B's onCreate (using findViewById()
  • update your ProgressBar with setProgress(int) everytime you want(as shown in the first part of the question for ProgressDialog)
  • hide your ProgressBar using your ProgressBar.setVisibility(View.GONE)
Osama Aftab
  • 1,161
  • 9
  • 15
Andre99
  • 61
  • 10
1

Sounds like you are doing everything (including the heavy loading stuff) in the onCreate function. Take a look at the Activity life cycle and consider putting code in e.g. the onStart or onResume. Also, heavy stuff can be put on a worker thread for example. This keeps your UI responsive and the user more happy.

Gooey
  • 4,740
  • 10
  • 42
  • 76
  • I'm implementing a Calendar library in the second activity, all it's doing is showing the selected date in a TextView, here's how it looks: http://imgur.com/b3ZmLRd – John Sardinha Apr 03 '16 at 15:11
1

Firstly start your activity as

startActivity(new intent(A.this, B.class));

from whatever event you have chosen, then here is how your Activity B should work.

 class B extends AppCompatActivity implements OnDateSelectedListener, OnMonthChangedListener {

  private static final DateFormat FORMATTER = SimpleDateFormat.getDateInstance();

  @Bind(R.id.calendarView) MaterialCalendarView widget;
  @Bind(R.id.textView) TextView textView;

  ProgressDialog progressDialog;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_b);
    if(!B.this.isFinishing() && progressDialog != null && !progressDialog.isShowing()) {
        progressDialog.show();
    Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
    @Override
    public void run() {
      ButterKnife.bind(B.this);
      widget.setOnDateChangedListener(B.this);
      widget.setOnMonthChangedListener(B.this);
      textView.setText(getSelectedDatesString());

      if(!B.this.isFinishing() && progressDialog != null &&  progressDialog.isShowing()) {
        progressDialog.hide();
      }
    }, 1500);
  }

  @Override
  public void onDateSelected(@NonNull MaterialCalendarView widget, @Nullable CalendarDay date, boolean selected) {
      textView.setText(getSelectedDatesString());
  }

  private String getSelectedDatesString() {
    CalendarDay date = widget.getSelectedDate();
    if (date == null) {
        return "No Selection";
    }
    return FORMATTER.format(date.getDate());
  }

  @Override
  public void onMonthChanged(MaterialCalendarView widget, CalendarDay date) {

  }
}

Edit: as @Andre99 showed using progressbar :

-add a ProgressBar to B layout

-get a reference to this ProgressBar in B's onCreate (using findViewById()

-update your ProgressBar with setProgress(int) everytime you want(as shown in the first part of the question for ProgressDialog)

-hide your ProgressBar using yourProgressBar.setVisibility(View.GONE)

Akash Raghav
  • 1,073
  • 9
  • 19
  • I cant place what I'm loading inside `doInBackground`, please see edited quesiton, I added the activity I'm opening – John Sardinha Apr 26 '16 at 12:37
  • try this edited code. This should work i believe. and yes this should work the same way with progressbar. Andre99's answer above showed how to use progressbar. – Akash Raghav Apr 26 '16 at 13:08
  • these lines inside doinBackground `widget.setOnDateChangedListener(Activity_c_Calendar.this);` are giving error "Method setOnDateChangedListener must be called from the UI thread, currently inferred thread is worker", also `ButterKnife.bind(B.this)` is not giving error but isn't beeing called since I'm getting a null point in the TextView – John Sardinha Apr 26 '16 at 13:17
  • another way is to use handler and do those tasks in postDelayed() runnable. i've edited my code again. – Akash Raghav Apr 26 '16 at 13:27
  • Nothing is happening, I changed the timer to 5s and it's not runing it, the activity opens after it loads, which is about 1s – John Sardinha Apr 26 '16 at 13:35
  • In my case, this is the best solution for any activity to start immediately and do the other work after sometimes. – Noor Hossain Sep 26 '20 at 09:59
1

You should use a ViewStub for everything you don't immediately need displayed. Inflating views from XML layouts can be quite an expensive operation, particularly if you have multiple nested ViewGroups. A ViewStub works by offsetting unnecessary layout inflation from onCreate() to a place of your choosing, which translates to your activity appearing on screen quicker.

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView 
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ProgressBar 
        android:id="@+id/progressbar"
        android:layout_width="match_parent"
        android:indeterminate="true"
        android:layout_height="match_parent"/>

    <ViewStub 
        android:id="@+id/view_stub"
        android:inflatedId="@+id/calendarView"
        android:layout="@layout/my_expensive_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

Now that the ProgressBar is displayed on screen, we can inflate the ViewStub in a lifecycle call after onCreate(), such as onPostCreate() or onResume(). You can achieve that using the following:

viewStub.setOnInflateListener(new OnInflateListener() {
    void onInflate(ViewStub stub, View inflated) {

        calendarView = (MaterialCalendarView) inflated.findViewById(R.id.calendarView);
        // perform any calendar setup here
        progressBar.setVisibility(View.GONE); // hide progressbar, no longer needed
    }
});
viewStub.inflate();

It would definitely be worth inspecting your View hierarchy, and simplifying any nested ViewGroups where possible, as this can really cause a hit when it comes to view performance. If your entire app is slow, it might indicate greater problems such as a memory leak, which you can detect with LeakCanary.

fractalwrench
  • 4,028
  • 7
  • 33
  • 49
  • This seems like a nice solution, and thanks for the library. What is the `my_expensive_layout` tho? – John Sardinha Apr 28 '16 at 20:36
  • my_expensive_layout would be a separate layout file. If all you have is a MaterialCalendarView, it would just contain that. – fractalwrench Apr 28 '16 at 21:03
  • Ok so I created a my_expensive_layout and added the calendar in there, so I had to remove `android:inflatedId="@+id/calendarView"` from the ViewStub and it isn't working, the layout only opens when the content is loaded – John Sardinha Apr 28 '16 at 21:31
0

You can try like below code:

Intent intent= new Intent(currentActivity.this, targetActivity.class);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(intent);

Hope, It will work properly. Best of luck.

  • 1
    This feels like opening a new app, when I click the 'back' button it even closes the app, although it does open before having it loaded, as I desire, I will try other flags – John Sardinha Apr 30 '16 at 09:42