TaskStackBuilder, multiple distinct PendingIntents, Notifications - android

I have problem with combination of TaskStackBuilder and distinct PendingIntents for notifications. Let me explain what it is about.
I have an IntentService which creates notification when something comes up. Sometimes it creates several independent notification. Why I don't merge notifications like Google said? Because each notification should open the same Activity, BUT with different extras in passed Intent. So here what a do:
Create new Intent with extras:
Intent notificationIntent = new Intent(this, ItemActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
notificationIntent.putExtra(ItemActivity.URL_KEY, feed.getUrl());
notificationIntent.putExtra(ItemActivity.FEED_ID, feed.get_id());
notificationIntent.putExtra(ItemActivity.TITLE, feed.getTitle());
And now is the tricky part - I want to open ItemActivity with proper back stack, this mean when I press Back button or Up in AppBar, I want to come back to parent Activity. So here's what I do (according to Google doc: http://developer.android.com/training/notify-user/navigation.html):
In AndroidManifest.xml I have:
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ItemActivity"
android:label="#string/item_activity"
android:launchMode="singleTask"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
Then creating back stack:
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(ItemActivity.class);
stackBuilder.addNextIntent(notificationIntent);
And PendingIntent:
PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
Finnaly - notfication:
NotificationCompat.Builder builder = (NotificationCompat.Builder) new NotificationCompat.Builder(this)
(...)
.setContentIntent(notificationPendingIntent)
(...)
mNotifyManager.notify(feed.get_id(), builder.build());
In this scenario, app creates different notification with proper back stack, BUT with the same PendingIntent.
When I replace requestCode while getting PendingIntent with, for example, feed.get_id() (or another different number for each PendingIntent like System.currentTimeMillis()) then tapping on notification brings Activity with proper Intent (distinct PendingIntent for each notification), BUT without back stack - Back and Up buttons close the applications.
I've tried deleting android:launchMode="singleTask" from manifest, do not adding flags when I've creating new Intent, read half of Internet, hundred of StackOverflow posts and nothing.
I haven't managed working combination of this two scenario - that is: open Activity with proper Intent and back stack.
In advance please do not write something like "just override onBackPressed and start activity from there" - I want to know what I'm doing wrong here and how to achieve this with proper tools.

After 4 hours of work finally i found the solution. It is very simple just you need to provide a different request codes for pending intents.
PendingIntent pendingIntent = stackBuilder.getPendingIntent((int) gcmMessage.getId() /*Unique request code for each PendingIntent*/, PendingIntent.FLAG_UPDATE_CURRENT);
Documentation for getPendingIntent() method TaskStackBuilder.getPendingIntent(int, int)

Related

Bring a singleInstance Activity Back Using a Notification

I have a normal activity, let's call it A, and it's the first activity presented to the user when opening the app. Activity A has a button that launches an activity, let's call it B, with launchMode set to singleInstance in the manifest. Activity B does some processing.
Users can press the home button and open the app again, which will present them with the starting activity, aka activity A. If users click on the button again (in activity A), they will be presented with activity B. In the case, activity B will not be restarted, i.e., onCreate will not be called, since it is singleInstance, which is what I want.
I want to make it easier for the users to come back to activity B once it started if they pressed the home button. So, I created an ongoing notification that lets the users bring activity B back. The ongoing notification will be canceled once activity B is finished.
Now to the problem, clicking the ongoing notification recreates activity B again, i.e., the onCreate is being called again. I don't know why the behavior here is not the same as clicking on the button on activity A. Here is how I created the notification:
Intent notifyIntent = new Intent(mContext, CallActivity.class);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(
mContext, 0, notifyIntent, PendingIntent.FLAG_NO_CREATE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_call_white_18)
.setContentTitle(title)
.setContentText(text)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(notifyPendingIntent);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext);
notificationManager.notify(NOTIFICATION_ID, builder.build());
Can anyone tell me why this is not working and how can I get it to work the way I described?
EDIT
Here is a snippet of my manifest:
<application
android:name="com.mnm.caller.IMApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="#style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name="com.mnm.caller.activities.SplashActivity"
android:configChanges="orientation|keyboardHidden"
android:noHistory="true"
android:screenOrientation="portrait"
android:theme="#style/AppTheme.Splash">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.mnm.caller.activities.LoginActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="#style/AppTheme.NoTitleBar"
android:screenOrientation="portrait" />
<activity
android:name="com.mnm.caller.activities.HomeActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="#style/AppTheme.NoTitleBar"
android:screenOrientation="portrait" />
<activity
android:name="com.mnm.caller.activities.CallActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleInstance"
android:screenOrientation="portrait"
android:theme="#style/AppTheme.NoTitleBar" />
<service
android:name="com.mnm.caller.services.IMFirebaseMessagingService"
android:stopWithTask="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
</application>
In a comment you wrote:
I'm actually developing a calling app. Activity A is the home screen,
while B is the calling activity. I want to let the users navigate back
to the home screen during a call, so I intentionally and knowingly
chose to create two instances of the app in recent apps (you can see
the exact behavior in the default phone app on Android)
If this is the case, you probably do want to have 2 separate tasks that you can switch between. To do this, you need to use taskAffinity. Your CallActivity should have launch mode of singleTask or singleInstance and you should set android:taskAffinity="call" or something like that. In order not to confuse your users, you should also provide a different android:label and a different android:icon for CallActivity so that when this task appears in the list of recent tasks it looks different than the rest of your app. Make sure that when you launch CallActivity you also set FLAG_ACTIVITY_NEW_TASK. You should also set this flag when you build the Notification.
You need to set FLAG_ACTIVITY_SINGLE_TOP to the Intent instance (in your case notifyIntent) before creating the PendingIntent object.
In other words:
Intent notifyIntent = new Intent(mContext, CallActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(
mContext, 0, notifyIntent, PendingIntent.FLAG_NO_CREATE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.baseline_call_white_18)
.setContentTitle(title)
.setContentText(text)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(notifyPendingIntent);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(mContext);
notificationManager.notify(NOTIFICATION_ID, builder.build());

How to preserve current back stack (or task) when notification is clicked?

In my application, I create a notification which starts Details Activity. I want to add this activity to top of current task (or back stack). For example I expect application task (back stack) to behave like this:
but I get this:
I have not used FLAG_ACTIVITY_CLEAR_TASK and FLAG_ACTIVITY_NEW_TASK flags. What should I do?
Edit: First picture is just an example. I think the the question's title is completely explicit. I want to add Details Activity on top of current stack, and not to start with a new task.
This is how I create the PendingIntent:
// Details activity intent
Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra(Com.KEY_ID, event.getId());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
And this is the manifest:
<activity
android:name=".MainActivity"
android:label="#string/app_name_system"
android:launchMode="singleTop"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".NoteActivity"
android:label="#string/app_name_system"
android:theme="#style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".DetailsActivity"
android:launchMode="singleTop"
android:label="#string/app_name_system"
android:theme="#style/AppTheme.NoActionBar" />
I found this link: Preserving Navigation when Starting an Activity. Although it does not provide the exact solution for my question, but it produces the desired result.
The link describes that we should start DetailsActivity in a new task with a different affinity.
Manifest:
<activity
android:name=".DetailsActivity"
android:excludeFromRecents="true"
android:label="#string/app_name_system"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="#style/AppTheme.NoActionBar" />
PendingIntent creation:
Intent intent = new Intent(context, DetailsActivity.class);
intent.putExtra(Com.KEY_ID, event.getId());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
You can use PendingIntent.getActivities instead of PendingIntent.getActivity to build you stack.
Intent mainActivityIntent = new Intent(context,MainActivity.class);
mainActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent noteActivityIntent= new Intent(context,NoteActivity.class);
noteActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intent detailActivityIntent= new Intent(context,DetailActivity.class);
final PendingIntent pendingIntent = PendingIntent.getActivities(ctx, UNIQUE_REQUEST_CODE++,
new Intent[] {mainActivityIntent,noteActivityIntent,detailActivityIntent},PendingIntent.FLAG_UPDATE_CURRENT);
This should solve your purpose.
Refer this link for more details Back to main activity from notification-created activity
You need to use getActivities() and specify the entire navigation path in the third argument which accepts an array of intents. You need to construct that array in the order your app navigation should work, i.e., leftmost being the root/main activity and further down the levels from there.
Something like this
Intent mainActivityIntent = new Intent(context, MainActivity.class);
mainActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // only specify new task flag for MainActivity
Intent noteActivityIntent= new Intent(context,NoteActivity.class);
Intent detailsActivityIntent = new Intent(context, DetailsActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivities(context, 0,
new Intent[] {mainActivityIntent,noteActivityIntent,detailsActivityIntent},PendingIntent.FLAG_UPDATE_CURRENT);
Note that : If your application is already active, once you press back android will take you back to your NoteActivity and then MainActivity. It will restart the activities if they are already there or create new otherwise.
I wanted the exact opposite (which is the default behaviour).
It turns out setting noHistory="true" or/and calling finish() on the Activity launched by the notification, keeps the existing backstack.
So basically the solution is either to not keep the Activity into the backstack or create a 'NotificationActivity' which will not be kept and will be in charge of starting the Activity you want (DetailsActivity in your example).
A solution from the android docs could work for you. It's worked for me, but I only had a single parent-child back stack.
Manifest with parent relationships:
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- MainActivity is the parent for NoteActivity-->
<activity
android:name=".ResultActivity"
android:parentActivityName=".MainActivity" />
...
</activity>
<!-- NoteActivity is the parent for DetailsActivity -->
<activity
android:name=".DetailsActivity "
android:parentActivityName=".NoteActivity " />
...
</activity>
Creating the pending intent:
// Create an Intent for the activity you want to start
Intent resultIntent = new Intent(this, DetailsActivity.class);
// Create the TaskStackBuilder and add the intent, which inflates the back stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
// Get the PendingIntent containing the entire back stack
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);

Notification intent closes app on up/back-navigation

My App has the following activity hierarchy:
A -> B -> C
So far, everything works well. Now I want to add a Notification. When the user clicks on it, I want to close all child activities of A and start an instance of C, which has A as parent, without B as intermediate Activity:
A -> C
To achieve this I used Androids TaskStackBuilder as shown here to build the notification. But this results in the app being closed (without error, checked logcat) when the user presses the back button or uses the up-navigation in the top bar. What did I do wrong?
...
Intent intent = new Intent(NotificationService.this, C.class);
Intent parentIntent = new Intent(NotificationService.this, A.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(NotificationService.this);
stackBuilder.addNextIntent(parentIntent);
stackBuilder.addNextIntent(intent);
PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
notificationBuilder.setContentIntent(pendingIntent);
...
My AndroidManifest.xml looks like this:
<activity android:name=".activities.A"
android:label="#string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".activities.B"
android:parentActivityName=".activities.A"
android:launchMode="singleTop">
</activity>
<activity android:name=".activities.C"
android:parentActivityName=".activities.B">
</activity>
Intent intent = new Intent(NotificationService.this, C.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(NotificationService.this);
stackBuilder.addParentStack(B.class);
stackBuilder.addNextIntent(intent);
Try this. I Haven't tested this but addParentStack(B.class) should add parent declared for B in AndroidManifest.xml
Okay, I was using OxygenOS with an OnePlus One when I discovered this issue. Yesterday, there were some updates and now it works without changing anything.
It works with Intent and with ParentStack as Adeel Ahmad suggested. Maybe it was just an OS issue. Anyways, problem solved. Thanks for your time.

Android notification Reorder to front doesn't work. Copied from APP that it has worked in

I've copied a code from dummy project testing Services and Notifications in which reoder to font worked like a charm.
Here is the code for notification (pretty same as in tutorials)
NotificationCompat.Builder nBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setAutoCancel(true)
.setContentTitle("Request awaiting")
.setContentText("There is a service request awaiting for Your reaction");
Intent resultIntent = new Intent(this, MainActivity.class);
resultIntent.setAction(Intent.ACTION_MAIN);
resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resultIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);//stackBuilder.getPendingIntent(0, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
nBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = nBuilder.build();
mNotificationManager.notify(1, notification);
Manifest looks like :
<application android:allowBackup="true" android:icon="#drawable/ld"
android:label="#string/app_name" android:theme="#style/AppTheme">
<activity
android:name="com.iraasta.cloudcab.driver.MainActivity"
android:launchMode="singleTop"
android:label="#string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
It looks exactly same in my other app, in which it perfectly brings app to front like when selecting from the home screen.
But in this one it calls OnCreate every time and loses whole state.
To bring your application to the front (if it is in the background) in whatever state it is in, or to launch your app (if it isn't aleady running), you need to launch your "root activity" (the one with ACTION=MAIN and CATEGORY=LAUNCHER) and set the flag Intent.FLAG_ACTIVITY_NEW_TASK. This will launch the activity in a new task (if your app isn't already running), or bring the existing task to the foreground (if your app is already running).
Since you are doing this from a Notification, you should be setting Intent.FLAG_ACTIVITY_NEW_TASK in the Intent you pass to the Notification. However, Android is nice enough to set this flag for you if you forget to do it.
I'm not exactly sure why setting Intent.FLAG_ACTIVITY_REORDER_TO_FRONT caused your problem, but I can imagine that this somehow confuses Android.
Not using any flag fixed it.
To people looking at this post in future.
If Your app uses single activity and You want to bring it to front after clicking the Notification just leave flags as integer 0.
It fixed my problem.
Thanks for suggestions
Bounty goes to the person that will explain why is this flag ruining everything.
Setting android:launchMode="singleTask" in a manifest for an activity you wanna bring to the front works for me

Open Activity with backwards to MainActivity in my app

I'm stuck with some activity's flow issue. The desired behaviour is the following:
From time to time, the user receives a notification. When this notification is clicked, a new Activity is opened with some information in it. In this Activity, there's a button whose purpose is to redirect the user to another Activity where more detailed information is showed. When the user is in the details Activity and presses the back button (or the back button in the ActionBar) this one is closed and the Main Activity is showed (this one is different from the one I mentioned in first place).
Everything works fine except from the last part. When the user presses the back button the application is closed and it is showed the Home Screen. Why is that happening?
Here is my AndroidManifest.xml:
<activity
android:name=".MainActivity">
</activity>
<activity
android:name=".DetailActivity"
android:label="#string/title_detail_activity"
android:parentActivityName="solar.panik.MainActivity" >
<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="solar.panik.MainActivity" />
</activity>
<activity
android:name=".NotificationActivity"
android:theme="#style/NotificationActivity"
android:excludeFromRecents="true">
</activity>
Here is the onClick code for the button that starts the DetailActivity from the NotificationActivity:
Intent intent = new Intent(NotificationActivity.this, DetailActivity.class);
startActivity(intent);
finish();
Thanks in advance
When you start your app from something other than the launcher, you'll need to pass the back stack with your intent.
Android tutorial
Scroll down to Create Back Stack When Starting Activity().
So in your case:
// Intent for the activity to open when user selects the notification
Intent detailsIntent = new Intent(this, DetailActivity.class);
// Use TaskStackBuilder to build the back stack and get the PendingIntent
PendingIntent pendingIntent =
TaskStackBuilder.create(this)
// add all of DetailActivity's parents to the stack,
// followed by DetailsActivity itself
.addNextIntentWithParentStack(detailsIntent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent);
Check out this answer.
OLD ANSWER BELOW
Make sure in your details activity that onBackPressed() method isn't overridden (or defined).
If that's not it, try adding this to your manifest and remove your current ".MainActivity" Activity and tags. (Or replace it with this)
<activity
android:name="solar.panik.MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
You have to declare it as MAIN, so the Up button knows where to go. "Back" will take you to the next Activity up on the hierarchy.
Hope that helps.
Do this in your detailed activity. The one you click the back button in.
#Override
public void onBackPressed() {
Intent intent = new Intent(DetailActivity.this, MainActivity.class);
startActivity(intent);
finish();
}

Categories

Resources