I have an application that starts with an Activity to load stuff before the main Activity is shown. Starting the application normally does MyApplication
--> MyLoadingActivity
--> MyMainActivity
. In MyMainActivity
there is a ViewPager with RecyclerViews and other stuff. The state in MyMainActivity
is properly saved and restored when navigation to and from other Activities in the application, but when starting MyMainActivity
from a Notification all the state is cleared since Android restarts that Activity.
The Notifications are created from a Service checking for updates from a remote server. Once updates are found, the new data are stored in the SQLite database and a Notification about this is created. When I click this Notification, MyMainActicity
is started, but the state is lost.
Following the official guide, here is how I create the Notification inside the Service:
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.foo)
.setAutoCancel(true)
.setContentTitle("title")
.setContentText("text")
.setStyle(new NotificationCompat.BigTextStyle().bigText("A long text will go here if we are on Lollipop or above."));
Intent notifyIntent = new Intent(context, MyMainActivity.class);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(666, builder.build());
I have tried combining the Intent.FLAG_***
in various ways, but I have not been able to make it work properly. I cannot figure this out, hence this question.
What I want to achieve when opening the Notification is:
- If the application is not running, start the application normally and go to
MyMainActivity
. - If the application is running and
MyMainActivity
is active, just bring that to the front with the existing state and run a few lines of code. - If the application is running and another Activity is running, go back to
MyMainActivity
, preferably using the existing stack. Most other activities are children ofMyMainActivity
.
What happens using the current code:
- Using
Intent.FLAG_ACTIVITY_NEW_TASK
creates a new instance ofMyMainActivity
which has empty state, even though its description states
When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in.
- Also using
Intent.FLAG_ACTIVITY_CLEAR_TASK
callsonDestroy()
on the existingMyMainActivity
after the newMyMainActivity
is created. That does not help much.
What other things I have tried:
A. Setting the Intent to start as the Application normally does, as described here, does not work.
B. Various combinations of flags has not been able to give the desired behavior.
C. Various other answers here on Stackoverflow.
For reference, here is my manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.my.app"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="11"
android:targetSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/my_app_name"
android:theme="@style/MyTheme"
android:name=".MyApplication" >
<activity
android:name=".activities.MyLoadingActivity"
android:icon="@drawable/ic_launcher"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activities.MyMainActivity"
android:icon="@drawable/ic_launcher"
android:label="@string/my_app_name"
android:screenOrientation="portrait" >
</activity>
<activity
android:name=".activities.AnotherActivity"
android:label="@string/aa_title"
android:icon="@drawable/ic_launcher"
android:parentActivityName=".activities.MyMainActivity"
android:screenOrientation="portrait">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MyMainActivity" >
</meta-data>
</activity>
<!-- More children activities of MyMainActivity here -->
<receiver android:name=".services.ScheduleUpdateReciever">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="com.my.app.action.ScheduleUpdateReceiver"/>
</intent-filter>
</receiver>
<receiver android:name=".services.StartUpdateReceiver" />
<service android:enabled="true" android:name=".services.UpdateService" />
</application>
</manifest>
Update: saving/restoring
Here is how MyMainActivity
is saved and restored. I realize that this might need some cleaning. Maybe it is related to the problems. Other parts of the class is omitted for brevity. As noted above, all of the saving and restoring works fine when navigating around inside the app.
private ShoppingList mShoppingList;
private FilteredRecipes mFilteredRecipes;
private MainFragment mMainFragment;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity_layout);
// Start the Broadcast receiver that creates notifications if new data is found.
startService(new Intent(this, ScheduleUpdateReciever.class));
sendBroadcast(new Intent(SCHEDULE_UPDATE_RECEIVER_BROADCAST));
// Setup Toolbar, NavigationView, DrawerLayout etc. here. No state restored for these widgets.
if (mShoppingList == null)
{
mShoppingList = new ShoppingList(this);
}
if (mFilteredRecipes == null)
{
mFilteredRecipes = new FilteredRecipes(this);
}
if (savedInstanceState == null)
{
mMainFragment = new MainFragment();
}
else
{
restoreState(savedInstanceState);
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, mMainFragment, FragmentConstants.MAIN_FRAGMENT_TAG)
.commit();
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
// These calls adds stuff directly to the Bundle instance, be it basic variables, lists or Parcelable classes.
mShoppingList.onSaveInstanceState(outState);
mFilteredRecipes.onSaveInstanceState(outState);
getSupportFragmentManager().putFragment(outState, SAVE_RESTORE_MAIN_FRAGMENT, mMainFragment);
}
private void restoreState(Bundle savedInstanceState)
{
// These calls restores stuff directly from the Bundle instance, be it basic variables, lists or Parcelable classes.
mShoppingList.onRestoreInstanceState(savedInstanceState);
mFilteredRecipes.onRestoreInstanceState(savedInstanceState);
mMainFragment = (MainFragment) getSupportFragmentManager().getFragment(savedInstanceState, SAVE_RESTORE_MAIN_FRAGMENT);
}