I have an app with a splash screen Activity, followed by a main Activity. The splash screen loads stuff (database, etc.) before starting the main Activity. From this main Activity the user can navigate to multiple other child Activities and back. Some of the child Activities are started using startActivityForResult()
, others just startActivity()
.
The Activity hierarchy are as depicted below.
| Child A (startActivityForResult)
| /
|--> Splash --> Main -- Child B (startActivityForResult)
| ^ \
| | Child C (startActivity)
| \
| This Activity is currently skipped if a Notification is started
| while the app is not running or in the background.
I need to achieve the following behavior when clicking a Notification:
- The state in the Activity must be maintained, since the user has selected some recipes to create a shopping list. If a new Activity is started, I believe the state will be lost.
- If the app is in the Main Activity, bring that to the front and let me know in code that I arrived from a Notification.
- If the app is in a child Activity started with
startActivityForResult()
, I need to add data to an Intent before going back to the Main Activity so that it can catch the result properly. - If the app is in a child Activity started with
startActivity()
I just need to go back since there is nothing else to do (this currently works). - If the app is not in the background, nor the foreground (i.e. it is not running) I must start the Main Activity and also know that I arrived from a Notification, so that I can set up things that are not set up yet, since the Splash Activity is skipped in this case in my current setup.
I have tried lots of various suggestions here on SO and elsewhere, but I have not been able to successfully get the behavior described above. I have also tried reading the documentation without becoming a lot wiser, just a little. My current situation for the cases above when clicking my Notification is:
- I arrive in the Main Activity in
onNewIntent()
. I do not arrive here if the app is not running (or in the background). This seems to be expected and desired behavior. - I am not able to catch that I am coming from a Notification in any child Activities, thus I am not able to properly call
setResult()
in those Activities. How should I do this? - This currently works, since the Notification just closes the child Activity, which is ok.
- I am able to get the Notification Intent in
onCreate()
by usinggetIntent()
andIntent.getBooleanExtra()
with a boolean set in the Notification. I should thus be able to make it work, but I am not sure that this is the best way. What is the preferred way of doing this?
Current code
Creating Notification:
The Notification is created when an HTTP request inside a Service returns some data.
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getNotificationIcon())
.setAutoCancel(true)
.setColor(ContextCompat.getColor(context, R.color.my_brown))
.setContentTitle(getNotificationTitle(newRecipeNames))
.setContentText(getContentText(newRecipeNames))
.setStyle(new NotificationCompat.BigTextStyle().bigText("foo"));
Intent notifyIntent = new Intent(context, MainActivity.class);
notifyIntent.setAction(Intent.ACTION_MAIN);
notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
/* Add a thing to let MainActivity know that we came from a Notification. */
notifyIntent.putExtra("intent_bool", true);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111, builder.build());
MainActivity.java:
@Override
protected void onCreate(Bundle savedInstanceState)
{
Intent intent = getIntent();
if (intent.getBooleanExtra("intent_bool", false))
{
// We arrive here if the app was not running, as described in point 4 above.
}
...
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case CHILD_A:
// Intent data is null here when starting from Notification. We will thus crash and burn if using it. Normally data has values when closing CHILD_A properly.
// This is bullet point 2 above.
break;
case CHILD_B:
// Same as CHILD_A
break;
}
...
}
@Override
protected void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
boolean arrivedFromNotification = intent.getBooleanExtra("intent_bool", false);
// arrivedFromNotification is true, but onNewIntent is only called if the app is already running.
// This is bullet point 1 above.
// Do stuff with Intent.
...
}
Inside a child Activity started with startActivityForResult()
:
@Override
protected void onNewIntent(Intent intent)
{
// This point is never reached when opening a Notification while in the child Activity.
super.onNewIntent(intent);
}
@Override
public void onBackPressed()
{
// This point is never reached when opening a Notification while in the child Activity.
Intent resultIntent = getResultIntent();
setResult(Activity.RESULT_OK, resultIntent);
// NOTE! super.onBackPressed() *must* be called after setResult().
super.onBackPressed();
this.finish();
}
private Intent getResultIntent()
{
int recipeCount = getRecipeCount();
Recipe recipe = getRecipe();
Intent recipeIntent = new Intent();
recipeIntent.putExtra(INTENT_RECIPE_COUNT, recipeCount);
recipeIntent.putExtra(INTENT_RECIPE, recipe);
return recipeIntent;
}
AndroidManifest.xml:
<application
android:allowBackup="true"
android:icon="@mipmap/my_launcher_icon"
android:label="@string/my_app_name"
android:theme="@style/MyTheme"
android:name="com.mycompany.myapp.MyApplication" >
<activity
android:name="com.mycompany.myapp.activities.SplashActivity"
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="com.mycompany.myapp.activities.MainActivity"
android:label="@string/my_app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" >
</activity>
<activity
android:name="com.mycompany.myapp.activities.ChildActivityA"
android:label="@string/foo"
android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.mycompany.myapp.activities.MainActivity" >
</meta-data>
</activity>
<activity
android:name="com.mycompany.myapp.activities.ChildActivityB"
android:label="@string/foo"
android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
android:screenOrientation="portrait" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.mycompany.myapp.activities.MainActivity" >
</meta-data>
</activity>
...
</manifest>
onPause()
. – Tormoria