TaskStackBuilder with startActivityForResult - android

In my case I have an activity A that calls activity B using startActivityForResult.
Activity B is a form that returns the data to activity A thus the data can be stored in my database.
Moreover, my app launch a notification which starts activity B when clicking and my problem occurs when I try to go back from activity B to activity A because the method "onActivityResult" is never called. I'm not able to simulate the startActivityForResult() when I creating my TaskStackBuilder:
Intent resultIntent = new Intent(this, activityB.class);
// This ensures that navigating backward from the Activity leads out of your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(activityB.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setContentIntent(resultPendingIntent);
Finally, I've added the parent activity for activity B in the manifest.xml:
<activity
android:name=".activityB"
android:parentActivityName=".activityA"
android:windowSoftInputMode="stateHidden">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activityA"/>
</activity>

As of my knowledge of the Android's framework, the OnActivityResult method is only called when an activity has been started with startActivityForResult() method and the corresponding activity has called the setResult one.
Android Official Documentation Android TaskStackBuilder:
Utility class for constructing synthetic back stacks for cross-task navigation
So I don't think you can say to the framework to go back to the activity using that callback.
Instead what you could do is put some extras within the Intent and then when you go back in the back stack (to activity A), check inside the initial methods (onCreate or onResume) for extras or arguments that would only be there in case of comming from that last activity (activity B)

Related

Activity in launchMode singleTask gets destroyed when started via TaskStackBuilder

I have an activity A, declared in Manifest with android:launchMode="singleTask"
and another activity B that has no further attributes.
As far as I know, if there is already some task running activity A,
instead of creating a new instance, the current task is brought to front,
if I attempt to start activity A.
Now, I want to use the TaskStackBuilder to make a call which includes both activities.
Let's suppose I am on a Fragment within Activity A and call:
TaskStackBuilder.create(context)
.addNextIntent(new Intent(context, A.class))
.addNextIntent(new Intent(context, B.class))
.startActivities();
I assume the current stack to become: A > B with A getting a call to onNewIntent and B with a new instance. Furthermore I expect Activity A to hold it's state.
But what happens is: I do not get a call to onNewIntent, but to onDestroy for Activity A. And if I go back from my newly created Activity B, onCreate is called for a new instance of Activity A. (They are not equal!).
Can anybody explain to me, why my Activity A is thrown away instead of doing nothing to it, but adding B to the stack? It is a shame, that I can't get my head around this problem by myself, but I can't see the obstacle here.
Edit/Ps: The context which I use for the StackBuilder can either be a views context or a Service context.
So, as nobody answered so far, I dived into the code from android in the meanwhile and discovered that the StackBuilder itself is the issue.
In detail..this part of code, which is present in the methods startActivities() and also in getPendingIntent(), causes my Activity A to get destroyed:
public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options) {
if (mIntents.isEmpty()) {
throw new IllegalStateException(
"No intents added to TaskStackBuilder; cannot getPendingIntent");
}
Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
// Appropriate flags will be added by the call below.
return IMPL.getPendingIntent(mSourceContext, intents, requestCode, flags, options);
}
It is this part:
Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]);
intents[0] = new Intent(intents[0]).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
IntentCompat.FLAG_ACTIVITY_CLEAR_TASK |
IntentCompat.FLAG_ACTIVITY_TASK_ON_HOME);
My Activity A was in an existing Task. Although it has the launchMode = "singleTask" and should get reordered to front normally, the FLAG_ACTIVITY_CLEAR_TASK is set to my first activity (which is A) and causes the existing task that would be associated with the A to be cleared before the activity is started.
That said, I won't use the StackBuilder in my special case now.
I see several approaches to this: either use an empty Activity before
my first target Activity or building the stack myself inside of
my Activity A's onCreate / onNewIntent, by providing a Bundle
with a List of parcelables (intents) to start.

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: when coming from notification, activities are added on top of already open activities and memory increases

Android: when coming from notification, activities are added on top of already open activities and memory increases.
How to clear all the previous activities or even kill the app when notification is tapped?
Here is how I build my notifications:
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)//
.setContentTitle(notificationTitle)//
.setContentText(notificationText)//
.setSmallIcon(R.drawable.app_icon_transparent)//
.setAutoCancel(true)//
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.asd));//
// Creates an explicit intent for an Activity in your app
Intent resultIntent = new Intent(context, StarterActivity.class);
resultIntent.putExtra("comingFromNotification", true);
// The stack builder object will contain an artificial back stack for
// the
// started Activity.
// This ensures that navigating backward from the Activity leads out of
// your application to the Home screen.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack for the Intent (but not the Intent itself)
stackBuilder.addParentStack(StarterActivity.class);
// Adds the Intent that starts the Activity to the top of the stack
stackBuilder.addNextIntent(resultIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// mId allows you to update the notification later on.
mNotificationManager.notify((int) now.getTimeInMillis(), mBuilder.build());
Now, when the user receives a notification and clicks it, StarterActivity is started. It initiates all the resources the app needs and then starts the main activity.
If the app had been running up to this moment and had been taking up 50 mb of ram, now ram goes up to 65, which means the previous process is not killed and this one starts on top of it.
Question is, how to kill the app if its running in the moment the user clicks the notification?
EDIT: In some similar question I found this
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP| Intent.FLAG_ACTIVITY_NEW_TASK);
will this help? What do these flags mean?
EDIT2: Nope. those flags didnt help. The application process that has some cached objects in memory still lives and the RAM goes up again.
Your architecture doesn't lend itself to solving this problem. As you mentioned in some comments, StarterActivity is your root activity (ie: the one with ACTION=MAIN and CATEGORY=LAUNCHER), but you don't keep it in the activity stack, so you cannot take advantage of FLAG_ACTIVITY_CLEAR_TOP.
To solve your problem you should not call finish() on StarterActivity when it launches MainActivity. This will allow you to use FLAG_ACTIVITY_CLEAR_TOP (with or without FLAG_ACTIVITY_SINGLE_TOP, depending on whether or not you want to use the existing instance of StarterActivity or create a new instance).
Once you've got that working, you now need to deal with the problem of the user pressing BACK in MainActivity and having it return to StarterActivity, which is obviously not what you want. You can solve that in several ways, here are 2 examples:
Add a boolean member variable to StarterActivity. In StarterActivity.onResume() set that variable to true at the very end of the method. At the start of onResume(), check the variable, and if it is true, you can assume that the user has pressed BACK in MainActivity, so you can just call finish().
In MainActivity override onBackPressed() and instead of calling super.onBackPressed(), call startActivity() with an Intent for StarterActivity with an extra named "exit" and FLAG_ACTIVITY_CLEAR_TOP. This will cause a new instance of StarterActivity to be created. In StarterActivity.onCreate(), check the presence of the extra "exit" in the Intent, and if it is there, it means that the user pressed BACK from MainActivity. In this case, you just want to call finish() to end your application. If you use this mechanism, make sure that StarterActivity has standard launchMode in the manifest, not launchMode="singleTop".
FLAG_ACTIVITY_CLEAR_TOP - Clears intermidiate activities, not every activity e.g:
You have call A > B > C > D >E , then from E you call B. After such call Activities C and D will be removed, activity A will remain.
If you have calls A > B > C > E and from E you call activity F, no activity will be purged.
FLAG_ACTIVITY_SINGLE_TOP ensures the top activity is not recreated if it is called again. eg:
you cal A > B > C , then you call B again. After such call B will not be recreated, but Current existent will be called. If you will call A, it will be recreated as it is not on top of stack.
Activity_new_task - is used for complecated back navigation. You can manage several tasks or in other words several histories of activity calls. Creating new task will not erase previos task. Is an absolute requirenment, when starting app with notification, deeplinks or any other "launcher" behaviour. Purifications inside one task will have no effect on another task history
I assume, a propper reset of history after launch will be achived with those 3 flags together: FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |FLAG_ACTIVITY_CLEAR_TASK
This shoul, clear task history, close it and create new task. If it does not work try to replace Clear_Task with FLAG_ACTIVITY_RESET_TASK_IF_NEEDED.
You don't need to kill the app and start it again. Use android:launchMode="singleTop" in your manifest for the activity:
<activity
android:name=".StarterActivity"
android:launchMode="singleTop"/>
This will open the same activity rather than creating a new one.

Why two instances of activity is getting created after reboot?

I have a boot receiver which receives BOOT_COMPLETED event and launches the default activity for my application as follows:
if(MyApplication.GetCurrentActivity()==null)
{
Intent mActivityIntent = new Intent(context, LauncherActivity.class);
mActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mActivityIntent);
}
I'm setting the activity after starting it. GetCurrentActivity() will return null if there are no activities launched or the last launched activity.
So far there is no problem.
But if the user touches the app icon before the LauncherActivity has been launched, two instances of the same activities are created as they are in two different tasks (I guess). How to prevent this and launch only one instance of the activity.
Try -
if(MyApplication.GetCurrentActivity()==null)
{
Intent mActivityIntent = new Intent(context, LauncherActivity.class);
mActivityIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
Intent.FLAG_ACTIVITY_NEW_TASK);
mActivityIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(mActivityIntent);
finish();
}
UPDATE:
Then you can use FLAG_ACTIVITY_REORDER_TO_FRONT -
This flag will cause the launched activity to be brought to the front of its task's history stack if it is already running.-
if(MyApplication.GetCurrentActivity()==null)
{
Intent mActivityIntent = new Intent(context, LauncherActivity.class);
mActivityIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
startActivity(mActivityIntent);
finish();
}
For example -
Consider a task consisting of four activities: A, B, C, D. If D
calls startActivity() with an Intent that resolves to the component
of activity B, then B will be brought to the front of the history
stack, with this resulting order: A, C, D, B.
You can use:
<activity
android:name="name"
android:label="label"
android:launchMode="singleTop">
</activity>
This launch only one instance of the activity. Now, you can implement onNewIntent().
Hope it helps you :)

How to correctly define activities and intents to behave as desired?

I have a 'list' activity which starts an 'article' activity when clicked.
I also have push notifications which opens the 'article' activity directly.
I changed the back button behavior in the 'article' activity to start the 'list' activity, when coming from a notification so that the user will go back to the article list.
The problem is when the app is already opened in the background and I open a notification - it just brings it back to front.
What I want to achieve is open the right article when clicking a notification and going back to the 'list' activity, without having the possibility the the list activity will be open twice.
I tried to separate the 'article' task and create new task in the notification intent but then it would open separate 'list' activities when opening multiple notifications and clicking back.
What is the correct way to define the activities' tasks and intent flags to achieve my goal?
EDIT:
Manifest part:
<activity android:name="ListFeed" android:configChanges="orientation|screenLayout" android:launchMode="singleInstance" android:screenOrientation="unspecified"
android:taskAffinity="com.app.MyTask"></activity>
<activity android:name="Article" android:launchMode="standard" android:configChanges="orientation|screenLayout" android:screenOrientation="unspecified"
android:taskAffinity="com.app.MyTask"></activity>
Notification intent:
Intent notificationIntent = new Intent(context, Article.class);
PendingIntent contentIntent = PendingIntent.getActivity(context, notificationID, notificationIntent, PendingIntent.FLAG_ONE_SHOT);
Thanks!!
what i got from your question is that
1) you have listActivity A
2) ArticalActivity B.
i) And first you want to open Activity A whenever back from B, Correct? for that you can use dispatchKeyEvent, listen to Back button event and start activity A. or by using below code
#Override
public void onBackPressed() {
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
ii) you want to open only single instance of Activity A(list). for this you can basically use
launchMode in Activity A's Manifest declration as singleInstance.
android:launchMode="singleTask"
you can read docs for launch mode
let me know if i missed anything.
I see that you are playing around with launchModes and excludeFromRecents and this isn't a good thing. The standard behaviour of Android should do pretty much what you want.
To verify this I've created a simple 3-activity application that contains a MainActivity, a ListActivity and an ArticleActivity. I'm not using any non-standard launch modes and I'm not setting any Intent flags (except in onBackPressed() see below). The Main Activity creates and posts a notification to display a specific Article. The MainActivity starts the ListActivity. Each element of the ListActivity starts an Intent for the ArticleActivity and passes some information in EXTRAS so that the ArticleActivity knows which article to display.
In order to have the behaviour you described (ie: returning from the ArticleActivity to the ListActivity after starting the app from a notification, even if the app was not running), I've done what Ankit has suggested (ie: override onBackPressed() in ArticleActivity) like this:
#Override
public void onBackPressed() {
// Return to ListActivity
Intent intent = new Intent(this, ListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
// Finish this activity (in case the ListActivity wasn't already in the stack)
finish();
}
I used FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP because this will not recreate the ListActivity if it already exists in the activity stack (ie: it will go back to the same instance).
I had to add the finish() call, because if the app was not running in the background and the user started it from the notification, the ListActivity would be created and put on top of the ArticleActivity. Then when the user pressed "back" to leave the ListActivity, the ArticleActivity would be exposed underneath. Adding finish() here makes the ArticleActivity go away so that pressing "back" from the ListActivity goes back to wherever it came from.
If you want me to send you the code, just let me know.

Categories

Resources