Returning from an activity using navigateUpFromSameTask()

AndroidAndroid ActivityAndroid ActionbarUp Button

Android Problem Overview


I have two activities, A and B. When activity A is first started, it accesses the Intent passed to it (because the Bundle is null, as it should be the first time through), and displays information accordingly:

CustInfo m_custInfo;
...
protected void onCreate(Bundle savedInstanceState)
{
    ...
    Bundle bundle = (savedInstanceState == null) ? getIntent().getExtras() : savedInstanceState;
    m_custInfo = (CustInfo) m_bundle.getSerializable("CustInfo");
    if (m_custInfo != null
        ...
}

This works fine the first time through. The EditText controls and ListView are filled out correctly.

Now, when an item in the list is clicked, activity B is started to show the details:

m_custInfo = m_arrCustomers.get(pos);
            
Intent intent = new Intent(A.this, B.class);
intent.putExtra("CustInfo", m_custInfo); // CustInfo is serializable
// printing this intent, it shows to have extras and no flags
            
startActivityForResult(intent, 1);

Right before acivity B is started, the framework calls A's overridden onSaveInstanceState():

protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);

    outState.putSerializable("CustInfo", m_custInfo);
}

In activity B, when the Up button is pressed in the action bar, I want to return to activity A and have it be in the same state as it was before:

public boolean onOptionsItemSelected(MenuItem item)
{
    if (item.getItemId() == android.R.id.home)
    {
        Intent intent = NavUtils.getParentActivityIntent(this);
        // printing this intent, it shows to have flags but no extras

        NavUtils.navigateUpFromSameTask(this); // tried finish() here but that created an even bigger mess
        return true;
    }
    ...
}

Herein lies the problem, when in onCreate() of activity A the second time, the Bundle parameter is null and getExtras() returns null. Since onSaveInstanceState() was called, I would have expected the Bundle parameter to be non-null.

I've read about this issue on other web sites, have tried the suggestions, but nothing works.

Android Solutions


Solution 1 - Android

If you want your application to react this way, you should declare the launch mode of your activity A as:

android:launchMode="singleTop"

in your AndroidManifest.xml.

Otherwise android uses standard launch mode, which means > "The system always creates a new instance of the activity in the > target task"

and your activity is recreated (see Android documentation).

With singleTop the system returns to your existing activity (with the original extra), if it is on the top of the back stack of the task. There is no need to implement onSaveInstanceState in this situation.

savedInstanceState is null in your case, because your activity was not previously being shut down by the OS.

Notice (thanks, android developer for pointing to this):

While this is a solution to the question, it will not work if the activity one returns to is not on the top of the back stack. Consider the case of activity A starting B, which is starting C, and C's parent activity is configured to be A. If you call NavigateUpFromSameTask() from C, the activity will be recreated, because A is not on top of the stack.

In this case one can use this piece of code instead:

Intent intent = NavUtils.getParentActivityIntent(this); 
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP); 
NavUtils.navigateUpTo(this, intent);

Solution 2 - Android

This happens because NavUtils.navigateUpFromSameTask() basically just calls startActivity(intentForActivityA). If activity A uses the default android:launchMode="standard" then a new instance of the activity is created and the saved state is not used. There are three ways to fix this, with various advantages and disadvantages.

Option 1

Override getParentActivityIntent() in activity B and specify the appropriate extras needed by activity A:

@Override
public Intent getParentActivityIntent() {
    Intent intent = super.getParentActivityIntent();
    intent.putSerializable("CustInfo", m_custInfo);
    return intent;
}

Note: If you're using AppCompatActivity and the toolbar from androidx or the support library then you'll need to override getSupportParentActivityIntent(), instead.

I feel that this is the most elegant solution as it works regardless of how the user navigated to activity B. The two options listed below don't work correctly if the user navigates directly to activity B without going through activity A, for example if activity B is launched from a notification or URL handler. I think this scenario is the reason the 'up' navigation API is complicated and doesn't simply act as a dumb back button.

One downside with this solution is that the standard starting-a-new-activity transition animation is used rather than the returning-to-previous-activity animation.

Option 2

Intercept the up button and treat it as a dumb back button by overriding onNavigateUp():

@Override
public boolean onNavigateUp() {
    onBackPressed();
    return true;
}

Note: If you're using AppCompatActivity and the toolbar from androidx or the support library then you'll need to override onSupportNavigateUp(), instead.

This solution is a bit hacky and only works for applications where "up" and "back" should behave the same. This is probably rare, because if "up" and "back" should behave the same then why bother having an up button? One advantage of this solution is that the standard returning-to-previous-activity animation is used.

Option 3

As suggested by yonojoy, set android:launchMode="singleTop" on activity A. See his answer for details. Note that singleTop isn't appropriate for all applications. You'll have to try it and/or spend some time reading the documentation to decide for yourself.

Solution 3 - Android

Instead of calling NavUtils.navigateUpFromSameTask

You can get the parent intent and modify it before navigating. For example:

Intent intent = NavUtils.getParentActivityIntent(this);
intent.putExtra("CustInfo", m_custInfo); // CustInfo is serializable
NavUtils.navigateUpTo(this, intent);

This will behave exactly the same as NavUtils.navigateUpFromSameTask but will allow you to add some extra properties to the intent. You can have a look at the code for NavUtils.navigateUpFromSameTask it's very simple.

public static void navigateUpFromSameTask(Activity sourceActivity) {
    Intent upIntent = getParentActivityIntent(sourceActivity);

    if (upIntent == null) {
        throw new IllegalArgumentException("Activity " +
                sourceActivity.getClass().getSimpleName() +
                " does not have a parent activity name specified." +
                " (Did you forget to add the android.support.PARENT_ACTIVITY <meta-data> " +
                " element in your manifest?)");
    }

    navigateUpTo(sourceActivity, upIntent);
}

Making the parent activity SINGLE_TOP may work for some simple applications, but what if this activity wasn't launched directly from it's parent? OR you might legitimately want the parent to exist in multiple places in the back stack.

Solution 4 - Android

I think this is a more general solution, to be added to your base Activity class:

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    if (item.getItemId() == android.R.id.home) {
        final Intent upIntent = NavUtils.getParentActivityIntent(this);
        if (upIntent == null)
            onBackPressed();
        else
            //optionally add this flag to the intent: Intent.FLAG_ACTIVITY_SINGLE_TOP
            NavUtils.navigateUpTo(this, upIntent);
        return true;
    }
    return super.onOptionsItemSelected(item);
}

And for each Activity that you wish that would navigate to some specific parent Activity, use this on the manifest:

    <activity android:parentActivityName="pathToParentActivity" ...>
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="pathToParentActivity"/>
    </activity>

And, if your minSdk is from API 16, remove the "meta-data" tag, so you have this instead in the manifest:

    <activity android:parentActivityName="pathToParentActivity" ...>
    </activity>

More information here

Solution 5 - Android

You mention:

> In activity B, when the Up button is pressed in the action bar, I want to return to activity A and have it be in the same state as it was before.

If your content on Activity A resides in a Fragment then call setRetainInstance(true) in the onCreate() of the Fragment.

Then the Fragment instance will be retained across Activity recreation.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavidCrowView Question on Stackoverflow
Solution 1 - AndroidyonojoyView Answer on Stackoverflow
Solution 2 - AndroidMark DolinerView Answer on Stackoverflow
Solution 3 - AndroidJared KellsView Answer on Stackoverflow
Solution 4 - Androidandroid developerView Answer on Stackoverflow
Solution 5 - AndroidStan SmithView Answer on Stackoverflow