Application which always requires Login

In this tutorial is an attempt to disallow the user to resume an application without signing in again. Whatever happens that causes my app to go off-screen should force the user to sign in again. In this context, signing in is rather case-specific: Say, every time the app is hidden and then shown again, the app will go to one specific activity.

To start with, we will need an activity that will be extended by all our activities. I will call it here RequireLoginActivity which is a base class and all our work will be in it.

This activity has 2 requirements:

Requirement 1: Once resumed, it should go to the Sign in screen if it was resumed from the background.
Requirement 2: Once we decide to force the user to sign in, any instance of this activity should finish.

Requirement 2:

To start, I will accomplish the somehow easier requirement 2 by using Francesco Laurita's Solution. Simply stated, each RequireLoginActivity should register a receiver that will listen for a CLOSE broadcast. Once we decide to go to the Sign in screen, we broadcast this message and all RequireLoginActivities alive will just finish.

First of all, let's name this BroadCast intent Action as INTENT_LOGOUT in RequireLoginActivity.

public class RequireLoginActivity extends Activity {
 private static final String INTENT_LOGOUT = "mobi.sherif.INTENT_LOGOUT";
}

Now, we need the broadcast receiver: Each activity should have a logout receiver, so just after this INTENT_LOGOUT we initialize our receiver:

private final BroadcastReceiver mLogoutReceiver = new BroadcastReceiver() {

 @Override
 public void onReceive(Context context, Intent intent) {
  finish();
 }
};

Notice that this receiver's only function is to finish the current RequireLogin Activity.

Finally, we need to register the broadcast receiver in our onCreate function and unregister it when our activity is finished in our onDestroy function:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 IntentFilter intentFilter = new IntentFilter();
 intentFilter.addAction(INTENT_LOGOUT);
 registerReceiver(mLogoutReceiver, intentFilter);
}
@Override
protected void onDestroy() {
 unregisterReceiver(mLogoutReceiver);
 super.onDestroy();
}

For the sake of completeness and neatness, we shall add a function that will broadcast this intent once we need to go to the SignIn screen. I will call it gotoLogin():

private void gotoLogin() {
 Intent intent = new Intent(this, LoginActivity.class);
 startActivity(intent);
 Intent broadcastIntent = new Intent();
 broadcastIntent.setAction(INTENT_LOGOUT);
 sendBroadcast(broadcastIntent);
}


Requirement 1:

Now, we need a mechanism that will allow us to capture when that activity is hidden and shown. To be specific, we need one that allows us to differentiate between a normal onResume, onPause cycle due to activity navigation and a cycle caused by hiding the activity for any reason such as phone call, pressing the home button, screen lock, navigating to a different activity.

Three cases should be captured when the onResume function is called:

  1. Jumping from one RequireLoginActivity to another RequireLoginActivity using a flavor of startActivity.
  2. Jumping from one RequireLoginActivity back to a previous RequireLoginActivity by finishing the current activity.
  3. Coming back to a RequireLoginActivity after hiding it (we should here show the login!)


The basic idea of my solution is to have 2 counters: number of Started activities (startCounter) and number of Paused activities (pauseCounter). Each time an activity starts we will increment startCounter. Similarly, when an activity pauses, pauseCounter should be incremented. In our onResume function, we will decide whether to go to the Sign in by comparing the 2 counters. We will gotoLogin() if the 2 counters are equal!

Let me explain:
At any time, case 1 can be captured simply because upon starting new activities, our startCounter will always be greater that pauseCounter by 1. This is true because we will always have one extra activity started but not paused.

Also, case 3 is easily captured, because once you leave our app, say, using the HOME button, we will increment the pauseCounter and the 2 counters will become equal. Once the app is resumed, the onResume will decide to gotoLogin().

Case 2 is a bit tricky, but simple as well. The trick is by overriding the finish() function and decrementing the startCounter once and the pauseCounter twice in it. Remember that when finishing the activity, onPause is called and our counters are equal. Now by decrementing startCounter once and pauseCounter twice, we ultimately returned to the counters' values of the previous activity, and startCounter will remain greater the pauseCounter by 1 when the previous activity resumes.

Implementation:
I will implement the counters in my Application instance:

public class RequireLoginApplication extends Application {
 
 private static int nRequiredStarted = 0; //this is our startCounter

 public void incrementStart() {
  ++nRequiredStarted;
 }

 private static int nRequiredPaused = 0; //this is our pauseCounter

 public void incrementPause() {
  ++nRequiredPaused;
 }

 public void notifyFinish() { //this is called when the activity finishes
  --nRequiredPaused;
  --nRequiredPaused;
  --nRequiredStarted;
 }

 
 public boolean shouldLogin() { //this decides whether to go to the login screen(true)
  return (nRequiredPaused==nRequiredStarted);
 }
 
 public void reset() { //reset the counters in the login screen
  nRequiredPaused = 0;
  nRequiredStarted = 0;
 }
}

Now, in our RequireLoginActivity, we simply incrementStart in onCreateincrementPause  in onPause, and notifyFinish in finish.

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 IntentFilter intentFilter = new IntentFilter();
 intentFilter.addAction(INTENT_LOGOUT);
 registerReceiver(mLogoutReceiver, intentFilter);
 ((RequireLoginApplication)getApplication()).incrementStart();
}

@Override
protected void onPause() {
 super.onPause();
 ((RequireLoginApplication)getApplication()).incrementPause();
}

@Override
public void finish() {
 ((RequireLoginApplication)getApplication()).notifyFinish();
 super.finish();
}

The last step is the onResume function: Just decide whether we need to gotoLogin:

@Override
protected void onResume() {
 super.onResume();
 if(((RequireLoginApplication)getApplication()).shouldLogin()) {
  gotoLogin();
 }
}


Click here for Sample Project + Source Code (download)

3 comments: