9

What's the best practise to manage / restore application back stack between multiple sessions ?

Exemple of a Workflow :

  1. Activity A started (stack: A)
  2. Activity B started (stack: A B)
  3. Activity C started (stack: A B C)
  4. ...
  5. User uses a different application (let's say the GMail app) for a while
  6. ...
  7. User goes back to my application, but the back stack has been cleared by Android.

At step 7, I'd like to have Activity C resumed, and that if user presses the back button 2 times, it will go back to Activity B, and then Activity A.

[Edit] Adding details.

After step 7 above, What happens by default in Android is this :

  1. Activity A started (stack: empty, and C is added)

And I would like the user to feel like he is still using the same session:

  1. Activity C resumed (stack: A B C)
  2. User presses back button, Activity B resumed (stack: A B)
  3. User presses back button, Activity A resumed (stack: A)

What would be a good approach to this situation while avoiding memory leaks ?

[Second EDIT] I have been crafting a workaround using a commong class UIController to all activities, and a LauncherActivity to delegate the logic to the UIController.

Since I only need to rebuild the back stack when ActivityC has been started, this solution seems to work fine :

public class UIController
{
    private boolean _launched = false;

    static private final UIController __instance = new UIController();
    static public UIController getInstance() { return __instance; }

    // Enforces the Singleton Pattern by preventing external access to constructor
    private UIController() { }

    public void onActivityCreated(Activity activity) {
        if (!_launched)
        {
            if ( shouldRebuildStack() )
            {
                // Rebuild Activity stack

                // Npte : actually Android will add ActivityA and ActivityB to the stack
                // but will *NOT* create them right away. Only ActivityC will be 
                // created and resumed.
                // Since they are in the back stack, the other activities will be 
                // created by Android once needed.
                startActivity(activity, ActivityA.class);
                startActivity(activity, ActivityB.class);
                startActivity(activity, ActivityC.class);
            } else {
                // Starts default activity
                startActivity(activity, ActivityA.class);
            }

            _launched = true;
        }
    }

    public void onActivityResumed(Activity activity) {
        memorizeCurrentActivity( activity.getClass().toString() );
    }

    private void memorizeCurrentActivity( String className ) {
        // write className to preferences, disk, etc.
    }

    private boolean shouldRebuildStack() {
        String previousActivity = " [load info from file, preferences, etc.] ";
        return (previousActivity != null && previousActivity.equals("my.package.ActivityC"));
    }

    private void startActivity(Activity caller, Class newActivityClass)
    {
        Intent intent = new Intent(caller, newActivityClass);
        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        caller.startActivity( intent );
    }
}

// This is the default activity in the AndroidManifest.xml
// This prevents ActivityA from starting right away if the UIController
// wants to rebuild the stack.
public class LauncherActivity() {
    protected void onCreate(Bundle data) {
        super.onCreate(data);
        UIController.getInstance().onActivityCreated(this);
        finish();
    }
}

public class ActivityA() {
    protected void onCreate(Bundle data) {
        super.onCreate(data);
        UIController.getInstance().onActivityCreated(this);
    }
    protected void onResume() {
        super.onResume();
        UIController.getInstance().onActivityResumed(this);
    }
}

public class ActivityB() {
    // onCreate() & onResume(), same as ActivityA
}

public class ActivityC() {
    // onCreate() & onResume(), same as ActivityA
}

public class LauncherActivity() {
    protected void onCreate(Bundle data) {
        super.onCreate(data);
        UIController.getInstance().onActivityCreated(this);
        finish();
    }
}

public class ActivityA() {
    protected void onCreate(Bundle data) {
        super.onCreate(data);
        UIController.getInstance().onActivityCreated(this);
    }
    protected void onResume() {
        super.onResume();
        UIController.getInstance().onActivityResumed(this);
    }
}

public class ActivityB() {
    // same as ActivityA
}

public class ActivityC() {
    // same as ActivityA
}

If someone has a better solution, feel free to post it.

David
  • 1,842
  • 2
  • 21
  • 31
  • http://stackoverflow.com/questions/151777/how-do-i-save-an-android-applications-state – ethrbunny Jul 26 '11 at 20:43
  • 5
    @David: "What would be a good approach to this situation" -- a good approach to this situation is to offer navigation via an action bar or option menus, and allow the back button to behave naturally. While *you* want the back button to behave that way, I doubt you have evidence that the *user* wants it. From what I've seen, users want BACK to mean BACK (i.e., return to the screen they were just on) and they complain when apps fail to honor that very basic concept (e.g., disable the BACK button, hijack the BACK button to do other things). – CommonsWare Jul 26 '11 at 23:23
  • @ethrbunny : I could use this to manually keep track of opened activities, but I am wondering if there exists a more automatic solution. – David Jul 27 '11 at 14:46
  • @CommonsWare : well, I my example the user never exitted the application. So I think it is natural for the user to experience the app like if it's session never ended. – David Jul 27 '11 at 14:52
  • The only case where you can hijack the back button is when you give the user the option to have it behave that way. For example: the Messages app in Cyanogen has a option (disabled by default) that makes the back button take you to the list of messages when in a message thread. This is acceptable because it is disabled by default. – Michael Allen Jul 27 '11 at 14:54
  • @David: In android users don't have control over whether the app is running or not. They open the app and then leave it and the os itself chooses when to kill the app. Since that is the case, you should consider every time the user switches to your app as a new session and not try to manipulate the back stack – Michael Allen Jul 27 '11 at 14:57
  • @Michael Allen : I don't necessarily needs to hijack the back button.. I only to restore the stack of opened activities. – David Jul 27 '11 at 14:57
  • @Michael Allen : how can you know when it's a new session? – David Jul 27 '11 at 14:59
  • In android, if the app has been closed by the os, then the next time it is opened it will be a new session, since the app wasn't running beforehand. If it hasn't been closed by the os then your code will pick up where it last left off. Despite this, the back button should be left as its default behaviour – Michael Allen Jul 27 '11 at 15:03
  • @Micael Allen : what is wrong in restoring the back stack if the os has closed the app? – David Jul 27 '11 at 15:36
  • What you're describing as the behavior you want is the default behavior of Android. If its not doing that, you're code is doing something to prevent it. – cyngus Aug 04 '11 at 01:02

1 Answers1

5

Sounds like you should set this to true and let Android handle managing the activity stack.

android:alwaysRetainTaskState

If this attribute is set to "true" in the root activity of a task, the default behavior just described does not happen. The task retains all activities in its stack even after a long period.

CrackerJack9
  • 3,650
  • 1
  • 27
  • 48
  • nice! From the description it looks like what I'm looking for. I'll give it a try! - Thanks – David Aug 05 '11 at 04:38
  • CrackerJack9: i was pushed onto more urgent projects, and never had time to experiment with it. However I accept your answer as it does sound like it should solve it. – David Sep 15 '11 at 04:35
  • This does not fix the problem described in the question fyi. – bond Dec 10 '13 at 14:19
  • @bond Have you asked a separate question (with useful information - some of which may differ from the OP's)? – CrackerJack9 Jan 15 '16 at 01:25
  • @CrackerJack9 I have no idea. Its been 2 years. – bond Jan 15 '16 at 19:06