how to handle syncing topics across multiple devices in firebase - android

I have a system where one user can be logged into multiple devices. The use case is as follows:
Suppose the user A subscribes to a weather topic from device A. This user will now get regular push messages from this topic.
Now assume that the same user is logged in to a device B. This device also needs to get subscribed to the weather topic as there is no gurantee that the user will just use device A.
Same use case can be applied in reverse case of unsubscription as well as more than two devices.
What is the best possible way to solve such a problem.
The current approaches which i have thought of are:
1) Make a node under each user id inside firebase db called subscriptions. This node will have subscribed data in the form of
Topidname :boolean issubscribed
This approach has a listener attached to this location in a background service. Each device with the same user id will always listen to the same location thus solving the issue in most cases. It can't gurantee integrity due to the normal service lifecycle reasons.
A service is used because if the listener is kept only for the lifetime of the app, if the user unsubscribes from a topic on one device and the second device app is closed, the second app will keep receiving notifications as it's registration token is still related to that topic and will only unsubscribe on app restart.
Here the service has been made START_STICKY, has a listener inside onCreate and has a backup alarm set to restart the service in onDestroy method. Thus the only case where this won't work is an app force stop or the listener being in a backoff mode due to long non connectivity.
When the app launches it also syncs once with the location and subs the left out topics. Service will stop on signout and unsub from all the previous user's topics. I have kept a keepSynced on the location to ensure it stays in sync
Potential issues are -> service killed by force stop. May cause some ram usage. Sync might not be instantaneous depending on how long the connectivity was lost and how long it takes firebase to resync with the db. Resync and sub unsub all topics in each service restart.
2) This is a relatively more complicated approach:
Save the firebase token from the instance id service for each user in the app server.
On each signout, instead of calling delete iid, keep the same token but overwrite the user attached with the new logged in uid.
On each subscription, do a bulk subscribe to the topic using the Instance Id server api using all the registration tokens for one user.
Each time a user signs out call unsubscribe on all the topics one by one using the instance id api for that device's particular token. Same process for token refresh.
Do to same on unsubscription of each topic.
This case will need one synchronous call for all the topics subscriptions and unsubscriptions. It will also require the token to be constantly updated in the app server.
Please suggest any other better scenario to solve this issue.
If my scenarios have any flaws or needs modifications, please do suggest. I'm stuck and can't think of anything else at the moment.

I'm assuming that you're keeping track of each of the tokens for a user's devices. So the code on your server should be able to know, for a given user, how to send a message to all of their devices, regardless of subscriptions.
Try thinking of user topic subscriptions in the same way that you think of the data broadcasted within those topic. When topic subscriptions change for a user, that event could be broadcast to all of their devices with FCM, telling your app to read the subscriptions from your database and set subscriptions again as necessary. Your app will only wake a receive a notice to change subscriptions when the user requests it.
Then, when it comes time to publish a message to the topic, you can be sure that all the devices that checked in like this will receive the message to the topic.
To boil it down:
1. When a user's subscriptions change in your app, notify the server of that change.
2. In your server, update the user's subscriptions to your database.
3. Then, send an FCM data message to all of the user's devices to reload subscriptions.
4. When that message is received in your app, read the subscription updates out of your database and update the FCM subscriptions accordingly. Your app will have to arrange to stay alive for as long as it takes to do this.

Related

Out-of-box solution for Android for guaranteed delivery of http requests

I have an Android application that has a screen for displaying information about any user of the service for which mobile application was developed. Users are able to subscribe to other users on that screen.
Current solution with Rx and Retrofit works fine but when user leave the screen I clear disposables and then the http call under the hood of Retrofit is being canceled. So if user leaves the screen his operation on it (such as subscribing or likinng/disliking) get lost.
I want to keep that requests which modify some data (usually POST-request) and send them until backend of my application will receive it and send a response. Moreover I want to persist that request on disk to survive the app's process death. It means that if user leaves screen and system kills my app then after it launched again it will perform an attempt to send saved requests. Of course if there is an internet connection.
I wonder if out-of-box solution for that purposes exists. I appreciate any advice.
Workmanager can be used to schedule this kind of request.

Realtime Database show if data is still hasn't been uploaded

so I'm working with realtime database and I'm trying to make a chat app for practice.
I want to add the message to my list then let the message item inside the list upload the message to the server, I'm also showing an indicator that tells the user if the message is being uploaded, there are other approaches to apply this but I want to go with this one, anyways.
there isn't any problem when there is an internet connection. the problem is when there isn't any internet connection, the message is added to the list and the indicator appears, when the internet connection returns everything works fine still.
but if I send a message (while offline) and then leave the chat room and return to the chat room, the messages will get loaded and the indicator won't appear altho it isn't uploaded to the server (the data is cached now).
I want to find a way to tell if the data has been uploaded or not? I don't want to check the server to see if the node exists, I can't do that to every message it will cost too much, thank you.
If you enable disk persistence, Firebase keeps all of its pending writes in its disk cache. When the app restarts, it reads those pending writes and starts trying them. This is usually the right behavior for your users.
Unfortunately there is no built-in way to persist completion handlers for the Realtime Database. So upon a restart it becomes impossible for you to detect when the pending writes have been committed on the server.
So this typically means that you need to do something custom to detect the situation, and will have to determine for yourself whether the use-case is worth the effort.
If your messages are in some way ordered/timestamped (for example, if you add them by calling push()) you can keep track of what the last message is for which you received a confirmation from the server. That way you will know when the client restarts, which messages may not have been sent to the server yet.
Your onDataChange or onChildAdded will be called for those unconfirmed message straight away though when your app restarts, so you'll need an additional mechanism to detect when those unconfirmed messages are written on the server.
The best approach I know if is to write a "dummy" message when the app starts. Since the pending writes are treated as a first-in-first-out queue, your new dummy message write will be sent to the server after all the pending writes from the previous run. So when your completion handler gets called for this dummy message, you can be sure that all messages before it have also been committed (or rejected in case they violate your security rules).
Firebase cloud functions fires an onFinalize event when a file has been uploaded to the storage. So you could probably write a cloud function like this.
exports.uploadedServer = functions.storage.object().onFinalize((object) => {
const filename = object.name
//mark this filename or filekey as upload complete
return
})
You should be able to find more explanation here.

Firebase onDisconnected called when closing the app by swiping it out

I got an app which uses Firebase.
The Firebase's records holds a "status" field which is supposed to give information if the user is offline or online.
I initialize it like that:
fireBase = new Firebase("https://myProj.firebaseio.com/users");
child = fireBase.child(MyApp.myStringIdentifier);
child.child("status").setValue(ClientStatus.ONLINE);
child.child("status").onDisconnect().setValue(ClientStatus.OFFLINE);
The initialization works fine, and the onDisconnect() is also called after a lot of time there's no internet connection and firebase does change the status to offline as expected.
The problem is that the onDisconnect event is also called when the user swipes out the app from the recent apps list, but I want the user to remain online since I got services which are still running in the background which should handle some events even when the app is closed (They are still running, but other users get the offline status of the closing user and then the actions to that user are blocked) .
Is there a way to prevent to onDisconnect event to be called when the user swipes out the app?
The onDisconnect handlers of your Firebase Database client are called:
when the database client actively disconnects from the Firebase server
when the server detects that the database client has disappeared (by the socket connection timing out)
Note that neither of these has anything to do with your application lifecycle, which seems to be what you are interested in.
If you want to change the database when your application is destroyed, you should probably listen to application lifecycle events to detect when the application exits.
When the user of an Android devices uses the overview display to "swipe away" an app, that's a signal that they don't want to use the app any more, and they're not interested in returning to it any time soon. Android will kill the app process. Killing the app process will close the connection that it has to your Realtime Database. Closing the connection will cause onDisconnect() to execute on the server. You have no control over this process when the user makes their decision. This is by design of the Android platform, which is to allow the user to make the final decision about what can actually run on their device. As the app developer, you are not empowered to force your own decision on the matter.
If you want to send the app a message even though it's been killed by the user, you can instead use Firebase Cloud Messaging to deliver events that the user has expressed interest in.

Android Firebase Cloud Messaging(FCM): Will subscribeToTopic do automatic retries?

To Subscribe to a topic in an android client we should call:
FirebaseMessaging.getInstance().subscribeToTopic("news");
I want to know what happens if the internet connection is not available at moment that this instruction is executed?
Will google services automatically retry to do subscription when Internet connection become available or we developer should handle this case?
Update:
subscribeToTopic() now returns a Task<Void> so you could attach an OnCompleteListener() to check if it is successful or not.
Update:
So it seems there's more to subscribeToTopic, as per #DiegoGiorgini's comment in your previous post:
subscribeToTopic will keep retry on background, but it's tied to your app lifecycle (not to google services, yet). So if your app is killed it will stop retrying until the app is open again. (the action is stored so it will be resumed when the app is started again)
The way I was trying out before was after the given period, I will be killing the app and didn't pull it up again.
So I've tried checking this out. It seems that if the device is offline when the request for subscription is sent, it will re-try for a period of time (20-30 seconds or so?) then will stop if still not connected. The request probably reached a time-out error. But since there is no return value for subscribeToTopic(), there is no way for you to determine this via your client app. You can send in a Feature Request if you want, pertaining to this.
However, as of the moment, an approach I think that you could do is to have a checker in your client app, where if only the device is online will you send the request.
You can also check it via your app server as I've mentioned in your previous post.

When SyncAdapter runs synchronization on android?

Let's say, my application implements SyncAdapter functionality and doesn't define periodic syncs. When synchronization will happen in such scenario? First scenario I may think about is local ContentProvided/database content change.
What is about server changes? How SyncAdapter will know about that?
If you have no periodic sync setup, Sync will happen if your code explicitly calls ContentResolver.requestSync(Account account, String authority, Bundle extras) with your account and authority.
Also, if your ContentProvider insert or update or delete functions call ContentResolver.notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork), if the bool syncToNetwork is true (the default), it will also trigger a sync. There's a short delay induced here, to ensure that a batch of database changes only causes one sync, not one per change. Note that your code should be calling notifyChange because it's how Android signals your UI to update after Content that the UI is reflecting had been changed.
If the server database changes, your app won't know, because sync isn't happening. Two options:
Use periodic sync. This will be cleaner if your server API implements etags or the if-modified-since http headers to filter the data you sync so only the updates come down.
C2DM (Cloud 2 Device Messaging) Essentially, push notification for Android. Requires some server components -- You tie a device ID to an account on the server and when the server changes, it has to explicitly send a message to the device to tell it to update. This is custom code work on the server to support android specifically, but once you invest the time, it's great. C2DM is how Android gets gmail to show up on your device 10 seconds after it arrives in your inbox, rather than at the next 10 minute periodic sync. It's also more battery efficient since you only turn on the radio and sync when you know there's new data to get.

Categories

Resources