How to communicate between Firebase Messaging Service and Activity? Android
Asked Answered
A

4

33

I know the question about how to communicate between a service and an activity has been answered many times but I also want my own way of doing this to be reviewed and to know if its an acceptable and the right way to do this and what are the drawbacks of how I handled it. First I will state the problem statement with as much detail as I can.

I have to build an app where I am using Firebase Messaging Service to communicate between two devices. Let's say its an Uber like system. One app is for the service provider(driver) and one for the customer(passenger). When the passenger requests to travel with their location, the drivers in a certain radius will get a push notification with a payload using Firebase. The Firebase service is running in the background. When the service receives a push notification, onMessageReceived method is invoked. An event is generated. I am not using Firebase here to generate notifications but actually transfer data between devices when I need to using the data field of Firebase push notification. Now the drivers app will receive the coordinates of where the user wants the car in the payload of Firebase push notification. I can simply start an activity with this data in the extras and show the driver that a request is received.

Now on the customer side, after the customer submits the request they are taken to the next activity where they are being shown a kind of loading screen telling them to wait for one of the drivers to accept their request. When one of the drivers accepts this user's request, this user will now receive a Firebase push notification with the designated driver's information in the payload of the push notification. Again the purpose is not to generate any notifications but to transfer data between devices.

Now that you understand the use case I will move on to the problem.

The problem arises when the user submits the request and moves on to the next waiting screen where he is shown a loading screen telling them to wait while there request is waiting to be accepted by one of the drivers. When a driver accepts the request, as I said the user will receive a Firebase push notification with the driver's information in the payload of the push notification. How do I communicate between the service and the Activity, to tell the activity to stop showing the loading screen and fill the TextView's with the data received in the payload of the push notification.

Here is how I have handled this. Suppose that I have an activity with the name AwaitingDriver, which has TextView's to be filled by the driver's data. But currently the activity is showing a loading screen because the request has not been accepted yet. Now the user receives a push notification with the driver's information in the service running in the background, not connected to the activity in any way. Here is my onMessageReceived method

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage){
        SharedPreferences rideInfoPref = getSharedPreferences(getString(R.string.rideInfoPref), MODE_PRIVATE);
        SharedPreferences.Editor rideInfoPrefEditor = rideInfoPref.edit();
        String msgType = remoteMessage.getData().get("MessageType");
        if (msgType.equals("RequestAccepted")){                
            rideInfoPrefEditor.putBoolean(getString(R.string.is_request_accepted), true);
            rideInfoPrefEditor.putString(getString(R.string.driver_phone), remoteMessage.getData().get("DriverPhone"));
            rideInfoPrefEditor.putString(getString(R.string.driver_lat), remoteMessage.getData().get("DriverLatitude"));
            rideInfoPrefEditor.putString(getString(R.string.driver_lng), remoteMessage.getData().get("DriverLongitude"));

            rideInfoPrefEditor.commit();
            AwaitingDriver.requestAccepted(); // A static method in AwaitingDriver Activity
        }            
    }

Here, AwaitingDriver.requestAccepted() is a static method of AwaitingDriver activity. Inside the AwaitingDriver activity itself, which is showing a progress dialog to tell the customer to wait, here is what the method AwaitingDriver.requestAccepted() is doing.

public static void requestAccepted(){
    try{
        awaitingDriverRequesting.dismiss(); //ProgressDialog for telling user to wait
    }catch (Exception e){
        e.printStackTrace();
    }        
    if (staticActivity != null){
        staticActivity.new TaskFindSetValues().execute();
    }
}

Here staticActivity is a static object of AwaitingDriver activity class declared inside this class. I am setting its value in onResume and onPause methods. Meaning if the activity is in the front, showing on the screen, only then will the value of staticActivity be not null. Here are the onResume and onPause methods.

@Override
public void onResume(){
    super.onResume();
    staticActivity = this;
    Boolean accepted = rideInfoPref.getBoolean(getString(R.string.is_request_accepted), false);
    if (accepted){        
       new TaskFindSetValues().execute();            
    }
}
@Override
protected void onPause(){
    super.onPause();
    staticActivity = null;
}

Here, TaskFindSetValues is an AsyncTask defined inside AwaitingDriver activity class. Here is the code for TaskFindSetValues

public class TaskFindSetValues extends AsyncTask<String, Void, String>{
    String phone;
    String lat;
    String lng;
    @Override
    protected void onPreExecute(){
        SharedPreferences pref = getSharedPreferences(getString(R.string.rideInfoPref), MODE_PRIVATE);            
        phone = pref.getString(getString(R.string.driver_phone), "");
        lat = pref.getString(getString(R.string.driver_lat), "");
        lng = pref.getString(getString(R.string.driver_lng), "");
    }
    @Override
    protected String doInBackground(String... arg0){
        return null;
    }

    @Override
    protected void onPostExecute(String returnValue){            
        awaitingDriverPhone.setText(phone); //setting values to the TextViews
        awaitingDriverLat.setText(lat);
        awaitingDriverLng.setText(lng);
    }
}

Please review this code and tell me about the drawbacks of doing this instead of the other solutions and if you could also explain the suggested way with the same example I'd be really grateful.

Aromaticity answered 29/1, 2017 at 20:39 Comment(1)
Check for EventBus. Its a better and easy way to do this.Cero
I
68

Instead of using AsyncTask, you can communicate with the Activity using BroadcastReceiver.

public class MyFirebaseMessagingService extends FirebaseMessagingService{
    private LocalBroadcastManager broadcaster;

    @Override
    public void onCreate() {
        broadcaster = LocalBroadcastManager.getInstance(this);
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        Intent intent = new Intent("MyData");
        intent.putExtra("phone", remoteMessage.getData().get("DriverPhone"));
        intent.putExtra("lat", remoteMessage.getData().get("DriverLatitude"));
        intent.putExtra("lng", remoteMessage.getData().get("DriverLongitude"));
        broadcaster.sendBroadcast(intent);
    }
}

and in your Activity

 @Override
    protected void onStart() {
        super.onStart();
        LocalBroadcastManager.getInstance(this).registerReceiver((mMessageReceiver),
                new IntentFilter("MyData")
        );
    }

    @Override
    protected void onStop() {
        super.onStop();
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
    }

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            awaitingDriverRequesting.dismiss();
            awaitingDriverPhone.setText(intent.getExtras().getString("phone")); //setting values to the TextViews
            awaitingDriverLat.setText(intent.getExtras().getDouble("lat"));
            awaitingDriverLng.setText(intent.getExtras().getDouble("lng"));
        }
    };

EDIT

According to @xuiqzy LocalBroadcastManager is now deprecated! Google says to use LiveData or Reactive Streams instead.

Incivility answered 30/1, 2017 at 8:11 Comment(12)
what's the need for unregister? Aka, what happens if you don't unregister?Awesome
my onCreate method in FirebaseMessagingService is never called. Any idea why?Jaan
did you override the onCreate method? And check if you have a typo, method's name is onCreate() not OnCreate()Incivility
@VassilisPallas If the activity is not on the foreground, then the activity will never be updated since the onStop has already been called and the broadcast has unregistered. Is there any alternatives to that case?Malva
@RedM You can't update the activity when the application is on the background. The views are null.Incivility
Did you mean "You can't update the activity when the activity... instead of application"? Also, That is not completely true if you are talking about the activity. I was able to do it using Observer pattern (used RXJava).Malva
this is very niceBauble
@RedM long time no see :P. I just saw your comment, lol. Well, of course, you can update the UI using RX while the app is in the background, but does this make any sense? If the app is in the background the user should be notified with a notification.Incivility
LocalBroadcastManager is now deprecated! Google says to use LiveData or Reactive Streams instead. I saw some people recommend Eventbus for it.Myotome
@VassilisPallas yes, it's been some time lol A good user case is sending a notification to the user when an update happened and the app is in the background, right?Malva
@RedM I would say yes. But this might be my personal idea for this scenario.Incivility
any example link to check with livedata communication with service and activity.Bohemian
R
4

Use Eventbus for background communication. It is best.

Just throw an event from your onMessageReceived() function calling

Eventbus.getDefault().post(Throw(value))

Create an event model for your event. Every event model must be unique.

class Throw(val value :Object) {}

Then in your activity just register the desired function with your event model. It will receive when you fire an event. It is easy to implement and more understandable.

override fun onStart() {
    super.onStart()
    EventBus.getDefault().register(this)
}


override fun onStop() {
    EventBus.getDefault().unregister(this)
    super.onStop()
}

@Subscribe(threadMode = ThreadMode.MAIN)
fun onThrowEvent(t : Throw) {
    // Do your staff here
}

You can also catch your event in background. Just change the ThreadMode. I will suggest you to give it a try

@Subscribe(threadMode = ThreadMode.ASYNC)
@Subscribe(threadMode = ThreadMode.BACKGROUND)
Reactor answered 19/8, 2018 at 11:30 Comment(0)
C
3

In the onMessageReceived method you could send an in application broadcast, transferring the driver details. The request accepted method would then be replaced by the broadcast receiver onReceive method. That way there is no need to write to the shared preferences or use a aSyncTask which is basically doing nothing as the doinbackground method just returns null . You are only using the onPreExecute and onPostExecute methods, which are both executed on the main thread.

Coworker answered 29/1, 2017 at 20:59 Comment(1)
I used AsyncTask because I couldn't access the activity's member variables and TextViews through a static method requestAccepted() but I could through the AsyncTask executed from the static methodAromaticity
H
-1

Why don't you simply create an intent within the onMessageReceived method, restart the activity, and pass on the driver's data in the extra's?

Hydroponics answered 29/1, 2017 at 21:32 Comment(2)
Because I don't want the customer seeing the activity restarting. In the use case the customer is actually watching the loading screen waiting.Aromaticity
Ok, that makes sense.Hydroponics

© 2022 - 2024 — McMap. All rights reserved.