androidfirebaseandroid-asynctaskstatic-methodsandroid-service-binding

How to communicate between Firebase Messaging Service and Activity? Android


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.


Solution

  • 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.