I’ve recently been faced with the challenge of detecting the login state of the user and deciding whether or not to show a login screen. This isn't only required with a fresh launch of the application, but every time it resumes from the background since the login times out fairly quickly. And, instead of handling it one way when the app is resumed and another when launching the app fresh, I wanted to find a way to do this with one consistent approach.

I decided to try to solve the issue of the app resuming from the background regardless of the circumstances, by consistently sending the login screen to the back and avoiding completing the final activity when the user presses ‘back.’  When I avoid this, the app can resume from the background instead of completely restarting -- a cold start of the entire application from a user’s perspective.  And when the app starts fresh, we can always go directly to the login activity because we know it will require a login.

If we didn’t approach the issue like this and we allowed the root activity to finish() the application would go through a cold start and require that we go through unextra Activity during startup to determine if we need to log in or not. That extra Activity acts as a guard into our app and would require it’s own separate logic to handle this particular case.

So how do we actually achieve this end result? A little research into the Activity class documentation uncovers that avoiding a cold start of the app is possible, but I have some questions:

  • What is the proper method for doing this?
  • What is the easiest method?
  • Are there any advantages to preventing a cold start?
  • Are there any disadvantages to avoiding a cold start?

My research turned up a method on Activity called moveTaskToBack(boolean) which will "move the task containing this activity to the back of the activity stack." This seems like a promising lead so I created a minimal, complete, and verifiable example (MCVE) to test this by just calling the method from onBackPressed():

@Override
public void onBackPressed() {
    moveTaskToBack(false);
}

Notice that I intentionally avoided calling super.onBackPressed() since that ultimately finishes the activity. Aside from not finishing my activity my example app doesn't do much. It appeared to work, but I'd like to guarantee that my assumptions are valid and to verify that calling super.onBackPressed() or finish() gives different behavior than backgrounding the application.

To do this, I added a RadioGroup to the UI so the user can control what happens when they press the back button. The user can have the task moved into the background, finish() the activity explicitly, or use the default behavior (which calls finish() anyway).

@Override
public void onBackPressed() {
    switch (behaviorOptionsGroup.getCheckedRadioButtonId()) {
        case R.id.finish:
            finish();
            return;
        case R.id.moveTaskToBack:
            moveTaskToBack(false);
            return;
        default:
            super.onBackPressed();
    }
}

I only have one activity in my simple example, but it's worth noting that if false is passed to moveTaskToBack this does nothing when the current activity is not the root of the task. Passing true will give the same effect regardless of the root activity.

As I continue, things seem to be working as expected, but parts of the activity lifecycle are difficult if not impossible to see most times -- e.g. - was onDestroy() called? Adding log statements in some of the lifecycle callbacks allowed me to see what was happening with the activity lifecycle and made it easy to confirm that things were working as expected.

When the activity is the root of the task, it gets moved to the back and effectively kicks the user back to wherever they were before they started (or resumed) the app. It looks to the user like the app has finished like they expect. The app doesn't have to go through the typical start up path to resume and the user is right back where they left off. Success!

Now that I've thoroughly tested my implementation, I see that the main advantage of this method is that it's fairly simple to implement. However, the disadvantage is that this approach does something unexpected for the users. Because most apps only have one activity running most of the time, the user expects that by pressing ‘back’ on that last activity it is finished and the app starts cold the next time the user comes to it.

This is not necessarily the best, right, or proper way to do this for every app on the market. Implementing this tactic in a simple app could reduce the effort needed to insert this behavior in the app and might not have any significant repercussions. However, it's difficult and dangerous to make broad generalizations, so, as with anything, make sure backgrounding a login screen is the right solution for you and the app you are building.


For an Android Studio project companion to this article that is ready to run, click here:  

View on Github

 The code is a MCVE that allows the user to experiment with various behaviors associated with pressing the 'back' button discussed in the article. The user can see what happens when the moveTaskToBack method is called with a root and nonroot Activity, at their discretion. They can also see the difference between using moveTaskToBack, the default behavior, and manually calling finish() on an Activity when back is pressed.


To learn about MVVM on Android using the Data Binding Library click below!

CHECK OUT MVVM  

 

stable-kernel-mobile-app-development

About The Author

David Mays is an Android software engineer and a recent Atlanta transplant, originally hailing from nearby Alabama. He’s previously worked at Metova and Tin Roof Software, though he’s been coding as a hobby for 20 years. The once War Eagle enjoys competitive shooting and playing guitar for fun, and he is the father to a beautiful 2-year old girl.