Android sync is rescheduled even when successfully completed [duplicate] - android

I'm writing my own ContentProvider which will be synced to a web service using a SyncAdapter.
Problem happens when the sync adapter is modifying the content provider's data the provider triggers a network sync when internally calling getContentResolver().notifyChange causing a sync loop.
The notifyChange with the network sync flag is required for when a client application does the modification but should be avoided when the sync adapter is modifying.
How can one, inside a contentprovider, easly tell if it's being used by a client application (which should trigger network sync upon modification) or by a sync adapter (which should not trigger network sync).
Currently I'm using different CONTENT_URI's (sync adapter accesses the data using a CONTENT_URI_NO_SYNC and client apps using a CONTENT_URI) to be able to distinguish between the two types of access and set the network sync flag accordingly.

Watch this video about REST API usage in SyncAdapters.
The method they discuss is to add a set of metadata flags columns to the database. This allows us to do 3 things.
The flags themselves allow the SyncAdapter to determine the rows that need changes and what those changes are. How do you tell the difference between a locally created row and a locally modified row? Furthermore how do you know which REST API call to make? If you just delete a row, how does your SyncAdapter know the row to be deleted if the data is now gone? Instead, set the "Should be deleted" flag, and then, when the SyncAdapter runs, it knows to push a delete to the server.
The flags allow your CursorAdapter to modify the view that is created (like adding a Spinner to show that "This row is being synced")
Finally, and this they don't point out, the flags allow you to tell why the row is being modified. If none of the flags are set and the row changes, it must have been because of an update from the server. Therefore, no need to sync to network.
So, the two workflows are as follows:
Local change
App creates new row. Row "create" flag is true.
ContentProvider stores the row, sees create flag and so it calls notifyChange(...,true);
Sync to network = true (the final parameter) causes SyncAdapter to fire.
SyncAdapter scans the database, finds the row with create flag set and performs appropriate server action. After success, SyncAdapter clears the flag.(row update on ContentProvivder)
ContentProvider sees the flag clear, no flags are left set, so it calls notifyChange(...,false);
ContentObservers see the flag change, update to look like "sync finished"
All these steps are equivalent for update / delete -- one flag per syncable row for each of create/update/delete.
Also notice the other win -- what if "Create" fails temporarily? server down... How do you know to retry? -- Simple, you don't clear the "Create" flag and you see it 15 minutes later.
Remote Change
SyncAdapter fires due to periodic sync.
SyncAdapter fetches an update from the server. Pushes changes into the database. Doesn't set any flags. ContentProvider sees the lack of flags, knows the change must have come from the server (or isn't a database change that needs to be pushed to the server), so it calls notifyChange(...,false);
ContentObservers see the content change and so they update with new row data

Related

Firebase RTDB: Call keepSynced while offline

I'm working on a Flutter app that uses Firebase RTDB. I'm a bit unclear on the mechanism of keepSynced():
Our app creates new collections and adds data to these collections. The app also observes these collections to display the data. In the process, we call keepSynced(true) in order to keep the offline cache alive for these queries. Now we noticed that, when creating a new collection + calling keepSynced(true) while offline, the listener we register on the new collection doesn't receive data until the app goes online again. From then on, the caching and updating the listeners works as expected (online + offline).
So, in code:
// Execute this while offline:
// Enable permanent offline sync for a new node that doesn't exist yet on the Firebase server
await database.ref().child('new_node').keepSynced(true);
// Register listener
database.ref().child('new_node').onValue.listen((event) => print(event));
// Add data
database.ref().child('new_node/child').set({'key': 'value'});
The listener will trigger only after the device goes online.
The documentation on keepSynced() states
By calling keepSynced(true) on a location, the data for that location will automatically be downloaded and kept in sync, even when no listeners are attached for that location. Additionally, while a location is kept synced, it will not be evicted from the persistent disk cache.
So is it actually required to be connected with the Firebase backend ("will automatically be downloaded") while calling keepSynced() in order for the offline caching/querying to work?
You can call keepSynced at any moment, but the client will only be able to download the data from the server when it is connected to the server.
If that is not the behavior you're observing, please edit your question to include a minimal repro.
Update based on the new repro in your question:
The behavior you're seeing is unfortunate, but expected.
The Firebase API guarantees (undocumented these days) that it will never surface a partial node to your app. Since you're only setting /new_node/child, and it has no knowledge of the current state of /new_node, it doesn't raise a value event on new_node since in only has partial information about that node.
If you were to instead listen for child_added on new_node, the SDK would raise an event in this scenario - but it won't raise a value event since until it can ascertain that it has a complete snapshot of new_node.
The two most common paths forward are:
Listen at a lower level in your JSON tree, for example by listening to child_* events (as mentioned above).
Perform an initial write operation on all of new_node, so that the local SDK knows what the expected initial state of the node is against it can then perform the lower-level writes. You could possibly then use security rules to reject this priming write operation on the server if there is also data that can't be wiped.

Logic to update network requests using Work Manager

I have an application which fetches data from an API. SO basically, right now, the app works as such:
If connected to the internet, fetch data and use Android Room to store for offline use
If not connected to the internet, check if data exists in Room. If exists, display it. If it doesn't exist, display an error message.
I did some research online on how to implement an efficient offline storing policy and Google suggests to use Work Manager to queue requests and then send it when connected.
I actually want to know how to implement this ? (not the code but the logic, i.e should i schedule requests everyday to the API or every time it's connected to the internet ?)
If someone with experience with offline apps could help would be great.
My network requests are done through Retrofit and i already create a class that perform calls to the API.
Keep in mind WM (work manager) is designed to perform operations when certain conditions are met (e.g.: the user has enough battery, the display is off, etc.). So this may end up with your data not being updated when you need it. WM is good for operations you want to happen but are not critical to occur "right now". I'd say always use the Room DB as the single source of truth. If the data is in room, show it, if it's not, fetch it, if you can't, well, you tried. Send a message to the user. You can use a NetworkConnectivityListener to monitor connectivity and check if you have a pending query (you could store the parameters of this query in your Room database in another table for ease of use). So you'd query the DB, obtain the pending queries (if any) and execute them, update the data, and let the ViewModel/Repository decide if there's a context to show this data (UI).
I feel like you are very close to achieve what you need.
So in other words:
UI: Observes its viewModel for some sealed class xxx state to tell it what to do (show an empty list, show an error, pass data to a recyclerview adapter, etc.).
ViewModel: Using its viewModelScope.launch { ... } will call a repository.fetch(...) or similar. Your viewModel will fetch this data when the Fragment tells it to do so (e.g. the user pressed a button) or on some lifecycle event (onStart for example).
The Repository in this case normally exposes a flow (if you can use the experimental api) or a suspend function that can perform the following actions (that can vary depending on your business rules)
If the data is available in the Database, return it immediately.
If the data is old (or we still want to refresh it), then perform the network API (if there's connectivity to do so). If there's No connectivity, you could store this "pending" query in the database for later. You could also check if you have a pending query before doing any of this, perhaps it's outdated or perhaps you need to execute it.
In any case, once the query goes through, you insert the results in the database, and call the same method you used in step 1.
Don't forget to update your "pending" query if you had one (or if you use this).
With WorkManager, you could schedule the "fetch data from API" part to happen at some point (so your data will be kept more up to date), but I all really depends on the use-cases you have.

Update firebase database after specific time

I have a data field and time field ,user Enter data and time
After Writing data on firebase realtime Database I want to reset data field and Time Field to 0 after The specifed time in Time Field
I read about cloud function but I don't know to solve my problem
I'm new to firebase and it's cloud function Please help...
Data field will contain somedata
Time Field Will Contain data like 20 ,30 or 40 which represent time in mins
so after writing both to database
say if time field contain 20 ,so after 20 min to writing data in database, it should be reset to 0
Thanks in advance
The scenario you describe could be achieved in two ways. In my opinion, one can be cheaper and easier (no function involved but need to add some logic and a query to data on client side) if you could implement it which depends on your case.
Not the cheapest one: Schedule (cron type jobs) to scan your database regularly and check for the specified time and reset your data if condition is met. And this requires you to use Firebase functions and cron type services to configure it.
The cheaper way (although dependent on your case) in my mind would be to set your logic client side, so whenever a user is navigating to that data , check for the time and if condition is met reset the data client side without client noticing. This way, you don't need to set up functions, and you are not performing anything as long as no client has gone there (wherever that the data your mentioned is used in your app) Just keep in mind that this depends on whether you can have such scenario or your data has to be update regardless of users interaction with it. I have managed to redesign stuff whenever I have come across a case that needs periodic updates, etc.
More info on second option:
Imagine your users are checking for an order which can expire after some time. Your intention is to reset data when expiry time has arrived. Instead of resetting data via functions, etc, you can write logic so that whenever a client queries the orders, you check for expiry time and if expired, you perform what has to be done there and then, in addition to making sure your client won't see the expired order. Hope this makes it more clear. It's sort of a passive way of updating your data in db.

Android Service updates table; How to get corresponding ListView to update?

Let's say I have a long-running service that monitors a network of external devices. Whenever it discovers a new device in the network, it adds a row to a table in a local SQLite database, and whenever it detects a change of state on a known device, it updates the corresponding row in the table. (BTW, there's no way to predict how frequently or infrequently these discoveries and updates will occur -- the external network marches to its own drummer!)
Now, let's also say I have an Activity (in the same process) that includes a ListView backed by the above-mentioned table. At the precise moment that the ListView is initially populated, it's of course in sync with the backing table -- but a moment (or two, or three) afterwards, because of changes in the external device network, the table changes, courtesy of the Service.
What's the best way for the Service to inform the Activity that it needs to refresh the ListView, i.e., call requery() on the Cursor, and then call notifyDataSetChanged() on the adapter?
I've seen a couple of posts here on SO in which the approach seemed to be "register a broadcastReceiver on the Activity, and send a broadcast Intent from the Service" -- but that seems a bit overkill for a local Service. Isn't there some simpler way?
For instance, I'm thinking that since I can get a handle to the local Service object via a Binder object, I might be able to register some kind of callback to the Activity, to be called by the service when needed. Does this sound like it would work? (Or would I be entering blind alley?)
Looking forward to your insights.
M

How is a SyncAdapter triggered by the system?

I have an application which uses a SyncAdapter to make a REST call to remote server, then use a Content Provider to persist the updates to the local SQLLite DB.
I can trigger the call by going the Accounts & Sync, then selecting my adapter, and using the Resync button to trigger the call
How does the system know when (how often) to make the onPerformSync() call in the SyncAdapter?
I am logging on the service to which the REST call resolves, but I am not seeing any calls unless I do it manually as described above.
Btw, i am running in the emulator at the moment, i have not installed this on a device yet
From your ContentProvider you must let your SyncAdapter know there was a change to the dataset.
You do this by calling notifyChange()
For example within your ContentProviders insert() method:
getContext().getContentResolver().notifyChange(rowUri, null);
It may not sync right away, but that is just how SyncAdapters work. The system decides when is a good time to sync. Usually it happens relatively fast in my experience. Of course you may not want it to sync every single time a change happens in your database, but you can always put code into your performSync to control the frequency of the syncs.
Hope this helps someone :)

Categories

Resources