Build a PendingIntent with a back stack - android

I have three activities (Home, Search, Destination), with which I could describe my UX flow. Home activity is my launcher activity, then its the search activity, which is happening to be the parent of Destination activity. So basically what I am trying to achieve is to have a notification, which starts the Destination activity and then when i press the back button I am supposed to go back to the Search activity and then to the Home, but the problem is that once I hit the back button from the Destination activity, the whole stack goes to the background...
In my manifest file, I`ve defined a parent activity for each of the child activities, like it is described here https://developer.android.com/training/notify-user/navigation
This is how my code looks like, when building the pending intent:
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(this, DestinationActivity.class);
notificationIntent.putExtra("test", destination);
//Intent testIntent = new Intent(this, SearchActivity.class);
notificationIntent.putExtra(DestinationAdapter.DESTINATION, destination);
// notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Push the content Intent onto the stack.
//stackBuilder.addNextIntentWithParentStack(testIntent);
stackBuilder.addNextIntentWithParentStack(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0,
PendingIntent.FLAG_UPDATE_CURRENT);
Could you please help me a bit? Am I doing something wrong?

your code should work, as described here:
https://developer.android.com/training/notify-user/navigation#java
make sure you define parent activity in manifest where relevant:
<activity
android:name=".DetailActivity"
android:parentActivityName=".MainActivity"

Related

Terminate all other previous activities when splash activity starts

In my app I always want user to start from Splash screen. For example, my app may be open in background and some notification pops up which starts splash activity. This should terminate all previous activities which were running.
I have accomplished this by storing list of all running activities references. And when splash activity starts it just calls
for(runningActivity : runningActivitiesList) {
runningActivity.finish();
}
This solution works well. However, Android Studio gives me warning of memory leaks when storing references to activities.
Can someone please suggest me a better approach which avoids memory leaks?
Maybe enough is to start Activity with clear stack:
Intent intent = new Intent(context, clazz);
intent.setFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Tried all other options, but only thing worked for me is:
final Intent intent = new Intent(applicationContext, SplashActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
| Intent.FLAG_ACTIVITY_NEW_TASK);
return IntentCompat.makeRestartActivityTask(intent.getComponent());
Please NOTE: This solution is also not full proof. Since, when I open my app through Google Play Store it launches splash activity even when another instance of app is running in background. Thus I end up having 2 instances of the same activity.
You don't need to finish all running/previous applications.Instead you can start your activity using TaskBuilder api to handle proper back navigation.
Open your activity with this:
private static PendingIntent makePendingIntent(#NonNull Context context, #NonNull Intent resultIntent) {
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack
stackBuilder.addParentStack(YourActivity.class);
// Adds the Intent to the top of the stack
stackBuilder.addNextIntent(resultIntent);
// Gets a PendingIntent containing the entire back stack
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
In your manifest file define the parent activity of YourActivity.class as:
<activity
android:name=".YourActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
</activity>
Follow these urls for more details: http://developer.android.com/reference/android/support/v4/app/TaskStackBuilder.html http://developer.android.com/guide/components/tasks-and-back-stack.html http://www.programcreek.com/java-api-examples/index.php?api=android.app.TaskStackBuilder
In android manifest set:
android:launchMode="singleTop"
For notifications generated from your app you can use #mac229's flags in #Nischal's pending intent.

Up Navigation not launching parent activity

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.

Android TaskStackBuilder ugly transition

What on earth is wrong with TaskStackBuilder that it uses this ugly transition when starting new activities.:
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(this)
.addParentStack(ActivityB.class)
.addNextIntent(new Intent(this, ActivityB.class));
taskStackBuilder.startActivities();
This is basically standard code that google however if you run this code you will see a super ugly transition when going to ActivityB.
I guess its because its a new Task. But I dont want it to look like this is there anything that I can do ?
Thanks !
After digging inside TaskStackBuilder's implementation, the problem is that it forces adding Intent.FLAG_ACTIVITY_CLEAR_TASK to the 1st intent in the stack, which makes that ugly transition, so use the following to start the stack:
Intent[] intents = TaskStackBuilder.create(this)
.addParentStack(ActivityB.class)
.addNextIntent(new Intent(this, ActivityB.class))
.getIntents();
if (intents.length > 0) {
intents[0].setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// Or any other flags you want, but not the `.._CLEAR_..` one
}
// `this` inside current activity, or you can use App's context
this.startActivities(intents, ActivityOptions.makeSceneTransitionAnimation(this).toBundle());
The idea here is to still use the TaskStackBuilder for creating your intents' stack, then remove the weird Intent.FLAG_ACTIVITY_CLEAR_TASK that the TaskStackBuilder adds to the 1st intent, then start the activities manually using any Context you want.

How can I synthesise a Task Stack using Intents with appropriated data?

Lets say I have 3 Activities defined as follows:
ACTIVITY PARENT
ClassActivity -
StudentActivity ClassActivity
StudentExamActivity StudentActivity
In the application's normal workflow, the user should be able to select a student in ClassActivity. This will start StudentActivity passing the student's content uri, represented here as <STUDENT_ID_URI> (to be handled by a ContentResolver), in the intent to specify what should be displayed. Similarly, an exam can be selected in StudentActivity by starting StudentExamActivity and passing its uri, represented as <EXAM_ID_URI>.
The question is: what if I need to start StudentExamActivity directly (e.g. from a notification or an external application)? How can I assure that when StudentExamActivity is started with the <EXAM_ID_URI> data, its parent (StudentActivity) will be started with <STUDENT_ID_URI> when the user navigates back to it?
You can use the TaskStackBuilder and use it something like this
TaskStackBuilder stack = TaskStackBuilder.create(context);
Intent classAct = new Intent(context, ClassActivity.class);
// This will clear any opened activities. Use if necessary
classAct.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Intent studentAct = new Intent(context, StudentActivity.class);
studentAct.putExtra("exam_id", 123);
stack.addNextIntent(home);
stack.addNextIntent(studentAct);
stack.addNextIntent(new Intent(context, StudentExamActivity.class));
stack.startActivities();

Android Activity singleton

I have an activity called MainActivity. This activity launches a notification that has a PendingIntent that opens this MainActivity.
So, to close the application, I have to click the back button twice. I would like to set up activity as singleton. I tried to set singleInstance or singleTask to manifest but this doesn't work.
singleInstance and singleTask are not recommended for general use.
Try:
android:launchMode="singleTop"
For more information please refer to launchMode section of the Activity element documentation.
In addition to the previous reference you should also read tasks and back stack
If you need to return to your app without creating a new instance of your activity, you can use the same intent filters as android uses when launching the app:
final Intent notificationIntent = new Intent(context, MainActivity.class);
notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
As the intent you created to open your activity from the notification bar is the same as android used for launching your app, the previously opened activity will be shown instead of creating a new one.

Categories

Resources