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 :)
Related
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.
i previously used ContentObserver to get notified about changes in device's contacts list.
the problem is i need that to work in background (when application is closed as well), so i used a service for that, but still it dosn't work (unless i declare the service to work on foreground - and we don't want that to happen).
so i saw another solution of using SyncAdapter.
i have my own storage solution and i'm not using ContentProvider in my application.
also i have my own implementation for detecting which contact has been added/removed/changed.
the only thing i need, is detecting WHEN that happen and get notified about it.
Do i need a stub content provider to achieve that?
Is it enough to declare com.android.contacts as the authority for the sync adapter?
i need some help with configuring those...
Thanks in advance!
Rotem.
O.k. so i finally figured things out.
to achieve that, (getting notified about device contacts changes only), you don't need a stub content provider.
simply declare a sync adapter with a sync adapter service.
in the syncadapter's xml declaration, use the ContactsContract.AUTHORITY
which means: android:contentAuthority="com.android.contacts"
and set an automatic sync:
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
remember that your onPerformSync method in your SyncAdapter implementation won't be called right away, the system "waits" for the best time to make those syncs, any way from my experience it took ~40 seconds.
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
I have a strange problem and hope that someone of you has an idea what happens here.
My app structure is as follows:
I have a main service which registers a broadcast receiver and listens to intents like screen on/off etc. So this service runs indefinitely.
When such an intent is received, I start another service which does the action
Inside this action service I launch an AsyncTask to fetch battery related stats via reflection. After the service is done, it calls stopSelf().
So everything works as expected, except that when the battery related infos have been fetched one time, each subsequent call of the AsyncTask/Reflection methods deliver exactly the same result which has been delivered before.
The battery stats have of course been updated in the meantime, but I do not get the new updated numbers, but always the stats from the first method call.
That is until I go to settings and force stop and restart my app, then I get updated battery statistics again, at least one time, because after that I'm stuck with these numbers again.
So my question:
Could it be that the results of the reflection call are automatically cached somewhere and that each subsequent call doesn't really fetch the new data but just delivers some cached results? What else could be the problem?
I'm thankful for any ideas, I you need some code lemme know :)
Ok, I've found a fix to this :))
The library of Better Battery Stats uses the singleton pattern for a needed class.
It also includes an invalidate() function, which sets the singleton instance to null, so that at the next getInstance() it gets reinitialized.
I'm using now invalidate after each statisitics fetch, and now I get the updated statistics on every call. Although I am still not sure why the Singleton pattern seems to be the root of this issue, it should also work with having one initialized singleton instance...
Well, one does not simply have to understand everything ;-)
I have a task which I need to run in the background in my Android app. It reads data over the network and populates a database. It can take several minutes to run.
Once it's started, it needs to complete successfully without interruption. (Otherwise I'll end up with a broken half-populated database.) I realise I can never guarantee it will always complete, but I want to make it as hard as possible for the system to kill off this task. For safety I guess I will have it populate a temporary database, and then only swap out the old database for the new one on successful completion of the import.
It's a modal operation; it does not make sense for the user to be interacting with the app while the import is in progress.
My first attempt is using an ASyncTask with a Progress dialog to achieve the modality, but this obviously breaks the "don't interrupt" requirement. I could work around the screen-rotation issue with ASyncTasks, but I don't think that goes far enough.
At the moment I'm not sure if this should be an ASyncTask, a Service, an IntentService, some combination of these, or something else entirely. Can you help me decide?
I'd run it as a service and additionally I'd also have a clean SQLite DB on my server populated with the data the clients are going to retrieve so I can generate a kind of signature. Have the clients check for the correct signature of the DB. If the signature is not matching the servers signature then reinitialize the database filling process.
This is just an idea tho. I have no idea whether it'd be possible with what you are trying to do or not.
You are better off with services in that case. The Android runtime will leave it alone working as long as enough memory is available. In the case it kills the service, you can save the state in a bundle, and the system will restart the process as soon as possible, so you can resume the process, if possible for your solution:
Android Fundamentals, Service Section
Then it is easy to communicate with the service, like showing the progress/ notifications etc, using a handle registry like proposes by Mark Bredy in his Android Service Prototype