I have problem with notifications. It all works well launching correct activity from notification.
Problem is that when new activity is launched from notification and app has been in background. Then first the latest activity is loaded (on Resume()) and after that intended activity is loaded. In onResume I'm checking the session expiration. And when its expired user will be taken to login. And in the latest activity i don't have notification extras.
Is there possibility to skip latest activity onResume when opening notification and going straight to the intended activity.
PendingIntent createContentIntent(Context appContext, NotificationData data) {
Intent notificationIntent = createNotificationIntent(appContext, data);
if (notificationIntent == null) {
notificationIntent = new Intent();
}
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle extras = createBundle();
extras.putSerializable(EXTRA_NOTIFICATION_DATA, data);
notificationIntent.putExtras(extras);
return createPendingIntentActivity(appContext, notificationIntent);
}
Modify your new activity manifest to include custom task affinity, and consider changing launchMode to singleTop to prevent creating duplicates of the activity:
<activity
android:name=".your_activity"
android:launchMode="singleTop"
android:taskAffinity=":task_2"/>
Also include Intent.FLAG_ACTIVITY_NEW_TASK in your launch intent.
The problem you are having is that Android is bringing your existing task to the foreground and then (obviously) resuming the top Activity in the task BEFORE it creates the Activity from the Notification. This sounds like an Android bug.
Anyway, to get around this, you could try having the Notification launch an Activity into a different task. To do this, set taskAffinity="" on the Activity that the Notification starts. The Activity started by the Notification would then have to start another Activity that will run in the task with the rest of your app. Hopefully this would all happen after the phone is unlocked so that you don't see this onResume() problem.
If you need more help, please post the code from createNotificationIntent() and createPendingIntentActivity() and the relevant parts of your manifest.
Related
I have an activity which is called if the app receives a push notification. The activity is started with FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP. The activity, let's call it 'A' shows UI and finishes after a while. In this point have a problem with activity stack.
Scenario:
The app is in the background with another activity 'B'
Then the app receives a push notification and starts Activity A.
After related things done, the app finishes Activity A
Then returns to Activity B and stays in the foreground even the app was in the background before the push notification is received.
After debugging, I figured out that the system calls onResume method of Activity B after finishing Activity A.
How can I do the app keep staying in background if the app started from background? Should I change intent flags of the activity A?
In your case you can achieve this in two ways
1- From manifest file with activity tag android:noHistory="true"
2- From code when you are staring the activity set flags like below
Intent mIntent = new Intent(context, Youractivity.class);
mIntent.setFlags(mIntent.getFlags() | Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(mIntent);
For more information checkout developers link
One other thing you can do is instead of this.finish() in notificationActivity is to use this.finishAffinity();. This will close the app instead coming to foreground.
I have issue in intent of my launcher activity.Scenerio is:
1. Send intents form notification service to my launcher activity
PendingIntent contentIntent = PendingIntent.getActivity(this, TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID, new Intent(this, MainActivity.class).putExtra("is_log", true), Intent.FLAG_ACTIVITY_CLEAR_TOP);
2. In my MainActivity i getting this intent. code is:
if(this.getIntent().getExtras()!=null){
boolean isLogNewTripScreen = (boolean)this.getIntent().getExtras().getBoolean("is_log");
}
}
3. this work fine but when i come from notification service,but when i launch from not notification service ,that data in intentis still there.How can i remove that data from intent.
EDIT: I've created a sample application to test this problem and possible solutions. Here are my findings:
If you launch your app from a notification with extras and then later return to your app by selecting it from the list of recent tasks, Android will launch the app again the same way it was launched from the notification (ie: with the extras). This is either a bug or a feature, depending on who you ask.
You'll need to add additional code to deal with this situation. I can offer 2 suggestions:
1. Use FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
When you create your notification, set the flag Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS in the Intent. Then, when the user selects the notification and launches the app from the notification, this will not create an entry for this task in the list of recent tasks. Also, if there was an entry in the list of recent tasks for this application, that entry will also be removed. In this case, it will not be possible for the user to return to this task from the list of recent tasks. This solves your problem by removing the possibility that the user launches the app from the list of recent tasks (but only when the app has been launched from the notification).
2. Detect FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
When the user launches your app from the list of recent tasks, Android sets the flag Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY in the Intent that is passed to onCreate() of your launch activity. You can detect the presence of this flag in onCreate() and then you know that the app has been launched from the recent tasks list and not from the notification. In this case, you can just ignore the fact that the extras in the Intent still contain data.
Choose the solution that best suits the workflow for your application. And thanks for the question, this was an interesting challenge to solve :-)
Additional information:
You are creating the PendingIntent incorrectly. You are calling
PendingIntent contentIntent = PendingIntent.getActivity(this,
TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID,
new Intent(this, MainActivity.class).putExtra("is_log", true),
Intent.FLAG_ACTIVITY_CLEAR_TOP);
You are passing Intent.FLAG_ACTIVITY_CLEAR_TOP as the 4th parameter to getActivity(). However, that parameter should be PendingIntent flags. If you want to set FLAG_ACTIVITY_CLEAR_TOP on the Intent, you need to do it this way:
PendingIntent contentIntent = PendingIntent.getActivity(this,
TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID,
new Intent(this, MainActivity.class).putExtra("is_log", true)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), 0);
I noticed that using fragments. I read a QR Code in Activity A that opens fragment 1, send its content to a webservice and if goes right, replace it with fragment 2. When user press back, the onBackPressed in Activity A call finish. If user select the app again in the list, it was opening fragment 1 instead of fragment 2.
I solved that checking in onBackPressed if extra contains a field indicating that fragment 2 was already opened. If true, moveTaskToBack(true) is called instead of finish()
Activity A
#Override
public void onBackPressed() {
Bundle extras = getIntent().getExtras();
if(extras.containsKey(Constants.TICKET_DONT_SHOW_QRCODE_SCREEN)){
moveTaskToBack(true);
}else {
finish();
}
}
Fragment 2
Intent mainIntent = getActivity().getIntent();
mainIntent.putExtra(Constants.TICKET_DONT_SHOW_QRCODE_SCREEN, true);
getActivity().setIntent(mainIntent);
I've tested all the answers of stackoverflow with no luck, what worked for me was this. Create a helper class to check the activity flags. Or a function, it does not matter.
object FlagHelper {
fun notLaunchedFromNotification(activity: AppCompatActivity): Boolean {
return activity.intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
}
}
Then use as the following code. It returns a boolean so you can check the intent extras when it's false
val notLaunchedFromNotification = FlagHelper.notLaunchedFromNotification(this)
Add android:launchMode="singleInstance" to your launcher activity
and then Use flag Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS while starting your activity
I'm using the following intent with a notification and the issue is that if the current activity is the same as the intent nothing happens. How do I open the same activity with the new data?
intent = new Intent(context, PackViewActivity.class);
intent.putExtra("pid", pack_id);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
Here is the details fromt he Manaifest:
<activity
android:name=".PackViewActivity"
android:configChanges="orientation|keyboardHidden|screenLayout|screenSize"
android:launchMode="singleTask"/> //I've also tried singleInstance with no success.
Currently everything works fine unless the current activity is PackViewActivity. In that case nothing happens.
Like #Vivek mentioned, use Intent.FLAG_ACTIVITY_CLEAR_TOP and remove Intent.FLAG_ACTIVITY_CLEAR_TASK. Now, if your activity is already running the new intent will be delivered in onNewIntent(). That is where you should put your intent reading code. Also, get rid of android:launchMode in your activity manifest description since it brings a lot of problems with it.
If the required activity is already in foreground then you need not to push a notification. Alternatively you can register the activity as a listener to the service or activity class which pushes the notification through NotificationManager. Then handle the notification appropriately.
I have an app with a splash screen Activity, followed by a main Activity. The splash screen loads stuff (database, etc.) before starting the main Activity. From this main Activity the user can navigate to multiple other child Activities and back. Some of the child Activities are started using startActivityForResult(), others just startActivity().
The Activity hierarchy are as depicted below.
| Child A (startActivityForResult)
| /
|--> Splash --> Main -- Child B (startActivityForResult)
| ^ \
| | Child C (startActivity)
| \
| This Activity is currently skipped if a Notification is started
| while the app is not running or in the background.
I need to achieve the following behavior when clicking a Notification:
The state in the Activity must be maintained, since the user has selected some recipes to create a shopping list. If a new Activity is started, I believe the state will be lost.
If the app is in the Main Activity, bring that to the front and let me know in code that I arrived from a Notification.
If the app is in a child Activity started with startActivityForResult(), I need to add data to an Intent before going back to the Main Activity so that it can catch the result properly.
If the app is in a child Activity started with startActivity() I just need to go back since there is nothing else to do (this currently works).
If the app is not in the background, nor the foreground (i.e. it is not running) I must start the Main Activity and also know that I arrived from a Notification, so that I can set up things that are not set up yet, since the Splash Activity is skipped in this case in my current setup.
I have tried lots of various suggestions here on SO and elsewhere, but I have not been able to successfully get the behavior described above. I have also tried reading the documentation without becoming a lot wiser, just a little. My current situation for the cases above when clicking my Notification is:
I arrive in the Main Activity in onNewIntent(). I do not arrive here if the app is not running (or in the background). This seems to be expected and desired behavior.
I am not able to catch that I am coming from a Notification in any child Activities, thus I am not able to properly call setResult() in those Activities. How should I do this?
This currently works, since the Notification just closes the child Activity, which is ok.
I am able to get the Notification Intent in onCreate() by using getIntent() and Intent.getBooleanExtra() with a boolean set in the Notification. I should thus be able to make it work, but I am not sure that this is the best way. What is the preferred way of doing this?
Current code
Creating Notification:
The Notification is created when an HTTP request inside a Service returns some data.
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getNotificationIcon())
.setAutoCancel(true)
.setColor(ContextCompat.getColor(context, R.color.my_brown))
.setContentTitle(getNotificationTitle(newRecipeNames))
.setContentText(getContentText(newRecipeNames))
.setStyle(new NotificationCompat.BigTextStyle().bigText("foo"));
Intent notifyIntent = new Intent(context, MainActivity.class);
notifyIntent.setAction(Intent.ACTION_MAIN);
notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
/* Add a thing to let MainActivity know that we came from a Notification. */
notifyIntent.putExtra("intent_bool", true);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111, builder.build());
MainActivity.java:
#Override
protected void onCreate(Bundle savedInstanceState)
{
Intent intent = getIntent();
if (intent.getBooleanExtra("intent_bool", false))
{
// We arrive here if the app was not running, as described in point 4 above.
}
...
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch (requestCode)
{
case CHILD_A:
// Intent data is null here when starting from Notification. We will thus crash and burn if using it. Normally data has values when closing CHILD_A properly.
// This is bullet point 2 above.
break;
case CHILD_B:
// Same as CHILD_A
break;
}
...
}
#Override
protected void onNewIntent(Intent intent)
{
super.onNewIntent(intent);
boolean arrivedFromNotification = intent.getBooleanExtra("intent_bool", false);
// arrivedFromNotification is true, but onNewIntent is only called if the app is already running.
// This is bullet point 1 above.
// Do stuff with Intent.
...
}
Inside a child Activity started with startActivityForResult():
#Override
protected void onNewIntent(Intent intent)
{
// This point is never reached when opening a Notification while in the child Activity.
super.onNewIntent(intent);
}
#Override
public void onBackPressed()
{
// This point is never reached when opening a Notification while in the child Activity.
Intent resultIntent = getResultIntent();
setResult(Activity.RESULT_OK, resultIntent);
// NOTE! super.onBackPressed() *must* be called after setResult().
super.onBackPressed();
this.finish();
}
private Intent getResultIntent()
{
int recipeCount = getRecipeCount();
Recipe recipe = getRecipe();
Intent recipeIntent = new Intent();
recipeIntent.putExtra(INTENT_RECIPE_COUNT, recipeCount);
recipeIntent.putExtra(INTENT_RECIPE, recipe);
return recipeIntent;
}
AndroidManifest.xml:
<application
android:allowBackup="true"
android:icon="#mipmap/my_launcher_icon"
android:label="#string/my_app_name"
android:theme="#style/MyTheme"
android:name="com.mycompany.myapp.MyApplication" >
<activity
android:name="com.mycompany.myapp.activities.SplashActivity"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.mycompany.myapp.activities.MainActivity"
android:label="#string/my_app_name"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" >
</activity>
<activity
android:name="com.mycompany.myapp.activities.ChildActivityA"
android:label="#string/foo"
android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustPan" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.mycompany.myapp.activities.MainActivity" >
</meta-data>
</activity>
<activity
android:name="com.mycompany.myapp.activities.ChildActivityB"
android:label="#string/foo"
android:parentActivityName="com.mycompany.myapp.activities.MainActivity"
android:screenOrientation="portrait" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.mycompany.myapp.activities.MainActivity" >
</meta-data>
</activity>
...
</manifest>
Such a complicated Question :D
Here is how you should treat this problem :
Use an IntentService in your notification instead of
Intent notifyIntent = new Intent(context, MainActivity.class);
by now, whenever user click on the notification, an intentservice would be called.
in the intent service,Broadcast something.
in OnResume of all your desired activity register the broadcast listener (for the broadcast you create in 2nd phase) and in OnPause unregister it
by now whenever you are in any activity and the user click on notification, you would be informed without any problem and without any recreation of activity
in your Application class define a public Boolean. lets called it APP_IS_RUNNING=false; in your MainActivity, in OnPause make it false and in OnResume make it true;
By doing this you can understand your app is running or not or is in background.
NOTE : if you want to handle more states, like isInBackground,Running,Destroyed,etc... you can use an enum or whatever you like
You want to do different things when the app is running, am i right ? so in the intent service which you declared in 1st phase check the parameter you define in your Application Class. (i mean APP_IS_RUNNING in our example) if it was true use broadcast and otherwise call an intent which open your desired Activity.
You are going on a wrong way buddy.
onActivityResult is not the solution.
Just A simple Answer to this would be to use Broadcast Receiver
Declare an action In your manifest file:
<receiver android:name="com.myapp.receiver.AudioPlayerBroadcastReceiver" >
<intent-filter>
<action android:name="com.myapp.receiver.ACTION_PLAY" />
<!-- add as many actions as you want here -->
</intent-filter>
</receiver>
Create Broadcast receiver's class:
public class AudioPlayerBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(action.equalsIgnoreCase("com.myapp.receiver.ACTION_PLAY")){
Myactivity.doSomething(); //access static method of your activity
// do whatever you want to do for this specific action
//do things when the button is clicked inside notification.
}
}
}
In your setNotification() Method
Notification notification = new Notification.Builder(this).
setWhen(System.currentTimeMillis())
.setSmallIcon(R.drawable.no_art).build();
RemoteView remoteview = new RemoteViews(getPackageName(), R.layout.my_notification);
notification.contentView = remoteview;
Intent playIntent = new Intent("com.myapp.receiver.ACTION_PLAY");
PendingIntent playSwitch = PendingIntent.getBroadcast(this, 100, playIntent, 0);
remoteview.setOnClickPendingIntent(R.id.play_button_my_notification, playSwitch);
//this handle view click for the specific action for this specific ID used in broadcast receiver
Now when user will click on the button in Notification and broacast receiver will catch that event and perform the action.
Here is what I ended up doing. It is a working solution and every situation of app state, child Activity, etc. is tested. Further comments are highly appreciated.
Creating the Notification
The Notification is still created as in the original question. I tried using an IntentService with a broadcast as suggested by #Smartiz. This works fine while the app is running; the registered child Activities receives the broadcast and we can do what we like from that point on, like taking care of the state. The problem, however, is when the app is not running in the foreground. Then we must use the flag Intent.FLAG_ACTIVITY_NEW_TASK in the Intent to broadcast from the IntentService (Android requires this), thus we will create a new stack and things starts to get messy. This can probably be worked around, but I think it easier to save the state using SharedPreferences or similar things as others pointed out. This is also a more useful way to store persistent state.
Thus the Notification is simply created as before:
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(getNotificationIcon())
.setAutoCancel(true)
.setColor(ContextCompat.getColor(context, R.color.my_brown))
.setContentTitle(getNotificationTitle(newRecipeNames))
.setContentText(getContentText(newRecipeNames))
.setStyle(new NotificationCompat.BigTextStyle().bigText("foo"));
Intent notifyIntent = new Intent(context, MainActivity.class);
notifyIntent.setAction(Intent.ACTION_MAIN);
notifyIntent.addCategory(Intent.CATEGORY_LAUNCHER);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
/* Add a thing to let MainActivity know that we came from a Notification.
Here we can add other data we desire as well. */
notifyIntent.putExtra("intent_bool", true);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(notifyPendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(111, builder.build());
Saving state
In the child Activities that need to save state I simply save the I need to SharedPreferences in onPause(). Thus that state can be reused wherever needed at a later point. This is also a highly useful way of storing state in a more general way. I had not though of it since I thought the SharedPreferences were reserved for preferences, but it can be used for anything. I wish I had realized this sooner.
Opening the Notification
Now, when opening a Notification the following things occur, depending on the state of the app and which child Activity is open/paused. Remember that the flags used are Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP.
A. Child Activity
Running in the front: The child Activity is closed, applicable state is saved using SharedPreferences in onPause and can be fetched in onCreate or wherever in the main Activity.
App is in the background: same behavior.
App is in the background, but killed by the OS (tested using adb shell: There is no stack at this point, thus MainActivity is opened. The app is in a dirty state, however, so I revert that intent back to the splash screen with the incoming data and back to the main Activity. The state is again saved in onPause in the child Activity when the user closed it and it can be fetched in the main Activity.
B. Main Activity
Running in the front: The Intent is caught in onNewIntent and everything is golden. Do what we want.
App is in the background: same behavior.
App is in the background, but killed by the OS (tested using adb shell: The app is in a dirty state, so we revert the Intent to the splash screen/loading screen and back to the main Activity.
C. App is not running at all
This is really the same as if Android killed the app in the background to free resources. Just open the main Activity, revert to the splash screen for loading and back to the main Activity.
D. Splash Activity
It is not very likely that a user can be in the splash Activity/loading Activity while a Notification is pressed, but it is possible in theory. If a user does this the StrictMode complains about having 2 main Activities when closing the app, but I am not certain that it is entirely correct. Anyway, this is highly hypothetical, so I am not going to spend much time on it at this point.
I do not think this is a perfect solution since it requires a little bit of coding here and little bit of coding there and reverting Intents back and forth if the app is in a dirty state, but it works. Comments are highly appreciated.
I am experiencing a weird behavior on my android application. When I open my application, I see my DashboardActivity, then I hit home button or back button and my application closes. This is ok. Then I receive a push message and with this push message I create a notification. The notification works fine, I click the notification and it opens my activity, using the code below:
Intent notificationIntent = new Intent(context, BookingOfferActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Bundle b = new Bundle();
b.putSerializable("booking", booking);
notificationIntent.putExtras(b);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
Then I execute some task in this BookingOfferActivity activity and call the method finish() to make sure this activity will be finished no matter what. Then I open my application again, but instead of seeing the DashboardActivity I am still seeing BookingOfferActivity.
I have tried the solution proposed here:
Prevent new activity instance after clicking on notification
but it just doesnt work.
Is there a way to force my application to always open on the DashboardActivity?
Thanks
T
That is strange behaviour considering you are calling finish()
Try setting
android:noHistory="true"
android:launchMode="singleInstance"
in the manifest for the BookingOfferActivity
i don't know exactly, but u can try to finish all activities in onStop() method.
in onResume() method start your DashboardActivity.
Try removing SINGLE_TOP from your intent. CLEAR_TOP should be all you want.
From the Android Developer documentation
FLAG_ACTIVITY_SINGLE_TOP -> If set, the activity will not be launched if
it is already running at the top of the history stack.