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.
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.
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).
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With