I have an app that contains a Notification. I want to set the "back" navigation behavior after the Notification is selected. I have two Activities: MainActivity and GoogleFormActivity. The Notification starts the GoogleFormActivity and I want the back button to go back to MainActivity. I tried this to no avail. Here's my code:
Manifest.xml:
<activity
android:name="com.ican.activities.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>
and
<activity
android:name="com.ican.activities.GoogleFormActivity"
android:label="#string/title_activity_google_form"
android:parentActivityName="com.ican.activities.MainActivity" >
<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.ican.activities.MainActivity" />
</activity>
Here's the code when my Notification is selected:
Intent resultIntent = new Intent(this, GoogleFormActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this).
addParentStack(GoogleFormActivity.class).
addNextIntent(resultIntent);
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
int mId = 1;
mNotificationManager.notify(mId, mBuilder.build());
As far as I can tell, this looks exactly like the Android docs. But when I press back after GoogleFormActivity starts, the app navigates out of the app to the home screen. I don't understand what's going on. Any ideas?
Edit I must apologize folks. This same code that didn't work yesterday now works today. I swear something is up with Android Studio. This is the second or third time this sort of thing has happened.
When you tap on back button it will close current activity. it means it will not open any activities. just closing the current activity.. The activities are arranged in a stack (the back stack), in the order in which each activity is opened. If the user presses the Back button, the current activity is popped from the stack and destroyed. The previous activity in the stack is resumed, if the stack contains that activity. When an activity is destroyed, the system does not retain the activity's state. So, when you are coming from notifications you will open only one activity. it will not goes back to main activity, bez it was not opened. you have to use Intent to navigate to particular activity. docs
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
super.onBackPressed();
Intent intent=new Intent(this,MainActivity.class);
startActivity(intent);
finish();
}
Add this in your GoogleFormActivity
try to do this,
getFragmentManager().popBackStack();
fm.beginTransaction().add(R.id.main, newFragment).addToBackStack("fragBack").commit();
Related
I need to create notification and it is working fine with the code given below. And when I click the notification it will redirect to a activity, say activity2. I can enter this activity2 from other activities or fragments, say activity1, fragment1 etc. While I'm staying on application and click the notification, it will redirect to activity2 and while I press back button it will go to the previous activity or fragment. But if I kill the application and clicked the notification, it will open up activity2, but on pressing back button, the app will exit. I need it to be redirected to activity1. How can I do it.
Thanks in advance.
void createDownloadNotification(String title) {
if (title != null) {
Intent intent = new Intent(context, SecondActivity.class);
intent.putExtra(EXTRA_STRING, title);
intent.putExtra("id", i);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pIntent = PendingIntent.getActivity(context, (int) System.currentTimeMillis(), intent, 0);
Notification myNotification = new Notification.Builder(context)
.setContentTitle(title)
.setContentText("Some text....")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pIntent)
.setAutoCancel(false).build();
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(i + 1, myNotification);
i = i + 1;
}
}
With your current implementation and the intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); part, the behavior in question happens by design. Understanding tasks and backstacks would explain that.
You would want to launch an activity with conditions in onBackPressed of activity2, detecting whenever it has arrived from a notification while recreating it if the activity doesn't exist.
Going through activity launch modes would help. You could use that with activity1 for your implementation.
Understand android activity launchmode is a good blog article, the only non-android developers link in this answer.
Try this code in your manifest
<activity
android:name="com.example.myfirstapp.DisplayMessageActivity"
android:label="#string/title_activity_display_message"
android:parentActivityName="com.example.myfirstapp.MainActivity" >
<!-- Parent activity meta-data to support 4.0 and lower -->
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myfirstapp.MainActivity" />
</activity>
Basically you need to setup parent activity in manifest file and create stacks for your resultant activity.'
Here is the very nice tutorial to get your started.
It is working as per the definition here: FLAG_ACTIVITY_CLEAR_TOP
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.
The intent you put in your PendingIntent for the notification, closes all the open Activities and opens SecondActivity. If you remove that flag from your Intent, it won't close the others.
In addition to that, you may add parent Activity to your SecondActivity so that it knows where to go when the user presses back and there was nothing open before the notification was clicked.
Simple Method
pass a bool via the intent you use to get to the activity2
from notification then pass bool true
from open activity then pass bool false
and get bool value in activity 2 and set accordingly
after that set onBackPressed in Activity 2 as this
#Override
public void onBackPressed() {
if (fromNotification) {
Intent i = new Intent(Activity2.this,
Activity1.class);
startActivity(i);
finish();
} else
super.onBackPressed();
}
I have two activities A and B where A is the parent of B. Now I show a notification that launches B. when I tap on the notification, B launches. Then I click on the up button. It works finr when activity A is in the backstack but otherwise the app Just closes and does not launch activity A.
My Setup.
I have declared A as Parent of B in Manifest with A in SingleTop launchMode
<activity
android:name=".A"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:launchMode="singleTop"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="<packegeName>.Home" />
</intent-filter>
</activity>
<activity
android:name=".B"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:parentActivityName=".A"
android:theme="#style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".A" />
</activity>
This is the notification Intent :
Intent intent = new Intent(this, B.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
In Activity B :
#Override
public void onCreate(Bundle savedInstanceState) {
....
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
....
}
What I've tried
In order to debug I've tried manually triggering this up navigation in Activity B's onResume, with all of these :
1)
Intent upIntent = NavUtils.getParentActivityIntent(this);
upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
2)
onNavigateUp();
3)
NavUtils.navigateUpFromSameTask(this);
But none of these seem to work when Activity A is not in the Backstack, the app just exits.
I have scoured SO and Google (even bing!) looking for an answer and tried everything I could find to no avail. As there is no error or log output, I also have no idea how to debug this.
Any solution or tips on finding out the issue will be greatly appreciated.
Thanks.
#pablobu's answer worked for me but I want to add a little more explanation of what I found.
Damn those docs are confusing
Where I initially got confused was due to this :
If your activity provides any intent filters that allow other apps to start the activity, you should implement the onOptionsItemSelected() callback such that if the user presses the Up button after entering your activity from another app's task, your app starts a new task with the appropriate back stack before navigating up.
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
// Add all of this activity's parents to the back stack
.addNextIntentWithParentStack(upIntent)
// Navigate up to the closest parent
.startActivities();
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
NavUtils.navigateUpTo(this, upIntent);
}
I was of the impression that this would generate the backstack for me. But there is a difference.
NavUtils.shouldUpRecreateTask(this, upIntent) will return true only if the current activity is inside another app's task. As for launching from a notification, this will be false, so the task-stack-building code does not execute.
The correct way, is to build the backstack when generating the notification and passing it as a pending intent. From this page :
when a notification takes the user to an activity deep in your app hierarchy, you can use this code to create a PendingIntent that starts an activity and inserts a new back stack into the target task.
Intent detailsIntent = new Intent(this, DetailsActivity.class);
// Use TaskStackBuilder to build the back stack and get the PendingIntent
PendingIntent pendingIntent =
TaskStackBuilder.create(this)
// add all of DetailsActivity'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);
Try the approach described for Synthesize a new Back Stack for Deep Links
You will have use TaskStackBuild to build the back stack and get the PendingIntent when starting on Activity B.
Check this video from Android Design Patterns explains it simple.
Override the public boolean shouldUpRecreateTask(Intent targetIntent) method in activity and always return true.
Launching an activity by clicking the app icon in launcher, should bring the activity to foreground just like picking it from history. So no onCreate call should exist.
However,if we try to do this after starting the activity by clicking a notification, then the launcher just starts another instance of the activity.
What flags must I add so that the launcher keeps working as expected ( resuming the exact state of the app from background )?
I'll post the essential code.
This starts the notification:
Intent resumeIntent = new Intent(this, MainActivity.class);
PendingIntent resumePendingIntent = PendingIntent.getActivity(this, 2,
resumeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification resumeNotification = new Notification.Builder(this).setContentTitle(
"Resume style")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(resumePendingIntent)
.build();
NotificationManager notificationManager = (NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
notificationManager.notify(1, launcherNotification);
This is how the manifest activity looks:
<activity
android:name="com.example.ihatenotifiicationsapp.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>
This resumeIntent will be automatically added the FLAG_ACTIVITY_NEW_TASK by Android. This flag will allow the resuming of the application from background if present.
All nice until here, but, If after you click this notification and resume the app, you click the app from launcher then Android launches another instance of MainActivity.
This breakes my application and the backstack ( you will have 2 MainActivity in the stack, weird for user ).
The funnies thing is this happens ( clicking the launcher behaviour to launch another instance ) only after you click the notification.
You can use the Flag android:launchMode="singleTask" in your activity Tag if you want this behavior. This prevents the OS from launching any other Instance, if there is currently one Active. See the SDK Doku for more information on launchbehaviors here
I edited this Answer corresponding to Emanuel Moecklin Comment below. Mixed the lauchModes up.
Excerpt from the Doku:
The system creates the activity at the root of a new task and routes
the intent to it. However, if an instance of the activity already
exists, the system routes the intent to existing instance through a
call to its onNewIntent() method, rather than creating a new one.
Try
Intent resumeIntent = new Intent(this, MainActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP;
If set, and the activity being launched is already running in the
current task, then instead of launching a new instance of that
activity, all of the other activities on top of it will be closed and
this Intent will be delivered to the (now on top) old activity as a
new Intent.
http://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_CLEAR_TOP
I have a background service that can create persistent notifications. When a user clicks on a notification it starts an activity. The user may then press the Home button causing this activity to be stopped.
If the user then clicks on the notification again I want to restart (and show) the same activity (if Android hasn't already destroyed it - which I appreciate it can), so that the GUI state is as the user left it.
How can I achieve this?
Manifest entry for activity
<activity android:name=".mrwidget.MrWidget" android:theme="#android:style/Theme.NoTitleBar" android:launchMode="singleTask">
<intent-filter><action android:name="android.intent.action.MAIN"> </action>
<category android:name="android.intent.category.LAUNCHER"></category>
</intent-filter>
</activity>
What I'm after is effectively the same behaviour you get if the activity is selected from the 'recent apps list' you get when you hold down the Home button.
To elaborate - when I click on a notification the first time I successfully create the activity using:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mService.startActivity(i);
If I pressed the Home button to switch away from this activity and then held the Home button down to bring up the "recent apps list" and selected the activity from there it works as expected - activity is shown in the state it was left, all good.
I want this same behaviour when I also select the same notification any subsequent time, i.e. it displays the previously created activity. I attempted to do this using:
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
activity.startActivity(intent);
where 'activity' is a reference to the activity created the first time the user selected the notification, but doing this doesn't cause the activity to be displayed.
To have the same behaviour as "bringing the app to the foreground", you just need to create an Intent that contains the root activity and set Intent.FLAG_ACTIVITY_NEW_TASK. The root activity is the activity that has ACTION=MAIN and CATEGORY=LAUNCHER in the manifest. Like this:
Intent intent = new Intent(this, MyRootActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Try this code when you display the notification:
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, "Title", "Text", pendingIntent);
Replace MainActivity with the class of the Activity you want to launch, and the strings for the notification title and text as you need it.
My app has activity stack like below
A: loading activity
B: main activity
C: detail activity
and manifest is like this
<activity A>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity B android:launchMode="singleTask"/>
<activity C/>
when I start app from launcher, it acts as A -> B -> C . press Home button at activity C and recall it from recent app list(long press Home), C is shown. this is OK.
but when I starts app from Notification, as I don't want to show loading screen, starts activity B. so, user can navigate B -> C.
but when user press Home at activity C and select app from recent app list, B is restarted and state is not preserved. So C activity is always disappears.
I've tried many flag options, but I didn't find a solution. What I want is that app behaves just like when user starts app from launcher.
I created pending intent for notification like this. In my app, I should use Intent.FLAG_ACTIVITY_CLEAR_TOP for activity B.
Notification notice = new Notification(R.drawable.icon_notification, context.getString(R.string.app_name), System.currentTimeMillis());
Intent intent = new Intent(context, B.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.putExtras(i);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
I think the recent app list send same intent with notification, so always activity B is called. But when I launch app from launcher, it just send me to the last activity C, not B.
plz help me. :(
UPDATE
I've solved this problem like below.
Add new activity D with different task affinity. This activity just starts B(main activity)
In notification, start activity D
In activity D, start activity B with some flags and finish itself
if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0 ) {
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | Intent.FLAG_ACTIVITY_NEW_TASK);
} else {
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
And I've registered activity D like below
<activity
android:name="D"
android:clearTaskOnLaunch="true"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleInstance"
android:noHistory="true"
android:taskAffinity="xxx"
android:theme="#android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
Since the notification has launched the app, it's now part of the history - you probably want to only handle it when it's actually tapped. Check for:
(getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0
If it's true, the activity is being opened from the history, not the notification's intent.
I suppose that you deal with 2 separate tasks with their own activities back stacks because of using the Intent.FLAG_ACTIVITY_NEW_TASK. First task is created when you start A from the launcher, while the second being created when you start B from notification. If so then you should be sure that 2 separate tasks is what you really need.