Backstack not cleared when notification clicked - android

I'm using Firebase Cloud Messenger (FCM) to push notifications to my app.
The notifications are received when the app is in the background so onMessageReceived is not triggered as the notification doesn't have a payload (data). Everything is fine with that but it means I can't create my own notification as everything is automatically handled by System tray.
When I click the notification I expect the entire backstack to be cleared and the app to restart from scratch. Basically I want the opposite of this post.
This is supposed to be the default behaviour.
However, when I click on the notification, if the app was already opened, the app restarts from the launcher but on top of the existing backstack.
For instance if you have:
HomeScreen -> Page1
when the notification is clicked, you now have in the stack:
HomeScreen -> Page1 -> HomeScreen
when it's supposed to only be:
HomeScreen
My launcher is an Activity only displayed when the app starts so I don't want it to be kept in the backstack. I turns out this this why I get this issue. So basically if the Launcher Activity calls finish() on itself and/or has noHistory="true" set in the Manifest, the backstack is not cleared when the notification is clicked.
How can I solve this issue?

I found a solution. The idea is to create a new LauncherActivity in charge of launching the existing one and clearing the backstack in the process.
There are probably other ways to do that but I wanted to keep the original Launcher with noHistory="true" as otherwise I have issue with the transition animation with the next Activity if I implemented the below solution directly to it.
The new Launcher is called StartActivity
In the Manifest:
<activity
android:name=".StartActivity"
android:screenOrientation="portrait"
android:theme="#style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
The Activity:
public class StartActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, LauncherActivity.class);
// Add the flags to clear the stack
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
// Start the intent with a defined transition animation. The animation is not
// required but it make the transition seamless.
startActivity(intent, getFadeInOutAnimation(this));
// Necessary for the app not to crash. Basically just a FrameLayout
setContentView(R.layout.activity_start);
}
public static Bundle getFadeInOutAnimation(Context context) {
// Allows us to display a fading animation as transition between the activities.
// The animation can be whatever you want
return ActivityOptions.makeCustomAnimation(context,
R.anim.fade_in, R.anim.fade_out).toBundle();
}
}

Related

Why can I not navigate back to my original activity from a different activity triggered from push notification intent?

My app has 2 activities: a main activity, and a detail activity. When I click a button on main it generates a push notification which when clicked brings up the details page.
The problem is, I do not believe I've configured anything to suggest that the activity should open with any different launch properties other than standard nor are their any flags set to suggest my activity open differently either. However when the notification is clicked and the new activity opens, when I click the back button I am taken back to the homescreen and no active tasks are available any longer.
I've noticed through experimentation that if I direct the intent triggered by the notification to go to back to the main activity instead of the detail activity it operates as I would expect. I can click the button on the first instance of main activity to fire the notification, click the notification to bring up the second instance of main activity, then press back to go back to the original instance of Main activity. It even has the correct state of details represented in the original (a text box populated with what I provided to trigger the first notification to say).
I've also found that if I direct the intent to fire the detail activity but set the affinity associated w/ the activity to something else that it SORT OF works by just creating a new task w/ that activity as the sole activity associated w/ the task. But this isn't what I want nor is it what I think should be happening anyway.
EDIT: I've added a button that takes me to the details activity through a standard intent. This works as intended. I've also added a button to the details activity that generates a notification to the Main Activity operating on the same logic as the button on Main that fires the notification to go to the Details Activity except with the intent class changed. Clicking this generates the notification, and pushes the main activity onto the already existing task stack as is standard.
So it seems like the issue is related to the Detail activity being targeted by the pending intent but I haven't figured out exactly what yet and following the details in the android website is of no help (though this is obvious as it's where I started from originally)
Code:
Generates the push notification (found on main activity)
fun generatePush2(view: View){
var generalTapIntent = Intent(this, ActivityDetail::class.java)
var pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, generalTapIntent, 0);
var notificationId = 0;
var builder = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("Instant Message")
.setContentText(txt_notification.text)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(pendingIntent) //sets the event to fire when notification is clicked
.setAutoCancel(true) //Removes notification when user taps it
with(NotificationManagerCompat.from(this)){
notify(notificationId, builder.build());
}
}
Android Manifest details (for proof)
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".ActivityDetail">
</activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
you can override the onBackPressed() like this:
public void onBackPressed()
{
this.startActivity(new Intent(DetailActivity.this,MainActivity.class));
return;
}
if you want the main activity to look different than default you can still pass some data through the intent and display it the way you want using :
Detail Activity
intent.putExtra(String key, Object data);
Main Activity
intent.getExtra(String key);
SO while not understanding the bug fully, I have found that the issue is related to two things. The AVD I was using and the activity name. On the original AVD I changed the activity name from ActivityDetail to ActivityDetail2 and this fixed the entire problem. I realized something was up w/ the activity when creating a third activity didn't have the issue. Unfortunately upon changing the name back (shift + f6 in Android studio) the problem began to occur again. Perplexed I then deleted the AVD (had to reinstall android studio to fix emulator:process failed for error code 0 error) and created a new one with a different phone type and different version of android. I was then able to run the application w/ the activity name "ActivityDetail" and the problem didn't occur.
I was also not able to re-create the issue on the original AVD. This tells me that something on the AVD was associated to that specific activity name, set somehow through an application call I might have made while following the guide on the official android notification page: https://developer.android.com/training/notify-user/build-notification
Unfortunately that's all I can say for certain. That and there's no need to overwrite the on back key action just to fix this issue.

Push notification to open specific activity

Consider 3 activities:
(A) Login Activity (main launcher)
(B) Main Activity
(C) Specific Activity
After users logged in (A) they will access (B), and from, and ONLY from (B), they can access (C).
Now everytime the app is opened, it will first launch (A) and then (B).
I want to make a push notification where when clicked, it can access (C) but must start (B) first.
I am using Xamarin.Android + Appcenter Push Notification
I can get notifications both when my app is in foreground and background.
My problem is when my app is in the background, clicking at the received notifications in the status bar causes it to relaunch the app, starting from (A).
I need help with skipping (A) since user is already logged in, opens (B) and THEN opens (C)
Any ideas? Hoping this is not too confusing for you guys.
I have also tried setting launchMode=singleInstance to (A) and (B) but it still relaunch the app
You cannot change the launcher activity dynamically, but there are a few workarounds you can try:
Create a transparent activity and make it as the Launcher activity:
<activity
android:name=".ActivityLauncher"
android:theme="#android:style/Theme.Translucent.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
And select the next activity in it's onCreate()
if ( logged() ) {
intent = new Intent(this,ActivityB.class);
} else {
intent = new Intent(this,ActivityA.class);
}
startActivity(intent);
finish();
You can achieve this send a activity name or some value to select your activity. Depending on the value that u are sending you can launch the activity and also add the activity in backstack whenever u go back fron notified activity the page in backstack will appear.
I do always like this, hope this will work for you.

How to bring Android activity to the front without bringing app to front if it is running in background?

I have an app that may run in the background. While it is running, it may bring a certain activity to the front. However, when the app brings the activity to the front, if the app is currently running at background, I do not want Android to bring the app itself to the front, just do it at background. The reason being is I do not want my app to interrupt what the customer is doing at the moment.
The following is my code that launches the activity.
Intent intentLogin = new Intent(getApplicationContext(), LoginActivity.class);
intentLogin.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intentLogin);
The issue with the above code is every time when it is called, it always bring my app to the front even the app is running at background, that is, annoy my customer.
My question is, is there a way that I can quietly bring the activity to the front? If my app is running in front, thats great --- the customer see the new activity straight away. If my app is running at background, I want to quietly bring the activity to the front when app is running in background, and next time when my customer resume the app, they will see the new activity I brought forward.
How can I do that?
Thank you very much!
如果我没理解错的话:
App background and usual player.
When user logged out , you can clear the user data saved in local and show a notification.And when the notification is clicked, start LoginActivity.
App background and unusual player.
When the notification shows ,the user do not click it instead of run the app.You can check the user data whether existing or not by onResume()in BaseActivity.If not ,start LoginActivity.
App foreground.
clear user data and force to start LoginActivity.
If you don't like the solution above(the best user experence I think),try this:
In onCreate() in the specific Activity ,use moveTaskToBack(true) to hide the intent.But remember to check whether the app is running background or foreground.you can refer to this.
If understand correctly, I suggest this:
User leaves your app, and when user come back to your app you want to show another activity instead of .MainActivity
define another activity as Main and with NoDisplay theme.
<activity
android:name=".MainActivity"
android:theme="#android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
then define your normal activities. In MainActivity class define a static variable. for example
public static boolean appRun = false;
onCreate() of SecondActivity change its value to true.
class SecondActivity extends Activity{
#Override
protected void onCreate(Bundle Saved){
super.onCreate(Saved);
MainActivity.appRun = true;
}
}
and onPause set this.finish(); to avoid not resuming to SecondActivity and force to start from MainActivity.
then onCreate() or onResume() of MainActivity check it s value and navigate to another activity.
if(appRun == true){
/*it means user went to SecondActivity and you want to display another*/
startActivity(new Intent(context, NewActivity.class));
}
else{
//this is first time so navigate to SecondActivity
}
Also If you can use Fragments this solution can work for that too.

One application with two independent activities

I am developing simple application with only two activities.
Activity C configures application
Activity A for interaction with user when some event occur
It is not possible for user to navigate from the one to the other - this is why I call them independent activities. Further more activity A is being invoked only form event, there is no way for user to do it manually.
Problem. Let's assume that application is properly configured. Some event occurs in the system, so application A is being shown to the user. The user interact with it and activity goes to background. Then the user decides to launch configuration activity C. Activity C is shown to the user. The user uses back button to "close" activity, but instead of android launcher or desktop the user is being shown activity A (taken from history).
Similar scenario might happen the other way. C is being used by user, then taken to background. Some event shows activity A and user using back button goes to C instead of closing activity A.
I have solved the problem, but the solution is pretty dirty. Is there any clean or standard way of solving such problem?
Part of my solution includes what was suggested in one answer:
snippet from AndroidManifest.xml:
<activity
android:name=".C"
android:clearTaskOnLaunch="true"
android:excludeFromRecents="false"
android:launchMode="singleTask"
...
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:exported="false"
android:name=".A"
android:excludeFromRecents="true"
android:noHistory="true"
android:launchMode="singleInstance"
android:clearTaskOnLaunch="true"
....
>
</activity>
snippet from activity A:
public boolean onKeyUp(final int p_keyCode, final KeyEvent p_event) {
switch(p_keyCode) {
case KeyEvent.KEYCODE_ENDCALL:
case KeyEvent.KEYCODE_HOME:
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MUTE:
case KeyEvent.KEYCODE_POWER:
this.finish();
break;
....
}
return super.onKeyUp(p_keyCode, p_event);
}
snipped from event handler:
public class H extends BroadcastReceiver {
...
Intent intent = new Intent(p_context, A.class);
intent.addFlags(Intent.FLAG_FROM_BACKGROUND);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
p_context.startActivity(intent);
...
}
It works for my application. However I want application (activity C) to appear in Recent application. But once activity A is invoked application is removed from Recent.
I don't know if this is the cleanest way to do this, but you can override the void onBackPressed() activity method. This way you can mannually move your activity to the background, like this, and prevent the previous activity from popping in:
public void onBackPressed () {
moveTaskToBack (true);
}
Edit: Turns out there's a better way to do this:
Open your AndroidManifest.xml, and inside each declaration put the following: `android:noHistory="true"``. Doing so will tell Android that your activity does not leave a history, and therefore, when the user hits back Android will quit the application, since there's no other activity for it to return to.

Android: bug in launchMode="singleTask"? -> activity stack not preserved

My main activity A has as set android:launchMode="singleTask" in the manifest. Now, whenever I start another activity from there, e.g. B and press the HOME BUTTON on the phone to return to the home screen and then again go back to my app, either via pressing the app's button or pressing the HOME BUTTONlong to show my most recent apps it doesn't preserve my activity stack and returns straight to A instead of the expected activity B.
Here the two behaviors:
Expected: A > B > HOME > B
Actual: A > B > HOME > A (bad!)
Is there a setting I'm missing or is this a bug? If the latter, is there a workaround for this until the bug is fixed?
FYI: This question has already been discussed here. However, it doesn't seem that there is any real solution to this, yet.
This is not a bug. When an existing singleTask activity is launched, all other activities above it in the stack will be destroyed.
When you press HOME and launch the activity again, ActivityManger calls an intent
{act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER]flag=FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_RESET_IF_NEEDED cmp=A}
So the result is A > B > HOME > A.
It's different when A's launchMode is "Standard". The task which contains A will come to the foreground and keep the state the same as before.
You can create a "Standard" activity eg. C as the launcher and startActivity(A) in the onCreate method of C
OR
Just remove the launchMode="singleTask" and set FLAG_ACTIVITY_CLEAR_TOP|FLAG_ACTIVITY_SINGLE_TOP flag whenever call an intent to A
From http://developer.android.com/guide/topics/manifest/activity-element.html on singleTask
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.
This means when the action.MAIN and category.LAUNCHER flags targets your application from the Launcher, the system would rather route the intent to the existing ActivityA as opposed to creating a new task and setting a new ActivityA as the root. It would rather tear down all activities above existing task ActivityA lives in, and invoke it's onNewIntent().
If you want to capture both the behavior of singleTop and singleTask, create a separate "delegate" activity named SingleTaskActivity with the singleTask launchMode which simply invokes the singleTop activity in its onCreate() and then finishes itself. The singleTop activity would still have the MAIN/LAUNCHER intent-filters to continue acting as the application's main Launcher activity, but when other activities desire calling this singleTop activity it must instead invoke the SingleTaskActivity as to preserve the singleTask behavior. The intent being passed to the singleTask activity should also be carried over to the singleTop Activity, so something like the following has worked for me since I wanted to have both singleTask and singleTop launch modes.
<activity android:name=".activities.SingleTaskActivity"
android:launchMode="singleTask"
android:noHistory="true"/>
public class SingleTaskActivity extends Activity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
intent.setClass(this, SingleTop.class);
startActivity(intent);
}
}
And your singleTop activity would continue having its singleTop launch mode.
<activity
android:name=".activities.SingleTopActivity"
android:launchMode="singleTop"
android:noHistory="true"/>
Good luck.
Stefan, you ever find an answer to this? I put together a testcase for this and am seeing the same (perplexing) behavior...I'll paste the code below in case anyone comes along and sees something obvious:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example" >
<uses-sdk android:minSdkVersion="3"/>
<application android:icon="#drawable/icon" android:label="testSingleTask">
<activity android:name=".ActivityA"
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=".ActivityB"/>
</application>
</manifest>
ActivityA.java:
public class ActivityA extends Activity implements View.OnClickListener
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.main );
View button = findViewById( R.id.tacos );
button.setOnClickListener( this );
}
public void onClick( View view )
{
//Intent i = new Intent( this, ActivityB.class );
Intent i = new Intent();
i.setComponent( new ComponentName( this, ActivityB.class ) );
startActivity( i );
}
}
ActivityB.java:
public class ActivityB extends Activity
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.layout_b );
}
}
I tried changing minSdkVersion to no avail. This just seems to be a bug, at least according to the documentation, which states the following:
As noted above, there's never more than one instance of a "singleTask" or "singleInstance" activity, so that instance is expected to handle all new intents. A "singleInstance" activity is always at the top of the stack (since it is the only activity in the task), so it is always in position to handle the intent. However, a "singleTask" activity may or may not have other activities above it in the stack. If it does, it is not in position to handle the intent, and the intent is dropped. (Even though the intent is dropped, its arrival would have caused the task to come to the foreground, where it would remain.)
I think this is the behaviour you want:
singleTask resets the stack on home press for some retarded reason that I don't understand.
The solution is instead to not use singleTask and use standard or singleTop for launcher activity instead (I've only tried with singleTop to date though).
Because apps have an affinity for each other, launching an activity like this:
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if(launchIntent!=null) {
launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
}
will cause your activty stack to reappear as it was, without it starting a new activity upon the old one (which was my main problem before). The flags are the important ones:
FLAG_ACTIVITY_NEW_TASK Added in API level 1
If set, this activity will become the start of a new task on this
history stack. A task (from the activity that started it to the next
task activity) defines an atomic group of activities that the user can
move to. Tasks can be moved to the foreground and background; all of
the activities inside of a particular task always remain in the same
order. See Tasks and Back Stack for more information about tasks.
This flag is generally used by activities that want to present a
"launcher" style behavior: they give the user a list of separate
things that can be done, which otherwise run completely independently
of the activity launching them.
When using this flag, if a task is already running for the activity
you are now starting, then a new activity will not be started;
instead, the current task will simply be brought to the front of the
screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK
for a flag to disable this behavior.
This flag can not be used when the caller is requesting a result from
the activity being launched.
And:
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Added in API level 1
If set, and this activity is either being started in a new task or
bringing to the top an existing task, then it will be launched as the
front door of the task. This will result in the application of any
affinities needed to have that task in the proper state (either moving
activities to or from it), or simply resetting that task to its
initial state if needed.
Without them the launched activity will just be pushed ontop of the old stack or some other undesirable behaviour (in this case of course)
I believe the problem with not receiving the latest Intent can be solved like this (out of my head):
#Override
public void onActivityReenter (int resultCode, Intent data) {
onNewIntent(data);
}
Try it out!
I've found this issue happens only if the launcher activity's launch mode is set to singleTask or singleInstance.
So, I've created a new launcher activity whose launch mode is standard or singleTop. And made this launcher activity to call my old main activity whose launch mode is single task.
LauncherActivity (standard/no history) -> MainActivity (singleTask).
Set splash screen to launcher activity. And killed launcher activity right after I call the main activity.
public LauncherActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
startActivity(intent);
finish();
}
}
<activity
android:name=".LauncherActivity"
android:noHistory="true"
android:theme="#style/Theme.LauncherScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Launcher screen theme should be set for the case that app is restarting after the process is killed. -->
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:theme="#style/Theme.LauncherScreen"/>
Pros: Can keep the MainActivity's launch mode as singleTask to make sure there always is no more than one MainActivity.
If both A and B belong to the same Application, try removing
android:launchMode="singleTask"
from your Activities and test because I think the default behavior is what you described as expected.
Whenever you press the home button to go back to your home screen the activity stack kills some of the previously launched and running apps.
To verify this fact try to launch an app from the notification panel after going from A to B in your app and come back using the back button ..........you will find your app in the same state as you left it.
When using launch mode as singleTop make sure to call finish() (on current activity say A) when starting the next activity (using startActivity(Intent) method say B). This way the current activity gets destroyed.
A -> B -> Pause the app and click on launcher Icon, Starts A
In oncreate method of A, you need to have a check,
if(!TaskRoot()) {
finish();
return;
}
This way when launching app we are checking for root task and previously root task is B but not A. So this check destroys the activity A and takes us to activity B which is currently top of the stack.
Hope it works for you!.
This is how I finally solved this weird behavior. In AndroidManifest, this is what I added:
Application & Root activity
android:launchMode="singleTop"
android:alwaysRetainTaskState="true"
android:taskAffinity="<name of package>"
Child Activity
android:parentActivityName=".<name of parent activity>"
android:taskAffinity="<name of package>"
Add below in android manifest activity, it will add new task to top of the view destroying earlier tasks.
android:launchMode="singleTop" as below
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:theme="#style/AppTheme.NoActionBar">
</activity>
In child activity or in B activity
#Override
public void onBackPressed() {
Intent intent = new Intent(getApplicationContext(), Parent.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
finish();
}
<activity android:name=".MainActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
//Try to use launchMode="singleTop" in your main activity to maintain single instance of your application. Go to manifest and change.

Categories

Resources