I use FCM (Firebase Cloud Messaging) in my app.
I have N activities in my app
During application's lifetime it may have any stack of activities:
Activity1 ->Activity2 -> Activity3-> ... -> ActivityN
I want to achieve behavior:
Go to ActivityN
Turn application to background
Click on notification
Turn application to foreground on ActivityN
Show dialog on ActivityN
How to achieve it?
I had the same issue in that I wanted the app to just resume in its current state when the Firebase background notification arrived. Part of my solution from this answer.
In my case, I had up to four activities on the stack: A->B->C->D.
In my app's manifest, I made activity D a singleTask activity (launchMode="singleTask") and put the intent-filter in that activity.
<activity android:name=".activityD">
<intent-filter>
<action android:name=".activityD" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
I then put activity D as the "click_action" in the notification sent by Firebase Messaging.
In my case, I didn't override the onNewIntent() method in activity D since the FirebaseMessageService's onMessageReceived method will get called when the app is brought to the foreground.
Most important: Because activity D is a singleTask activity, it is only created if it's not already on the stack. Therefore, in activity D's onCreate() method, I check the contents of the Intent extras. If the extras didn't come from activity C, I pop D immediately (finish();return;) because I know it was created by the Firebase notification. That resumes the app's activity (A, B or C) when it went into the background. If the app's activity was D when it went into the background, onNewIntent() method will be called instead of onCreate() in which case I do nothing as described in step #3.
After thinking about it some more, a more general solution would be to create a "dummy" singleTask activity with the intent filter, then always pop it off the stack in the onCreate() method to resume the current activity when it was put in the background. Surely there's a better way... I just couldn't find it.
in FirebaseMessagingService class>> onMessageRecived
Intent intent = new Intent(this, ActivityN.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
FLAG_ONE_SHOT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable._logo)
.setContentText(notification)
.setContentTitle(title)
.setAutoCancel(true)
.setSound(defaultSoundUri);
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
In your case where you are not sure to which Activity to be open on Notification click . Then you should broadcast on Notification click .
Create a Broadcast Reaceiver.
class NotificationClickReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
// here you will get Intent
// Check which activity is currently open and pass the data to it
// For passing data you can use a Another BroadcastReceiver or
}
}
Entry in manifest
<receiver
android:name=".NotificationClickReceiver"
/>
Then use pendingIntent to getBroadcast on notification click
Intent intent = new Intent(this, NotificationClickReceiver.class);
intent.putExtra("key","val");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 3, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable._logo)
.setContentText(notification)
.setContentTitle(title)
.setAutoCancel(true)
.setSound(defaultSoundUri);
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
Now you will get intent in onReceive of NotificationClickReceiver then you can figure out to which activity to pass it on.
Related
I'm using single activity architecture in my app. Now I'm stuck with handing notifications. I'm receiving notification from firebase and when app is on background, the google play services handle such notifications great. When it's tapped it brings the app from background to foreground (it does't recreate activity / app).
I need to have the same behaviour for notification received while the app is on foreground. Therefore I override onMessageReceived() in my firebase service and create new notification here. I tried many variation of Intent's Flags passed to the notification and launchMode in manifest but it always results into activity recreation (activity has different hashcode and it's onCreate() it's called) after tapping on notification created by onMessageReceived().
Here is the code:
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Logger.d("Msg received " + remoteMessage.getNotification().getTitle());
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(this, FCM_CHANNEL_ID)
.setContentTitle(remoteMessage.getNotification().getTitle())
.setContentText(remoteMessage.getNotification().getBody())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
.setStyle(new NotificationCompat.BigTextStyle().bigText(remoteMessage.getNotification().getBody()))
.build();
NotificationManagerCompat manager = NotificationManagerCompat.from(getApplicationContext());
manager.notify(FCM_NOTIFICATION_ID, notification);
}
manifest: android:launchMode="singleTask"
Any idea what to change to prevent activity recreation? (I'm testing on android 10, MainActivity extends AppCompatActivity)
Thanks.
Finally I get it work. All magic was done by adding those two lines to my code.
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
Original answer
Did you try this?
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Use the
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK flags.
This will start activity as the root of the stack.
I am using an external library in our project. That library showing notification. On tapping that notification starts activity in the library.
I want to detect that activity launch from push notification to track some analytics data.
Is there any way to detect those notification taps or activity launch?
As far as I understand you activity is already launched on notification tap. For detecting activity launch you can use ActivityLifecycleCallbacks. In such case you will need to override onActivityCreated/onActivityStarted which includes created/started activity as argument. You can inject analytics component inside and send events about launched activities.
class AppLifecycleCallbacks : ActivityLifecycleCallbacks {
override fun onActivityStarted(activity: Activity) {
if (activity is MyActivity) {
//...
}
}
//...
}
Pass some extra meta data along with your pending intent generated for showing notification. And parse same in your destination activity.
Intent destination = new Intent(context, HomeActivity.class);
destination.putExtra("SOURCE","NOTIFICATION");
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, destination, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setContentTitle("Notification Title")
.setAutoCancel(true)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.setContentIntent(pendingIntent)
.setContentInfo("App")
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_launcher))
.setColor(context.getColor(R.color.colorAccent))
.setLights(Color.RED, 1000, 300)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setSmallIcon(R.drawable.ic_like);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
And in activity level :
if(getIntent().getStringExtra("SOURCE").equals("NOTIFICATION")){
// launched from notification
}
If your activity is already running your intent might get delivered to :
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
Of course, yes. When you create Intent for launching your target activity you can pass arguments to the intent and when Activity starts just obtain outer arguments from intent and if these arguments came from Notification do your actions need.
In my application I unable to recreate the activity with new information (bundle data) when tap push notification. The following is the steps of my push notification process flow.
Step 1:
I open Activity B from when click the push notification with information showing in the Activity B.
Step 2:
Tap home button or go to some other activity so Activity B goes to background (OnPause) state.
Step 3:
Now again I open Activity B via clicking push notification with new information but Activity B goes to onResume instead of onCreate state.
My requirement is it should go to onCreate state so that I can process the with the new information. How can IActivity B goes to onResume instead of onCreate state do this? I did the following in the notification receiver class.
Intent intent = new Intent(this, ListScreenActivity.class);
Bundle bundle = new Bundle();
bundle.putInt("res_id", requestCode);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("uid", id_value);
intent.putExtras(bundle);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, requestCode, intent,0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext());
Notification notification = mBuilder
.setSmallIcon(R.mipmap.ic_launcher)
.setTicker(tickerMessage)
.setWhen(0)
.setAutoCancel(true)
.setContentTitle(app_name)
.setStyle(new NotificationCompat.BigTextStyle().bigText(tickerMessage)
.setContentIntent(resultPendingIntent)
.setContentText(tickerContent).build();
NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify((int) System.currentTimeMillis(), notification);
Note:
here ListScreenActivity (Activity B) is not Launcher activity
You can't change the Android activity lifecycle. You should expect this same thing can happen every time. onCreate is only called once per instance of an activity object. That can't be changed. You can read about the activity lifecycle to get more information.
If you want to close existing activity stack regardless of what's in there and create new root, correct set of flags is the following:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
I am creating notifications with following code:
Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setContentTitle(messageBody)
.setAutoCancel(true)
.setSmallIcon(R.drawable.top_icon)
.setContentText(senderName)
.setTicker(senderName+" ")
.setSound(soundUri);
Intent resultIntent = new Intent(this, LoginActivity.class);
resultIntent.putExtra("gcm_username",senderName);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_ONE_SHOT);
mBuilder.setContentIntent(resultPendingIntent);
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(NotificationID.getID(senderName), mBuilder.build());
When user clicks the notification I am catching it with following code in loginActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
gcmUserId= getIntent().getStringExtra("gcm_username");
startAction(gcmUserId);
....
My problem starts here.
Scenario:
1)When app is closed user receives notifications from 2 different users
2)User clicks first notification and app starts then startAction method calls.
3)Then user clicks the second notification
But when user clicks the second notification app has already started so startAction won't be able to call again because it is in the onCreate method.How can catch second notification ?
You can handle it inside onNewIntent(). You would need to override that method inside your activity. The docs say:
This is called for activities that set launchMode to "singleTop" in their package, or if a client used the FLAG_ACTIVITY_SINGLE_TOP flag when calling startActivity(Intent). In either case, when the activity is re-launched while at the top of the activity stack instead of a new instance of the activity being started, onNewIntent() will be called on the existing instance with the Intent that was used to re-launch it.
I am trying to add a button to my notification in android.
I use the addAction method in order to add an intent which supposes to open up the main activity (same as clicking the entire notification) but with an extra bundle with data.
this is what I have done so far:
notificationManager = (NotificationManager)this.getSystemService(NOTIFICATION_SERVICE);
//regular intent to view main activity
PendingIntent contentIntent = PendingIntent.getActivity(this,Constants.MAIN_ACTIVITY,
new Intent(this, MainActivity.class), 0);
//intent for viewing transaction dialog, within main activity using PURCHASE_DIALOG request code
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.LIST, (java.util.ArrayList<? extends android.os.Parcelable>) list);
PendingIntent purchaseIntent = PendingIntent.getActivity(
this, Constants.PURCHASE_DIALOG,
new Intent(this, MainActivity.class), 0, bundle);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(Constants.NOTIFICATION_TOPIC)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(watchResponse.toString()))
.setAutoCancel(true)
.addAction(R.drawable.ic_launcher, "Buy", purchaseIntent)
.setContentText(message);
mBuilder.setContentIntent(contentIntent);
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
I simply expect that my onResume in MainActivity class will be called once clicking the extra action, and there i'll be able to get the bundle trough getIntent(), yet nothing happens when I click on it. the button is clicked, but the activity remains open and my application activity wont start.
I had a very similar issue but a very different solution. Pending intent is also not fired if you have declared <service android:enabled="false"></service> in your manifest.xml file.
Replace from android:enabled="false" to android:enabled="true"
This might not be a direct issue of the problem. But if you create the service in android studio using default template it automatically adds these properties to the service.
If this does not work there is a similar question you can find here:
Android Notification Action is not fired (PendingIntent)