On logout, clear Activity history stack, preventing "back" button from opening logged-in-only Activities
Asked Answered
B

18

243

All activities in my application require a user to be logged-in to view. Users can log out from almost any activity. This is a requirement of the application. At any point if the user logs-out, I want to send the user to the Login Activity. At this point I want this activity to be at the bottom of the history stack so that pressing the "back" button returns the user to Android's home screen.

I've seen this question asked a few different places, all answered with similar answers (that I outline here), but I want to pose it here to collect feedback.

I've tried opening the Login activity by setting its Intent flags to FLAG_ACTIVITY_CLEAR_TOP which seems to do as is outlined in the documentation, but does not achieve my goal of placing the Login activity at the bottom of the history stack, and preventing the user from navigating back to previously-seen logged-in activities. I also tried using android:launchMode="singleTop" for the Login activity in the manifest, but this does not accomplish my goal either (and seems to have no effect anyway).

I believe I need to either clear the history stack, or finish all previously- opened activities.

One option is to have each activity's onCreate check logged-in status, and finish() if not logged-in. I do not like this option, as the back button will still be available for use, navigating back as activities close themselves.

The next option is to maintain a LinkedList of references to all open activities that is statically accessible from everywhere (perhaps using weak references). On logout I will access this list and iterate over all previously-opened activities, invoking finish() on each one. I'll probably begin implementing this method soon.

I'd rather use some Intent flag trickery to accomplish this, however. I'd be beyond happy to find that I can fulfill my application's requirements without having to use either of the two methods that I've outlined above.

Is there a way to accomplish this by using Intent or manifest settings, or is my second option, maintaining a LinkedList of opened activities the best option? Or is there another option that I'm completely overlooking?

Beaufort answered 9/6, 2010 at 16:53 Comment(0)
U
215

I can suggest you another approach IMHO more robust. Basically you need to broadcast a logout message to all your Activities needing to stay under a logged-in status. So you can use the sendBroadcast and install a BroadcastReceiver in all your Actvities. Something like this:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);

The receiver (secured Activity):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**snip **/
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_LOGOUT");
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("onReceive","Logout in progress");
            //At this point you should start the login activity and finish this one
            finish();
        }
    }, intentFilter);
    //** snip **//
}
Ula answered 9/6, 2010 at 18:24 Comment(17)
This doesn't seem useful. Only the current top-most activity will be able to dynamically-register for these broadcasts. And the logout event comes from that activity itself, so this is redundant. Pressing Back at this point would take you to the previous activity, which would be unaware the logout happened.Tarbes
@Christopher, each activity registers for the broadcast when it gets created. When it goes to the background (i.e., a new activity comes to the top of the stack), its onStop() will get called, but it can still receive broadcasts. You just need to make sure you call unregisterReceiver() in onDestroy() rather than in onStop().Byrdie
Does this work if an activity somewhere in the stack was shut down by the OS to recover memory? Ie. will the system consider it really finished after the broadcast above is sent, and will not recreate it on hitting the back button?Spiraea
While this does seem like an elegant solution, it is important to note that this is not synchronous.Baku
@Che Jami: so what would be synchronous?Manhole
@Igor Ganapolsky: maybe I can think of it. Suppose you have have a Login Activity and Activity A, B, C. The task stack would be Login->A->B->C. And Activity A, B and C are secured. Once you on Activity C received the logout intent. A and B have also received.Then what is the order of their onReceive on these three activities?Arty
A nice solution, but instead of using a registering the Broadcast receiver as described in the code above, you should use a LocalBroadcastManager.getInstance(this).registerReceiver(...) and LocalBroadcastManager.getInstance(this).unregisterReceiver(..). Otherwiser your application can receive intents from any other application (security concern)Congenial
What if activity on backstack is killed by system (low-memory) and this activity will not receive broadcast. But this activity will stay on back stack record. Not sure if this is the most bullet proof solution.Splanchnic
Too much coding for a fairly simple (for the user) feature.Immortality
@Splanchnic You are correct. The pitfall to this approach is when an Activity in the back stack is destroyed by the system (can happen for various reasons, such as the noted low-memory scenario). In that case, the Activity instance will not be around to received the broadcast. But the system will still navigate back through that Activity by recreating it. This can be tested/reproduced by turning on the developer setting "Don't Keep Activities"Voigt
This approach, with @Uku Loskit's tip does it for me. It also covers the problems pointed out by user Mike Repass (regarding calling startActivity(new Intent(this, LoginActivity.class)) with FLAG_ACTIVITY_CLEAR_TOP).Boisleduc
Old post but one case where this fails is if the activity goes to sleep(android serializes it to save memory). If this happens the activity wont be able to receive the broadcast and close itself. To test set the 'Don't keep Activities' flag in the developers settings. Is there an alternative, did you try the linkedList?Heterogenesis
as @Splanchnic stated, this solution will not work on every situation. See my basic solution below for a working exampleLezlie
This could probably be achieved with 'finishAffinity' - https://mcmap.net/q/92450/-android-clear-the-back-stackRevile
I used Otto instead of a broadcast receiverWellordered
This is a hack that will fail as soon as your backstack is destroyed to reclaim memory.Homegrown
should we make one activity and extend all activities from it ,so we write the code only once ?Chromous
C
154

It seems a rite of passage that a new Android programmer spends a day researching this issue and reading all of these StackOverflow threads. I am now newly initiated and I leave here trace of my humble experience to help a future pilgrim.

First, there is no obvious or immediate way to do this per my research (as of September 2012). You'd think you could simple startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK) but no.

You CAN do startActivity(new Intent(this, LoginActivity.class)) with FLAG_ACTIVITY_CLEAR_TOP - and this will cause the framework to search down the stack, find your earlier original instance of LoginActivity, recreate it and clear the rest of the (upwards) stack. And since Login is presumably at the bottom of the stack, you now have an empty stack and the Back button just exits the application.

BUT - this only works if you previously left that original instance of LoginActivity alive at the base of your stack. If, like many programmers, you chose to finish() that LoginActivity once the user has successfully logged in, then it's no longer on the base of the stack and the FLAG_ACTIVITY_CLEAR_TOP semantics do not apply ... you end up creating a new LoginActivity on top of the existing stack. Which is almost certainly NOT what you want (weird behavior where the user can 'back' their way out of login into a previous screen).

So if you have previously finish()'d the LoginActivity, you need to pursue some mechanism for clearing your stack and then starting a new LoginActivity. It seems like the answer by @doreamon in this thread is the best solution (at least to my humble eye):

https://mcmap.net/q/93650/-on-logout-clear-activity-history-stack-preventing-quot-back-quot-button-from-opening-logged-in-only-activities

I strongly suspect that the tricky implications of whether you leave LoginActivity alive are causing a lot of this confusion.

Good Luck.

Cooperstein answered 17/9, 2012 at 23:6 Comment(2)
Good answer. The FLAG_ACTIVITY_CLEAR_TOP trick, that most people advice to use, just does not work if you have finished the LoginActivity.Cusec
Thanks for the answer.Another scenario is like when there is a session(e.g. like fb) even we don't call Login activity so there is no point of Login activity in stack. Then Above mentioned approach is useless.Reich
M
124

UPDATE

the super finishAffinity() method will help to reduce the code but achieve the same. It will finish the current activity as well as all activities in the stack, use getActivity().finishAffinity() if you are in a fragment.

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));

ORIGINAL ANSWER

Assume that LoginActivity --> HomeActivity --> ... --> SettingsActivity call signOut():

void signOut() {
    Intent intent = new Intent(this, HomeActivity.class);
    intent.putExtra("finish", true);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // To clean up all activities
    startActivity(intent);
    finish();
}

HomeActivity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    boolean finish = getIntent().getBooleanExtra("finish", false);
    if (finish) {
        startActivity(new Intent(mContext, LoginActivity.class));
        finish();
        return;
    }
    initializeView();
}

This works for me, hope that it is helpful for you too. :)

Misconceive answered 6/3, 2012 at 8:16 Comment(13)
I think your solution assumes that the user will click signOut and go back to just one activity (HomeActivity). What if you have 10 activities on the stack?Manhole
If you have 10 activities on the top of HomeActivity, the FLAG_ACTIVITY_CLEAR_TOP flag will help to clean all of them.Misconceive
If you chose to finish() the original instance of LoginActivity, then THIS (@doreamon's) answer is definitely the way to go. The _CLEAR_TOP semantics only work if the framework can find a previous instance below you in the stack - otherwise no dice.Cooperstein
This can only work if when starting the HomeActivity, you get your OnCreate of HomeActivity. Simply starting home activity won't necessarily recreate it unless it was finished or destroyed already. If HomeActivity doesn't need to be recreated, OnCreate won't get called and after you log out you will be sitting on your home activity.Stairs
This is a possible solution and which involves a lot less coding a simple (for the user) feature such as logout.Immortality
+1 I dare say, this is the ONLY way to implement FLAG_ACTIVITY_CLEAR_TASK like task clearing effect in API level 10 or below!Tripartite
What if I need to go directly to the Login activity but I don't want it restarted. Could I use CLEAR_TOP | SINGLE_TOP ? I'm concerened about this threadStridulous
If so, just remove startActivity(new Intent(mContext, LoginActivity.class)) line, it will dismiss your HomeActivity and LoginActivity will show up without restarted. Suppose that you don't call finish() before starting HomeActivity.Misconceive
The Intent.FLAG_ACTIVITY_CLEAR_TOP flag helps to clean up all activities including HomeActivity, thus the onCreate() method will be called when you start this activity again.Misconceive
@doraemon This is not supported if the device lower than honecomp with already closed LoginActivity then its not working.Belia
Could you be more specific. What is your problem? I use this function on from 2.2 to 4.2 and don't see any problem.Misconceive
It does not cover the case when HomeActivity is not on backstack :)Miltonmilty
It works well, but please ensure that you haven't used launchMode="singleTop" (in Manifest) for the HomeActivity, otherwise this thing won't work.Sweven
S
75

If you are using API 11 or higher you can try this: FLAG_ACTIVITY_CLEAR_TASK--it seems to be addressing exactly the issue you're having. Obviously the pre-API 11 crowd would have to use some combination of having all activities check an extra, as @doreamon suggests, or some other trickery.

(Also note: to use this you have to pass in FLAG_ACTIVITY_NEW_TASK)

Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("finish", true); // if you are checking for this in your other Activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 
                Intent.FLAG_ACTIVITY_CLEAR_TASK |
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
Sheliasheline answered 4/2, 2013 at 22:54 Comment(4)
Working like a charm. Thank you very much! As I develop on min API 14, that's the only thing to implement.Salchunas
I think we don't need FLAG_ACTIVITY_CLEAR_TOP when using this solution for LoginActivity.Cholula
I think just finish(); will do the job to prevent going back after logout. What's the need of setting flags?Narial
This creates an exception Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flagProvencal
S
32

I spent a few hours on this too ... and agree that FLAG_ACTIVITY_CLEAR_TOP sounds like what you'd want: clear the entire stack, except for the activity being launched, so the Back button exits the application. Yet as Mike Repass mentioned, FLAG_ACTIVITY_CLEAR_TOP only works when the activity you're launching is already in the stack; when the activity's not there, the flag doesn't do anything.

What to do? Put the activity being launching in the stack with FLAG_ACTIVITY_NEW_TASK, which makes that activity the start of a new task on the history stack. Then add the FLAG_ACTIVITY_CLEAR_TOP flag.

Now, when FLAG_ACTIVITY_CLEAR_TOP goes to find the new activity in the stack, it'll be there and be pulled up before everything else is cleared.

Here's my logout function; the View parameter is the button to which the function's attached.

public void onLogoutClick(final View view) {
    Intent i = new Intent(this, Splash.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
    finish();
}
Sundstrom answered 19/6, 2013 at 14:15 Comment(2)
Won't work on API<=10 as FLAG_ACTIVITY_CLEAR_TASK wasn't added yetRisser
@Sundstrom You're talking about FLAG_ACTIVITY_CLEAR_TOP and your code snippet has FLAG_ACTIVITY_CLEAR_TASK; which is valid then?Miltonmilty
C
18

Lots of answers. May be this one will also help-

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();

Kotlin version-

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()
Cupreous answered 22/7, 2015 at 9:38 Comment(0)
B
4

Use this it should be helpful to you. Slightly modified xbakesx answer.

Intent intent = new Intent(this, LoginActivity.class);
if(Build.VERSION.SDK_INT >= 11) {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent);
Belia answered 25/6, 2014 at 8:37 Comment(0)
L
4

Accepted solution is not correct, it has problems as using a broadcast receiver is not a good idea for this problem. If your activity has already called onDestroy() method, you will not get receiver. Best solution is having a boolean value on your shared preferences, and checking it in your activty's onCreate() method. If it should not be called when user is not logged in, then finish activity. Here is sample code for that. So simple and works for every condition.

protected void onResume() {
  super.onResume();
  if (isAuthRequired()) {
    checkAuthStatus();
  }
}

private void checkAuthStatus() {
  //check your shared pref value for login in this method
  if (checkIfSharedPrefLoginValueIsTrue()) {
    finish();
  }
}

boolean isAuthRequired() {
  return true;
}
Lezlie answered 9/7, 2015 at 16:32 Comment(3)
It's been years, but I believe I did both. Each Activity extended LoggedInActivity, which checked the user's logged-in status in onCreate(). LoggedInActivity also listened for the "user logged out" broadcast, and did whatever it needed to do to respond to this.Beaufort
more probably you should put the checkAuthStatus in the onResume() methode. Because when you press the back button, the activity is more likely to get resumed instead of created.Assumed
@Assumed Thanks for suggestion, yes this way back button will work correctly too, I updated the entryLezlie
V
4

Sometime finish() not working

I have solved that issue with

finishAffinity()

Vines answered 31/8, 2017 at 4:43 Comment(0)
S
3

Here is the solution I came up with in my app.

In my LoginActivity, after successfully processing a login, I start the next one differently depending on the API level.

Intent i = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    startActivity(i);
    finish();
} else {
    startActivityForResult(i, REQUEST_LOGIN_GINGERBREAD);
}

Then in my LoginActivity's onActivityForResult method:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

Finally, after processing a logout in any other Activity:

Intent i = new Intent(this, LoginActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);

When on Gingerbread, makes it so if I press the back button from MainActivity, the LoginActivity is immediately hidden. On Honeycomb and later, I just finish the LoginActivity after processing a login and it is properly recreated after processing a logout.

Setula answered 4/11, 2013 at 22:56 Comment(1)
Its not working for me. In my case i used fragmentactivity. Onactivityresult method is not called.Belia
N
3

I'd suggest a different approach to this question. Maybe it's not the most efficient one, but I think it's the easiest to apply and requires very little code. Writing the next code in your first activity (log in activity, in my case) won't let the user go back to previously launched activities after logging out.

@Override
public void onBackPressed() {
    // disable going back to the MainActivity
    moveTaskToBack(true);
}

I'm assuming that LoginActivity is finished just after the user logs in, so that he can't go back to it later by pressing the back button. Instead, the user must press a log out button inside the app in order to log out properly. What this log out button would implement is a simple intent as follows:

Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();

All suggestions are welcome.

Nano answered 12/2, 2018 at 12:7 Comment(0)
O
2

The selected answer is clever and tricky. Here's how I did it:

LoginActivity is the root activity of the task, set android:noHistory="true" to it in Manifest.xml; Say you want to logout from SettingsActivity, you can do it as below:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
Ooze answered 18/12, 2014 at 13:19 Comment(0)
P
1

This worked for me:

     // After logout redirect user to Loing Activity
    Intent i = new Intent(_context, MainActivity.class);
    // Closing all the Activities
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Add new Flag to start new Activity
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    // Staring Login Activity
    _context.startActivity(i);
Pacien answered 24/6, 2015 at 12:14 Comment(1)
Could you please elaborate more your answer adding a little more description about the solution you provide?Nitrification
B
0

Start you activity with StartActivityForResult and while you logout set your result and according to you result finish your activity

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(intent, BACK_SCREEN);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case BACK_SCREEN:
        if (resultCode == REFRESH) {
            setResult(REFRESH);
            finish();
        }
        break;
    }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        AlertDialog alertDialog = builder.create();

        alertDialog
                .setTitle((String) getResources().getText(R.string.home));
        alertDialog.setMessage((String) getResources().getText(
                R.string.gotoHome));
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {

                        setResult(REFRESH);
                        finish();
                    }

                });

        alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {
                    }
                });
        alertDialog.show();
        return true;
    } else
        return super.onKeyDown(keyCode, event);

}
Bellboy answered 15/12, 2010 at 12:50 Comment(0)
D
0

on click of Logout you may call this

private void GoToPreviousActivity() {
    setResult(REQUEST_CODE_LOGOUT);
    this.finish();
}

onActivityResult() of previous Activity call this above code again until you finished the all activities.

Durer answered 23/2, 2012 at 8:21 Comment(2)
That means it has to traverse through all activities linearly, instead of broadcasting the finish() all at once?Manhole
@Igor G. yes it is the alternative way to do finish sequentiallyDurer
R
0

The solution @doreamon provided works fine for all the cases except one:

If After login, Killing Login screen user navigated direct to a middle screen. e.g. In a flow of A->B->C, navigate like : Login -> B -> C -> Press shortcut to home. Using FLAG_ACTIVITY_CLEAR_TOP clears only C activity, As the Home(A) is not on stack history. Pressing Back on A screen will lead us back to B.

To tackle this problem, We can keep an activity stack(Arraylist) and when home is pressed, we have to kill all the activities in this stack.

Reeher answered 20/6, 2014 at 11:19 Comment(0)
P
0

It is possible by managing a flag in SharedPreferences or in Application Activity.

On starting of app (on Splash Screen) set the flag = false; On Logout Click event just set the flag true and in OnResume() of every activity, check if flag is true then call finish().

It works like a charm :)

Palisade answered 31/7, 2014 at 12:47 Comment(0)
A
-1

One option is to have each activity's onCreate check logged-in status, and finish() if not logged-in. I do not like this option, as the back button will still be available for use, navigating back as activities close themselves.

What you want to do is call logout() and finish() on your onStop() or onPause() methods. This will force Android to call onCreate() when the activity is brought back on since it won't have it in its activity's stack any longer. Then do as you say, in onCreate() check logged in status and forward to login screen if not logged in.

Another thing you could do is check logged in status in onResume(), and if not logged in, finish() and launch login activity.

Antarctica answered 9/6, 2010 at 17:52 Comment(2)
I'd prefer to not logout on each activity pause or stop. Also, the application initiates logout or login, so I don't need to check if logged-in, really.Beaufort
@Ricardo: doesn't your solution require a BroadcastReceiver?Manhole

© 2022 - 2024 — McMap. All rights reserved.