Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 642
Rehan Yousaf Avatar asked Jan 29 '17 20:01

Rehan Yousaf


People also ask

How does FCM work in Android?

Using FCM, you can notify a client app that new email or other data is available to sync. You can send notification messages to drive user re-engagement and retention. For use cases such as instant messaging, a message can transfer a payload of up to 4000 bytes to a client app.

How do I get my messages from Firebase?

To receive messages, use a service that extends FirebaseMessagingService. Your service should override the onMessageReceived and onDeletedMessages callbacks. It should handle any message within 20 seconds of receipt (10 seconds on Android Marshmallow).


2 Answers

Why are you using AsyncTask? It doesn't make sense. Anyway, if you want to communicate with the Activity you can do it with 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.

like image 182
Vasileios Pallas Avatar answered Oct 05 '22 01:10

Vasileios Pallas


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) 
like image 27
Shaon Avatar answered Oct 05 '22 02:10

Shaon