13

When my application is open and I receive a notification I want to be able to open the activity associated immediately without the need of the user to tap on the notification.

This question is very similar: Open app on firebase notification received (FCM)

But it opens the app when it is in background, I need to do it when my app is in foreground.

From the firebase documentation:

Notifications delivered when your app is in the background. In this case, the notification is delivered to the device’s system tray. A user tap on a notification opens the app launcher by default. Messages with both notification and data payload, both background and foreground. In this case, the notification is delivered to the device’s system tray, and the data payload is delivered in the extras of the intent of your launcher Activity.

This is my implmentation of onMessageReceived

@Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

       // Check if message contains a data payload.
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());
        }

        // Check if message contains a notification payload.
        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
            sendNotification( remoteMessage);              
        }     
    }

    /**
     * Create and show a simple notification containing the received FCM message.
     *
     * @param remoteMessage FCM message message received.
     */
    private void sendNotification(RemoteMessage remoteMessage) {
        Intent intent = new Intent(this, MyActivity.class);

        Map<String, String> hmap ;
        hmap = remoteMessage.getData();
        hmap.get("data_info");
        intent.putExtra("data_info", hmap.get("data_info"));
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);


        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);

    }

I am able to get the notification correctly but the activity only starts once I tap the notification on the system tray.

Is there a way to start the activity without tapping the notification while in foreground?

The method onMessageReceived() from the class MyFirebaseMessagingService that extends FirebaseMessagingService is getting called correctly while in foreground, but the activity is not getting started. I have also tried with the flag FLAG_ACTIVITY_NEW_TASK also with no luck. Thanks in advance.

Community
  • 1
  • 1
bankode
  • 141
  • 1
  • 2
  • 6

8 Answers8

4

You can achieve that registering a broadcast receiver in you foreground activity and sending a broadcast from your onReceiveMessage() method.

ForegroundActivity

mReceiver = new BroadcastReceiver() {
 @Override
 public void onReceive(Context context, Intent intent) {
     Intent myNewActivity = new Intent(this, MyActivity.class);
     startActivity(myNewActivity);
   }
 };

mIntentFilter=new IntentFilter("OPEN_NEW_ACTIVITY");

@Override
protected void onResume() {
     super.onResume();
     registerReceiver(mReceiver, mIntentFilter);
}



@Override
protected void onPause() {
     if(mReceiver != null) 
            unregisterReceiver(mReceiver);
            mReceiver = null;
     }
     super.onPause();
   }

FirebaseNotificationReceiver

@Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

   // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());
    }

    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
        sendNotification( remoteMessage);  

        Intent broadcast = new Intent();
        broadcast.setAction("OPEN_NEW_ACTIVITY);
        sendBroadcast(broadcast);
    }     
}

You can add a check to know if the app is in foreground or not to choose between send a notification or send a broadcast.

Community
  • 1
  • 1
cdiazmo
  • 94
  • 1
  • 4
  • Where should the ForegroundActivity code go, inside Myactivity.class (the activity that should get open) or in a different activity? – bankode Sep 07 '16 at 00:39
  • In a different activity. The one is supposed to be used by the user in the moment of the notification arrival. i.e: the user is with the app opened and checking some content, you can have a MainActivity class and declare the observers there, just mind if the user leaves that activity the broadcast notification will not be captured. – cdiazmo Sep 08 '16 at 10:19
  • This is wrong and won't work reliably. Runtime-registered receivers only work while the app is running. Not to mention the memory leak from that non-static inner class keeping the Activity from being garbage collected. Finally, to whatever extent that this did work when posted, it [won't work on Android 10 or later](https://developer.android.com/guide/components/activities/background-starts). – Ryan M May 10 '23 at 23:44
1

I was able to achieve this by calling send() on the pendingIntent:

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
                PendingIntent.FLAG_ONE_SHOT);
try {
       pendingIntent.send();
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
    }
bankode
  • 141
  • 1
  • 2
  • 6
  • Thanks for `PendingIntent.FLAG_ONE_SHOT`! In case we then open an app from recent apps, it gets open with void `extras`, while with `FLAG_UPDATE_CURRENT` it opens every time with the same `extras` that we set in `PendingIntent` in `onMessageReceived`. – CoolMind Aug 27 '20 at 13:30
1

You need to get infos about the current foreground app in your device. Based on that you can decide if start the activity or send a notification.

In order to do this I'll suggest something like this:

public void onMessageReceived(RemoteMessage remoteMessage) {
    // Check if message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "Message data payload: " + remoteMessage.getData());
    }

    // Check if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
        Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
        handleNotification(remoteMessage);              
    }  
}

private void handleNotification(RemoteMessage remoteMessage){
    Intent intent = new Intent(this, MyActivity.class);
    Map<String, String> hmap ;
    hmap = remoteMessage.getData();
    hmap.get("data_info");

    intent.putExtra("data_info", hmap.get("data_info"));
    intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

    Context context = getApplicationContext();

    //CHECK IF THIS APP IS IN FOREGROUND
    ActivityManager am = (ActivityManager)
    AppService.this.getSystemService(ACTIVITY_SERVICE);

    // The first in the list of RunningTasks is always the foreground task.
    RunningTaskInfo foregroundTaskInfo = am.getRunningTasks(1).get(0);
    String foregroundTaskPackageName = foregroundTaskInfo .topActivity.getPackageName();

    if(foregroundTaskPackageName.equals(context.getPackageName()){
        //THIS STARTS MAINACTIVITY DIRECTLY IF THE FOREGROUND APP IS THIS APP
        startActivity(intent);
    }else{
        //IF THE FOREGROUND APP ISN'T THIS APP THEN SEND A PENDING INTENT TO OPEN MAIACTIVITY WHEN USER TAP ON NOTIFICATION
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
            PendingIntent.FLAG_ONE_SHOT);
        //CREATE A NOTIFICATION IN THE SYSTEM TRAY
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setContentTitle("TITLE")
                .setContentText("SUBMESSAGE")
                .setPriority(Notification.PRIORITY_MAX)
                .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);
        NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

        // notificationId is a unique int for each notification that you must define
        notificationManager.notify(notificationId, mBuilder.build());
    }
}

You have to set the notification id, the title and the submessage that will be shown in the notification.

You will also need <uses-permission android:name="android.permission.GET_TASKS" /> permission to be added in the app manifest.

IronJohn
  • 306
  • 2
  • 8
  • 1
    If I use `PendingIntent` for all situations, it opens an activity. What is the reason to separate foreground and background? – CoolMind Aug 27 '20 at 14:40
1

(kotlin) use this code inside onMessageReceived if you want to check is app either foreground or background

    var foreground = false

    try {
        foreground = ForegroundCheckTask().execute(this).get()
    } catch (e: InterruptedException) {
        e.printStackTrace()
    } catch (e: ExecutionException) {
        e.printStackTrace()
    }

then use the "foreground" variable to do action as you needed

 if (foregroud) { //app in foreground
            intent = Intent(this, ChatAdminActivity::class.java)
            intent.putExtra("intent_backchat", 1)
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK)
            pendingIntent = PendingIntent.getActivity(this, Integer.valueOf(random) /* Request code */, intent, PendingIntent.FLAG_UPDATE_CURRENT)
            startActivity(intent)      // to directly open activity if app is foreground
        } else { //app in background
            intent = Intent(this, ChatAdminActivity::class.java)
            intent.putExtra("intent_backchat", 1)
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
            pendingIntent = PendingIntent.getActivity(this, Integer.valueOf(random) /* Request code */, intent, PendingIntent.FLAG_UPDATE_CURRENT)
        }
.....

hope its helping..

and you can see my full FCMService code

fayerketto
  • 39
  • 1
  • 5
  • If I use `PendingIntent` for all situations, it opens an activity. What is the reason to separate foreground and background? – CoolMind Aug 27 '20 at 14:40
  • @fayerketto why do you need `PendingIntent` when the app is in background anyway? – Papps Sep 02 '20 at 06:56
  • @Papps, thanks for the interesting question. I think, there is an error in the code. The author creates `pendingIntent` depending on the state of the app when a push notification is received (in `onMessageReceived`), not when a user clicks the push notification. So, when the push notification shows, it doesn't mind, but when the user clicks, it opens the app depending on the state that was before (for instance, an hour before). So, we don't need to divide two situations. – CoolMind Sep 07 '20 at 10:55
  • 1
    @Papps, I think `PendingIntent` is needed to open an activity, when the app is in background. – CoolMind Sep 07 '20 at 10:56
  • The code for `ForegroundCheckTask` should be in this answer, not behind a link. Also, that use of an AsyncTask does not make sense at all, given that you're calling it and then immediately, synchronously, blocking on the main thread waiting for it to finish. Finally, your "background" case doesn't...do anything. It doesn't actually launch anything. It just sets a variable to an updated PendingIntent, then returns. – Ryan M May 10 '23 at 23:48
0
public class MyFirebaseMessagingService extends FirebaseMessagingService {
 private static final String TAG = "FCM Service";
 @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        // TODO: Handle FCM messages here.
        // If the application is in the foreground handle both data and notification messages here.
        // Also if you intend on generating your own notifications as a result of a received FCM
        // message, here is where that should be initiated.
//     IntentFilter filter = new IntentFilter("OPEN_NEW_ACTIVITY");
//        registerReceiver(new BroadcastNotification(),filter);
//     showNotification(remoteMessage.getNotification().getBody());

        Log.d(TAG, "From: " + remoteMessage.getFrom());
        Log.d(TAG, "Notification Message Body: " + remoteMessage.getNotification().getBody());
     Log.e("Myname","shdjhfghgh");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Intent in= new Intent(this,MainActivity.class);
        in.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(in);
    }
}
  • Welcome to StackOverflow, please add some description on your answer and describe which part that solve the question, so that others can understand more easily. – koceeng Jan 05 '17 at 05:57
  • [This won't work on Android 10 or later](https://developer.android.com/guide/components/activities/background-starts). – Ryan M May 10 '23 at 23:51
0

Creating BroadcastReceiver is the best way of handling for your scenario. But you need to know which activity is being used by the user.

Creating BroadcastReceiver in every activity gives odd look. So Create one BaseActivity which extends Activity. BaseActivity will have the BroadcastReceiver code, and All other activity extends this BaseActivity.

open class BaseActivity : AppCompatActivity() {

private lateinit var broadcastReceiver: BroadcastReceiver

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(p0: Context?, p1: Intent?) {
            if (p1 != null) {
                handleBroadcastActions(p1)
            }
        }

    }
}

private fun handleBroadcastActions(intent: Intent) {
    when (intent.action) {
        Constants.ACTIVITY_STUFF -> {
            onHandleActivity()
        }
    }
}

protected open fun onHandleActivity() {
    startActivity(intentFor<YourActivity>())
}

override fun onResume() {
    super.onResume()
    registerReceiver(broadcastReceiver, IntentFilter(Constants.ACTIVITY_STUFF))

}

override fun onPause() {
    super.onPause()
    unregisterReceiver(broadcastReceiver)
}}

I have added my kotlin code. Hope you can understand :)

Finally you can call this BroadcastReceiver from onMessageReceived() in FirebaseMessagingService.

override fun onMessageReceived(message: RemoteMessage) {
    sendBroadcast(Intent(Constants.ACTIVITY_STUFF))
}
Xpleria
  • 5,472
  • 5
  • 52
  • 66
dpvmani
  • 103
  • 3
  • what is Constants.ACTIVITY_STUFF here? – Sid Nov 03 '20 at 06:19
  • @Sid You can use any common string. e:g object Constants { const val ACTIVITY_STUFF = "handle push notification" } – dpvmani Nov 10 '20 at 13:19
  • This is wrong and won't work reliably. Runtime-registered receivers only work while the app is running, and to make matters worse, you're unregistering it when the activity is paused, so this _definitely_ won't work at all when the app is in the background. Finally, even with that fixed, the approach of launching an Activity from a BroadcastReceiver like this [won't work on Android 10 or later](https://developer.android.com/guide/components/activities/background-starts). – Ryan M May 10 '23 at 23:48
0

According to the Google page about Broadcasts:

If you don't need to send broadcasts to components outside of your app, then send and receive local broadcasts with the LocalBroadcastManager which is available in the Support Library. The LocalBroadcastManager is much more efficient (no interprocess communication needed) and allows you to avoid thinking about any security issues related to other apps being able to receive or send your broadcasts. Local Broadcasts can be used as a general purpose pub/sub event bus in your app without any overheads of system wide broadcasts.

So, prefer use LocalBroadcastManager instead of BroadcastReceiver.

In the FirebaseMessagingService class, you receive the notification from FCM (Firebase Cloud Messaging) and use:

LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

to send a message to the MainActivity:

public class AppFirebaseMessagingService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        try {
            String messageFrom = remoteMessage.getFrom();
            String messageBody = (remoteMessage.getNotification() != null ? remoteMessage.getNotification().getBody() : null);
            Map<String, String> messageData = remoteMessage.getData();

            NotificationInfo notificationInfo = new NotificationInfo(messageData.get(message));
            notifyMainActivity(notificationInfo);
            showNotification(notificationInfo);
    }

    private void notifyMainActivity(NotificationInfo notificationInfo) {
        Intent intent = new Intent();
        intent.setAction(Constants.BROADCAST_MESSAGE_NOTIFICATION_RECEIVED);
        intent.putExtra(Constants.PARAM_NOTIFICATION_INFO, notificationInfo);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void showNotification(NotificationInfo notificationInfo) {

        Intent intentNotificationClicked = new Intent(this, MainActivity.class);
        intentNotificationClicked.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intentNotificationClicked.putExtra(Constants.PARAM_NOTIFICATION_INFO, notificationInfo);
        PendingIntent resultIntentNotificationClicked = PendingIntent.getActivity(this, 0, intentNotificationClicked, PendingIntent.FLAG_ONE_SHOT);

        String title = "MY APPLICATION";
        String message = notificationInfo.message;

        Uri notificationSoundURI = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder mNotificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_notification_icon)
                .setContentTitle(title)
                .setContentText(message)
                .setAutoCancel(true)
                .setSound(notificationSoundURI)
                .setContentIntent(resultIntentNotificationClicked);

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        int notificationID = Integer.parseInt(new SimpleDateFormat("HHmmssSSS").format(new Date()));
        notificationManager.notify(notificationID, mNotificationBuilder.build());
    }
}

And in MainActivity you create the BroadcastReceiver to receive the message:

public class MainActivity extends AppCompatActivity {
    private NotificationBroadcastReceiver mNotificationBroadcastReceiver = null;
    private IntentFilter mIntentFilter = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        this.mNotificationBroadcastReceiver = new NotificationBroadcastReceiver(this);
        this.mIntentFilter = new IntentFilter(Constants.BROADCAST_MESSAGE_NOTIFICATION_RECEIVED);
    }

    @Override
    protected void onResume() {
        super.onResume();
        LocalBroadcastManager.getInstance(this).registerReceiver(this.mNotificationBroadcastReceiver, this.mIntentFilter);
    }

    @Override
    protected void onPause() {
        if (this.mNotificationBroadcastReceiver != null) {
            unregisterReceiver(mNotificationBroadcastReceiver);
            this.mNotificationBroadcastReceiver = null;
        }

        super.onPause();
    }

    private class NotificationBroadcastReceiver extends BroadcastReceiver {

        WeakReference<MainActivity> mMainActivity;

        public NotificationBroadcastReceiver(MainActivity mainActivity) {
            this.mMainActivity = new WeakReference<>(mainActivity);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            MainActivity mainActivity = mMainActivity.get();
            if (mainActivity != null) {
                Bundle extras = intent.getExtras();
                if (extras != null && extras.containsKey(Constants.PARAM_NOTIFICATION_INFO)) {
                    NotificationInfo notificationInfo = (NotificationInfo) extras.getSerializable(Constants.PARAM_NOTIFICATION_INFO);
                    mainActivity.notificationReceived(notificationInfo);
                }
            }
        }
    }

    public void notificationReceived(@NonNull final NotificationInfo notificationInfo)
    {
        //handle the notification in MainActivity
    }
}

Below the classes referenced in code:

public class Constants {
    public static final String NOTIFICATION_BROADCAST_RECEIVER_MESSAGE_RECEIVED = "com.mycompany.myapp.NOTIFICATION_RECEIVED";
    public static final String PARAM_NOTIFICATION_INFO = "NotificationContent";
}

public class NotificationInfo implements Serializable {
    public String message;
    public NotificationInfo() { }
    public NotificationInfo(String message) { this.message = message; }
}

That's it!

educoutinho
  • 869
  • 9
  • 16
0

What i usually do is send data message and create a intent from it.

public void onMessageReceived(@NonNull RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);
    Log.d(TAG, "onMessageReceived: called");

    Log.d(TAG, "onMessageReceived: Message received from: " + remoteMessage.getFrom());

    if (remoteMessage.getNotification() != null) {
        String title = remoteMessage.getNotification().getTitle();
        String body = remoteMessage.getNotification().getBody();
        Log.d(TAG, "onMessageReceived: "+title+" "+body);

        Notification notification = new NotificationCompat.Builder(this, FCM_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ridersquare)
                        .setContentTitle(title)
                .setContentText(body)
                .setColor(Color.BLUE)
                .setSubText("KKKK")
                .build();

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(FCM_CHANNEL_ID, "Default channel", NotificationManager.IMPORTANCE_HIGH);
            manager.createNotificationChannel(channel);
        }
        manager.notify(1002, notification);
    }

    if (remoteMessage.getData().size() > 0) {
        Log.d(TAG, "onMessageReceived: Data: " + remoteMessage.getData().toString());
        if (remoteMessage.getData().toString().length()>0){
            Notification notification = new NotificationCompat.Builder(this, FCM_CHANNEL_ID)
                    .setSmallIcon(R.mipmap.ridersquare)
                    .setContentTitle("New Order")
                    .setContentText("New order received from"+remoteMessage.getData().get("restaurant_name"))
                    .setSound(App.soundUri)
                    .setLights(Color.RED, 3000, 3000)
                    .setAutoCancel(true)
                    .setPriority(NotificationCompat.PRIORITY_HIGH)
                    .setVibrate(new long[]{0,1000, 500, 1000})
                    .build();

            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                NotificationChannel channel = new NotificationChannel(FCM_CHANNEL_ID, "Default channel", NotificationManager.IMPORTANCE_HIGH);
                manager.createNotificationChannel(channel);
            }
            manager.notify(50, notification);
            Intent intent=new Intent(getApplicationContext(), NewOrderNoti.class);
            intent.putExtra("username",remoteMessage.getData().get("username"));
            startActivity(intent);

        }


    }

This basically creates a notification and calls a intent to open new activity

  • [This won't work on Android 10 or later](https://developer.android.com/guide/components/activities/background-starts). – Ryan M May 10 '23 at 23:52