On my last post on the subject, Write your own Android Authenticator, we embarked on a journey to the unfamiliar part of authentication on Android. I received many positive responses about it from you all, and it seems I really helped a lot of people to get to know this feature well.
Since then, I had a lot of stuff going on in my life (getting married is one of them) which delayed the release of this obvious sequel. On this post, our journey continues to another not-well-documented area, which goes hand-in-hand with our own authenticator. It’s no other than the notorious SyncAdapter. I’ll show you how to make your app more efficient and robust when it comes to data synchronization, by using sync adapters. The web doesn’t have all the information I’d hope to find about it. I felt like I need to give this feature another one of my in-depth researches, see what this feature is all about, how it works, how to write one, and finally – report back.
In the screenshot: That’s how a sync adapter looks on the account screen, under the device’s Settings screen.
First, I’ll explain the benefits of the sync adapter. Then, how it works and after that how to build and monitor it. Finally, I’ll introduce the sample app I wrote, demonstrating how the sync adapter works, and also allowing you to play with its settings to learn from experience.
Terra Incognita
So what is this SyncAdapter?
A SyncAdapter is a plug-in that handles background syncs. It’s usually needed to sync data from your app to a server. This plug-in is registered on the platform’s Sync Manager, which is in charge of running it. It’s triggered when needed, requested or scheduled.
Some of you may think about writing a simple Service/IntentService to do that task. But, writing a SyncAdapter has many advantages over that and other similar approaches:
- Battery efficiency – The system schedules the sync to run when other syncs run, or when some other network request was already made on the device. This prevents awaking the device from its sleep for performing a single sync.
- Interface – All sync adapter on the device can be accessed from the Settings screen, under the account they are tied to. This gives the end user the option to change sync preferences, see if there are any sync problem or even disable the sync.
- Content awareness – If we use ContentProvider for accessing/manipulating our data, the sync adapter can observe any changes done to it. That way it can run only when the data actually changes.
- Retry mechanism – The sync manager has its implementation to retry failed syncs, using timeouts and exponential back offs. All those to conserve battery and sync your data as soon as possible.
And you get all that for free!
The fine print is that you need to learn how to write one, which was not easy until recently. The documentation got improved since this feature was introduced on Android 2.0 (API level 5), and there’s even a training chapter on the Android developers site. But, even combined with the SampleSyncAdapter by Google (comes with the SDK), it’s still not simple to understand how to create your own adapter to fit your needs. There are many unanswered question about its properties, and how it operates under certain circumstances.
How the sync adapter works
The sync adapter has 2 main properties: syncable and auto-sync. The syncable property is the enabled state for the sync adapter. A syncable=false adapter cannot sync automatically or manually, and won’t be seen in the device’s Settings screen. The auto-sync property determine if the sync adapter should sync automatically, which will be discussed next:
Automatic sync
We can set the sync adapter to run automatically, which means it’ll trigger whenever it’s needed. By default, all SyncAdapters with auto-sync=ON will be triggered every 24 hours. But, they can also run when there is a change to our data, notified by the content provider. We’ll get back to how it’s actually done later.
The auto-sync setting cannot be ON by default, but can be set programmatically by calling setSyncAutomatically(), or by the user on the device’s Settings screen. Here’s how it looks when the sync is off and then on:
Note: A sync request will be submitted when this setting changes from OFF to ON.
Periodic Sync
If you need to check up on your server once in a while, see if it has new data for us, you can call addPeriodicSync() and get our sync adapter to run periodically. The sync requests won’t be fulfilled exactly when the time period has passed, but on the closes battery efficient moment to that time. Note: A periodic sync can only be run if its auto-sync setting is ON and it’s syncable=true.
A better approach to this problem will be to use GCM (Google Cloud Messaging). The server can send push notification to the device(s) whenever there’s a change the client needs to know about. On the client, we request a sync manually upon receiving such message. It’s more battery efficient and we get updated as soon as our server is, without waiting for the next sync cycle.
Manual Sync
If we have our own way knowing the data has changes, or we just want to implement a simple “Refresh”/”Sync Now” button on our app, we can request a manual sync. For that, we use requestSync(). Besides the usual account and authority, we also pass as an argument extras to specify parameters to describe a more specific request. If we have no special requests, the system will run our sync after it gathers other sync or network tasks to run at the same time, for battery efficiency. The auto-sync setting must also be ON for that to work. To override these constraints, you can use the extras:
SYNC_EXTRAS_MANUAL – will force a sync, even if the auto-sync setting is OFF.
SYNC_EXTRAS_EXPEDITED – will start the sync immediately, no waiting for the system to find optimal moment for that.
This function cannot override the syncable= false setting. If the sync adapter is not syncable, there’s no way to run the sync.
Canceling
We have the option to cancel a sync request, by calling cancelSync(). If the sync adapter is in “Pending” state, which means it hasn’t started yet, it’ll be canceled immediately and become “Idle”. If it’s already running, the onSyncCanceled() method on the sync adapter will be called. You can use it, for example, to set a “isSyncStopped” flag that the onPerformSync() will check regularly and respond to.
Building the Sync Adapter
After learning more about the way it works, we can now build our sync adapter. We’ll do that in 4 steps:
- Creating the account authenticator and content provider – the sync adapter will later authenticate with one and listens to changes of the other.
- Writing the sync adapter class – where all the syncing algorithm goes.
- Creating the Sync Service – under its context the sync adapter will run.
- Connecting all the pieces – introduce the account authenticator and content provider to the sync adapter, and make them all work together.
Creating the account authenticator and content provider
The sync adapter will access the local data, represented by a ContentProvider, and contact the server using an auth token retrieved from the app’s Authenticator. Using these mechanisms will make our lives easier and our code simpler and robust.
But they aren’t mandatory!
The sync adapter requires you to declare an account type and a ContentProvider to be tied with it, but it doesn’t mean that you actually need to use them. For each of these, I’ll show you how to easily create a stub, among a list of reasons why you should just get the real deal.
Account Authenticator is the component that handles a specific account type. It will authenticate the users with their credentials against the server, hold the auth-token retrieved from the server and will raise a login screen upon token/password invalidation. I strongly recommend writing an authenticator to your app, making it robust to all those loose ends that nobody take care of when implementing your own simple user log-in/register. This is the reason I wrote my last post about the benefits of the Account Authenticator and a step-by-step guide to create one of your own. If all that didn’t convince you, just go ahead and write a stub authenticator using this great guide.
ContentProvider is a component that manages the access to your data. Content providers have the ability to control access from outside or inside the app, allowing you to easily and securely share data. They also has standard API to register for data changes, using ContentObserver. You can learn more about content providers and see how to create one in this official guide or from this blog post. Examples for content providers: Accessing your device’s Contacts and Calendar is done by a content provider. Even for Any.do, we recently published a read only content provider for tasks. If all that also didn’t convince you that you need one, you can create a stub content provider using this guide.
Writing the sync adapter
The sync adapter itself is a subclass of AbstractThreadedSyncAdapter. The most important method there is onPerformSync(), called by the sync manager when it’s sync time. This method operates from a background thread, so no worries while doing network calls.
A sync adapter can be tied to one account type on the device, but there could be multiple users signed in to this account type. For example, you can connect with more than one Google account on your device, and the “Calendar” for both accounts will be synced to your device, using sync adapters. This method receives as an argument the current account the sync manager has requested the sync for.
Let’s see an example for such class:
public class TvShowsSyncAdapter extends AbstractThreadedSyncAdapter { private final AccountManager mAccountManager; public TvShowsSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); mAccountManager = AccountManager.get(context); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { Log.d("udinic", "onPerformSync for account[" + account.name + "]"); try { // Get the auth token for the current account String authToken = mAccountManager.blockingGetAuthToken(account, AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS, true); ParseComServerAccessor parseComService = new ParseComServerAccessor(); // Get shows from the remote server List remoteTvShows = parseComService.getShows(authToken); // Get shows from the local storage ArrayList localTvShows = new ArrayList(); Cursor curTvShows = provider.query(TvShowsContract.CONTENT_URI, null, null, null, null); if (curTvShows != null) { while (curTvShows.moveToNext()) { localTvShows.add(TvShow.fromCursor(curTvShows)); } curTvShows.close(); } // TODO See what Local shows are missing on Remote // TODO See what Remote shows are missing on Local // TODO Updating remote tv shows // TODO Updating local tv shows } catch (Exception e) { e.printStackTrace(); } } }
The sync adapter I chose to write is syncing TV shows information from the device to a server, and vice versa. I created a ContentProvider to hold the records locally, and a Parse.comaccount to store the records remotely.
The syncing algorithm is completely under your responsibility. The sync adapter is only in charge of running your code, it has no idea how your data should be synced with the server, so it’s your job to get it right. There are sample algorithms for common sync tasks on the web, for you to “get inspired” when designing your algorithm.
To communicate with our server, we need an auth token, so we first call blockingGetAuthToken(), which is doing the same as getAuthToken() but done synchronously, since we’re already running on a background thread. The last parameter for this method is notifyAuthFailue and if set to “true” it will raise a notification to the user in case there was an authentication problem, such as an invalidated auth token.
Once the user click on that notification, the authenticator’s default sign in activity will show and ask him for credentials. After the user identify himself, the auth token gets renewed and the sync is working again! To learn more about invalidating tokens and showing a default sign in activity from the authenticator, see my previous post about account authenticators.
Note: On my sample app, I built a smart re-login mechanism for my authenticator, which auto sign-in the user in case of a token invalidation. That means this problem will occur only after the token has been invalidated and the password was changes on the server. See the getAuthToken() method on UdinicAuthenticator for more information.
Creating the Sync Service
The sync adapter, as for the Account Authenticator, also needs a Service to operate in. We need to create it ourselves in order for the sync adapter to be run on the same UID (user-id) as our app. This privilege gives the sync adapter access to our app’s resources, such as the account information and ContentProvider.
The Service is pretty simple and only needs to instantiate our sync adapter upon creation.
public class TvShowsSyncService extends Service { private static final Object sSyncAdapterLock = new Object(); private static TvShowsSyncAdapter sSyncAdapter = null; @Override public void onCreate() { synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) sSyncAdapter = new TvShowsSyncAdapter(getApplicationContext(), true); } } @Override public IBinder onBind(Intent intent) { return sSyncAdapter.getSyncAdapterBinder(); } }
Connecting the pieces
Connecting between the sync adapter, the account authenticator and the content provider, is done using the XML definition of the sync adapter. This XML is a resource, as the strings.xml and the layout files, that’s why it’s going into the “res” folder under a folder called “xml”.
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:contentAuthority="com.udinic.tvshows.provider" android:accountType="com.udinic.sync_example" android:userVisible="true" android:allowParallelSyncs="false" android:isAlwaysSyncable="false" android:supportsUploading="true"/>
Let’s review the attributes:
contentAuthority is the authority for our content provider. Also defined in the <provider> tag in the Android Manifest.xml, with the rest of the content provider’s attributes.
accountType is the account type that is tied with our sync adapter. It’s also defined in the authenticator’s xml, with the rest of the authenticator’s attributes.
allowParallelSyncs will allow this sync adapter to handle syncs for multiple accounts at the same time. If you use a common resource for those syncs and parallel syncs will cause problems – you can keep this setting to “false”. Furthermore, if you want to disallow adding more than one account, preventing this situations entirely, you can return an error code on the authenticator’s addAccount() in such case.
userVisible will determine if the user can see our sync adapter on the device’s Settings screen.
isAlwaysSyncable set the adapter to be syncable=true by default for every new account the user adds. This property can be override from code with setIsSyncable().
supportUploading will start a sync whenever there was a change to the content provider, with a special argument that hint the sync adapter only to upload the changes. The ContentResolver.SYNC_EXTRAS_UPLOAD will be inside the extras passed to the onPerformSync(), and you can use it to optimize your sync adapter not to perform a full sync, but just to update your server. The content provider notifies there was a change by calling notifyChange(), which its 3rd argument determine if the sync will be requested for this change. That way, you can fine-tune your sync requests to the changes that really needs immediate sync to the server.
A reference to the xml is sent as meta data to the sync adapter service, when declaring the service on the AndroidManifest.xml:
<service android:name=".syncadapter.TvShowsSyncService" android:exported="true"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" /> </service>
Relevant permissions to ask for on the AndroidManifest.xml:
WRITE_SYNC_SETTINGS – Gives us the right to add a sync adapter to the system. Mandatory.
READ_SYNC_SETTINGS – Allows us to read the sync adapter’s settings, such as isSyncable or syncAutomatically.
READ_SYNC_STATS – Helps us get the sync state, if it’s currently syncing, pending or idle.
If you want to add sync settings of your own, the kind your users can change, you can use the accountPreferences property in the authenticator xml. You can also read about it on my last post about authenticators.
Note: The sync adapter’s name, as it shows on the Settings screen, is the content provider’s name as defined in the <provider> tag in the AndroidManifest.xml.
Results and progress
SyncStatusObserver
If you need to know what your sync adapter is up to, you can register a SyncStatusObserver by calling addStatusChangeListener(), and removing it with removeStatusChangeListener(). It will trigger for any sync adapter status change, not just yours. Meaning, you need to check every time you get a callback the status of your sync adapter, by using isSyncActive() and isSyncPending(). You cannot catch those events on a background thread, unless you use a Service that runs all the time, which is a bad practice and a bad idea in general. For such a case, you can just send your own broadcast intents when the sync starts or ends (you implement the sync algorithm remember?). You can also do a little more and send progress intents, helping to give the user a more visual way of the sync progress, when he’s interacting with your app. Don’t forget that sending intents has a system overhead. Don’t send a progress update intent for every 1 percent of sync progress.
SyncResult and Errors
While the sync process is running, we can update the SyncResult object, which is received as an argument for the onPerformSync() method, the problems and statistics of the sync process. This data is used by the sync manager to determine if the sync was successful or not, and helps determine when to run the next sync, if at all. The SyncStats object, inside the SyncResult object, holds that information as counters for the number of exceptions for each type (e.g. numAuthExceptions, numIoExceptions etc.). Depending on the type of error, soft or hard, it decides what to do. You can see which error is considered to be hard or soft be reviewing the SyncStats class.
For hard errors, such as authentication exceptions, it will show an error mark on the sync adapter’s screen on the device’s Settings:
For soft errors, the sync manager will run the sync again later. The time of the next run will be calculated by the sync manager using a backoff algorithm.
The other statistics members, such as numInserts, numUpdates etc., are not saved anywhere, and merely needed to see if there was some progress on the sync operation. This information will help the sync manager decide what to do in case of an error (schedule another sync or wait for user action).
Using the sample app
The sample app is intended to understand the concept of sync adapters. I wrote a simple app that holds a local list of TV shows and syncs them with a Parse.com server, using its REST API. The content provider is a simple provider, managing one table with 2 columns: TV show title and release year.
Once connected, you’ll have access to all the sync adapter’s options. You can “Sync now”, change the “syncable” and “auto-sync” settings and also see the current status of the sync adapter (Pending, Syncing, Idle).
If there’s no “Udinic” account logged-in, you will receive a log-in screen when pressing the “Connect” button. Removing accounts can be done from the Settings screen.
The sync algorithm is very simple – it checks which TV shows are on the local list, but not on the remote list, and vice versa, then it sync the missed TV shows between the client and the server. A new TV show can be added to the local list using the “Add TV show to local” button, which randomly picks a TV show from a resource file, created by scraping Wikipedia’s “List of American television series” (if you’re interested, the script I created for this task can be found here).
I encourage you to experiment with the settings, add shows, force syncs, remove and add accounts etc. This is the best way to really know the sync adapter and they way it works.
The full source code can be found on my GitHub.
The sample app can also be downloaded from Google Play.
More information
You can never learn enough. Follow these links to learn more:
Android Protips 3: Making apps work like magic, by Reto Meier – Great tips to consider when developing an app, sync adapters are among them (min. ~49). Reto explains how to implement one with stub account authenticator and content provider, and also admits its not a very documented feature 🙂
Transferring Data Using Sync Adapters, on the Android Developers website – explains the steps to create a sync adapter with stub authenticator and content provider.
Writing your own Content Provider, by Wolfram Rittmeyer – Great tutorial to write your own content provider, which you need for your sync adapter.
Write your own Android Authenticator, by Me – My last post about writing your own authenticator, which you also need for your sync adapter.
I hope you have found this post useful. As always – you can comment here with questions/idea/corrections.
It may look like the ContentProvider doesn’t need to actually be used but you are wrong.
In reality not actually using it causes the SyncMananger to retry you constantly and thus drain the battery even as you report no errors and a successful sync.
(I had that with a DropboxSyncAdapter that syncs a directory in the file system instead of content in it’s DummyContentProvider.)
Strange.
In Any.do I’ve connected the sync adapter to our content provider, which is read-only, but I’m not using it at all. I’ve implemented only the query methods in it. Maybe the stub implementation that’s on the link I provided is not good enough.
Try to play with the methods’ implementation. Maybe write a simple content provider, make it work with the sync adapter as written here, and then slowly remove the implementation for all the methods to be as on the stub. I’m sure you’ll find the problem this way. Also – report back when you do, I’ll update my post.
Hi Udinic, what if i want to sync data to my own database remotely how and is this method using sync adapter suitable? How about making sure all users’ apps data are synced on the server without collisions. Regards
I have been waiting for this. Thank you. Great post.
The TV Shows list are not that big and it is not a problem here. But if you think of a large database, comparing everyitem in the local db with the remote is too much work. What do think we can do about it?
I am thinking about Gmail app for example. It pushes an update message using GCM and start syncing right? Do you think that it is checking all the posts? What is doing exactly?
1 more thing: The less permissions are in the app, the better. Do we have to add READ_SYNC_SETTINGS permission for example? If we implement custom preference in the authenticator, can we do it manually?
Hey,
About the size thing – databases are good for these purposes. You can add a “dirty” column to your table and select only those rows to sync to your server.
About the permissions – there’s a section I wrote about the purpose of each permission here. You can also do a little try and error. You can remove all relevant permissions and add them one by one whenever you get an exception. That’s what I usually do when I’m not sure.
A “dirty” flag is an invitation to conflicts and fails on syncing deletions.
You’d rather want a last-modified timestamp (this also solves network problems with resetting the dirty flag and lost acknowledgements) and a deleted flag. as well as a single timestamp of the point up to wich things have already been synced.
Conflicts can be resolved by the latest timestamp winning.
There are many ways to optimize the sync process. The dirty flag is one simple solution to identify changes. Of course timestamps are more robust.
I would even suggest considering a “data_hash” column in addition to the timestamp:
You create a hash of all the other columns in the row, save it in a “data_hash” column on the same row, and whenever you need to check that a row has been updated you compare the hash of the data with the hash in that column. This method will also ignore 2 opposite-updates that after doing them the row data has returned to its initial state. Therefor, it optimize the sync only for rows that really changed.
hey, can give simple example of syncing mysql database in local sqlite db as well as cloud syncing ?
I was searching such a post. Thanks for sharing.
It’s a shame you wrote this post 3 days after I finally managed to implement this 😉 Nevertheless, it’s the most thorough tutorial I’ve seen on this subject and the docs really suck. Even the naming of methods in SyncAdapter sucks. So thank you very much for this article 🙂
Do I need to create a ContentProvider if I don’t want to share my data with any other app ??
Can I do the process of SyncAdapter only with a SQLite DB ?? thanks for the post 🙂
No you don’t.
That’s why I added information about creating stub content provider. Content Providers can also be private to your app, just define them as exported=”false” in the manifest. That way you can enjoy all the benefits of content providers, without sharing the information with other apps.
What do you mean by “benefits of content providers”?
Hey, thanks for this great article, it really help me a lot to understand sync adapters.
I want to ask if you could share any resources (links to other articles) about the sync algorithms, i have been looking but couldn’t find any.. Thanks again.
Another question,
If it is the best practice to synchronize with a web server why any.do doesn’t use an AsyncAdapter ?
What i try to understand is when I need to use android autenticator ? If my data is per user such as any.do do I need an android authenticator ? (I need a unique ID for each user, and it can’t be the email user because it can change..)
and if yes why any.do doesn’t use that ?
My target is not to understand how any.do works but only to know what is the best practices 😉
THank you very much !
The user name and identifier is his email. That’s also how we do that in Any.do. This identifier cannot be changed.
If you keep data per user, as Any.do does, then creating a SyncAdapter is a classic solution for you.
SyncAdapter could be redundant if you sync information without logging in the user.
This article is brilliant, thank you.
I have a question which i cant seem to find anywhere. In your first screenshot of your account screen there is the checkbox to turn auto-sync on and off.
In my app i want to listen for when a user checks/unchecks this box. Do you know how to do this?
You said “A sync request will be submitted when this setting changes from OFF to ON.” but i want to determine if the user has checked or unchecked this box and I cant tell this when sync is actually called.
I presume the value of the checkbox is stored in some preference that i could perhaps listen for a change, but i have no idea what this preference is, or listening for a change on it.
Any help anyone can give me would be greatly appreciated
There’s no listener to these attributes unfortunately.
If you want to learn about the sync attributes, you can query this information every once in a while and when the sync state has changed, which could indicate the user has set auto-sync=ON for instance..
Excellent tutorial. However, I don’t see why clearing local list does not trigger any sync operation. I see the delete method in content resolver is calling notifyChange(uri, null, true). Any reason, why it does not trigger sync? What changes should me made to trigger sync immediately in this case? Thanks,
I was pretty sure it did call the sync.
It could be that other syncs were running as the time, which put our sync request in a queue to run after that.
How can I add a periodic sync?
You can do that be reading the “Periodic Sync” section of this tutorial 🙂
Pingback: Android Tutorial: Writing your own Content Provider | Grokking Android
Pingback: Android Sync Adapter | tediscript.wordpress.com
Hi
Very good tutorial it has broadened my understanding about Sync Adapters. Just a question though, I have a scenario where some android devices are being used on my local network how would I use the authenticator to authenticate from my local network, I have an SQL server DB running on a windows? If this is not possible can I use the Sync Adapter without an account? I would not mind an example. I am a bit new to this.
Hey Francis,
You cannot create a sync adapter without an account. However, you can create a dummy account as I explained in the post.
Authentication on a local network is the same as on the Internet. They are both “Networks”.
And just as an addition question since the devices will not be allowed to connect to the internet as yet, hence cannot make use of Google push notifications what would be the best way to notify devices of changes in the data on the SQL server DB? Kindly help and I will really appreciate. Thanks in advance.
の【楽天市場】
Hi
Nice Tutorial on Sync Adapter… 🙂
Can you please suggest or create concept on how to sync local database i.e., SQLite to server database by fetching or pushing data through web services. It will helpful for so many people.
Hi udinic, thanks you so much by your useful and well explained post.
I need to ask you a question about sync adapter. I implement my own adapter, account authenticator and provider but I have a trouble with sync adapter because he is executed multiple times. I have debugged my sync adapter and I can see that my onperformsyn method is executed twice. I don’t know why it is.
It could be if the adapter fails to sync. Make sure the onPerformSync method is finishing successfully.
Hi udinic! I followed your tutorial along with the official Android API sample to create my first SyncAdapter. It works, but I have a question. I wanted to make periodic syncing, so I set the frequency to 5 min and I was expecting to see roughly 5 min intervals of syncing, but instead in logcat I saw way bigger intervals, sometimes 10 mins or so, other times its exactly 5 mins. The thing is, it happens more often to see 10 min intervals between two synchronization than 5. Is this normal behavior?
Hey,
Yes, the platform tries to trigger the sync when it has other network tasks to do at the same time.
Also, read about this relevant change in KitKat:
http://developer.android.com/about/versions/android-4.4.html
* search for “addPeriodicSync()”
Thank you for an excellent tutorial and especially sharing your source code for a full working example. I was struggling to get my sync adapter working with account authenticator since the Android tutorial just uses a stub, and there is not much documentation on this subject. Your code saved me a lot of time and helped my understanding – thank you!
Howdy just wanted to give you a brief heads up and let you know a few of
the pictures aren’t loading properly. I’m not sure why but I think its a linking issue.
I’ve tried it in two different web browsers and both show the same outcome.
Hi, Thanks for the excellent tutorial. I have come up with my own sync adapter following your tutorial and the one on developer.android.com. In the onPerformSync() method I have added code to show a demo toast notification to check if my adapter is working. If I check the box navigating to the accounts section in settings like the first image of your tutorial, it works fine. I have added a periodicSync. I can’t get that to work.
This is how I have done it :
Bundle bundle=new Bundle();
mResolver = getContentResolver();
mAccount=CreateSyncAccount(this);
ContentResolver.setIsSyncable(mAccount, AUTHORITY, 1);
ContentResolver.setSyncAutomatically(mAccount, AUTHORITY, true);
ContentResolver.addPeriodicSync(
mAccount,
AUTHORITY,
bundle,
SYNC_INTERVAL);
I am stuck for pretty long, any insights would be helpful. Thanks in advance.
Hi.
I think you Post about this are great, right now, i only have 1 problem that i’m trying to fix.
You are using a parse.com account, in my case, i wanna be able to comunicate to my server, so i can compare the user and password so it can receive the token then from the server, and then, it will login, save the user, pass and token on the account manager, but i was trying to find some info on the web and nothing!! some of the examples i find doesn’t even work, others are so complex that i think some coders just don’t get the “KISS” philosophy…. can you help? i’ll be doing this, so i can login, and then, make a sync class so my SQLite db and MySQL could be mirrored.
Thanks (sorry for my english :D)
Hey Udinic can you please tell me how you are getting the API key and Application Id in the Syncadapter example and why is it fix for all the users……
Thank you very much for presenting us with such a detailed guide, because people like me are always searching for the most desirable way of doing things instead of just getting it to work.
However there is still something that remains unclear to me. What is the best practice of dealing with multiple accounts?
In my app, only one user can be the active user when the app is running, while user switching can be done by changing the setting and restarting the app, so it is a natural idea that each user owns a separate db with multiple tables. How should this be done when implementing the ContentProvider?
I did a google search and found a question unanswered
http://stackoverflow.com/questions/16007861/content-provider-syncing-with-multiple-accounts , so I chose to turn to you for some advice.
Thank you for your readinig and I’m looking forward to your reply.
Hey Robert,
From what I understand, you want these features:
1. The user to be logged in to both accounts all the time
2. Syncing the data for both accounts all the time
3. Allowing the user to switch without entering his credentials every time
If I got this right, than the way I would probably do this is:
1. Have a field on the content provider’s tables that’s linked to an account id
2. All the queries in the app will be filtered using the current account id, which is the one the user has currently selected to use.
3. On the authenticator, when the user has entered his credentials, and you add a new account for your account type, you can generate an account id and put it as a user data. Check out http://developer.android.com/reference/android/accounts/AccountManager.html#setUserData(android.accounts.Account, java.lang.String, java.lang.String).
4. When the user is selecting the current account that he’d like to use, you can save that information on a shared preference, and use it to filter all your queries.
5. Since this is just another field on the table, there’s a need to only one content provider
6. The sync adapter will know what data he can touch based on the account id, which it can easily get.
This is just something that I put together in my head, there might be some stuff that I’m missing. I’m sure there are other ways, with their own pros\cons.
I wished I had time to create a sample code for this, but if you will – you’re welcome to post here for future reference.
Good luck,
Udi
Thank you for your quick and detailed reply.
After reading your post, I came up with the solution that including the user account as a segment of ContentUri will solve this problem elegantly, with Uris in the form of “content://com.example.yourapp.provider/account_name/table_name”.
Be sure there will not be a “/” in account_name, in my case this will be an illegal username so it’s ok.
Today I modified my working single-user code to accommodate multiple users with their data in separate dbs, but I havn’t tested it yet. I’m working (hard) on this project as my extracurricular activity now 😛
Because this is a campus app that usernames are student ids which consist of only Ascii characters and have a limited length, so I decided to simply use usernames for referencing a account. For other people reading this, you may feel like to use the userid approach as suggested by udinic.
In Android it is recommended that you use a singleton DatabaseOpenHelper to manage your db connection for concurrency (It took me to dive into the source of ContactsProvider to find out this, it is weird that they didn’t mention this pattern in the official guide), so in this multiuser case we are going to use a Map of instances.
Below is some code segment from my modifacations today, hope this will give others some idea.
—
publci class YourDatabaseHelper extends SQLiteOpenHelper {
…
private static final Map instanceMap = new HashMap();
…
private YourDatabaseHelper(Context context, String databaseName) {
super(context, databaseName, null, DATABASE_VERSION);
}
public static YourDatabaseHelper getInstance(Context context, String databaseName) {
YourDatabaseHelper instance;
synchronized (instanceMap) {
instance = instanceMap.get(databaseName);
if (instance == null) {
instance = new YourDatabaseHelper(context, databaseName);
instanceMap.put(databaseName, instance);
}
}
return instance;
}
…
}
public class YourContract {
…
public static final String AUTHORITY = “com.example.yourapp.provider”;
public static final Uri AUTHORITY_URI = Uri.parse(“content://” + AUTHORITY);
static final String URI_SEGMENT_SPLITTER = “/”;
public static class ContentUris {
public static Uri makeBase(String accountName){
return Uri.withAppendedPath(AUTHORITY_URI, accountName);
}
public static Uri makeCurricula(String accountName) {
return Uri.withAppendedPath(makeBase(accountName), YOUR_TABLE_NAME);
}
…
}
…
}
public class YourProvider extends ContentProvider {
…
// You may feel like changing this to “#” if you are using a numeric userid.
private static final String URI_SEGMENT_ACCOUNT = “*”;
private static final String URI_SEGMENT_ID_POSTFIX = URI_SEGMENT_SPLITTER + “#”;
…
static {
…
// Add a Uri to your UriMatcher like this.
uriMatcher.addURI(YOUR_AUTHORITY, URI_SEGMENT_ACCOUNT + URI_SEGMENT_SPLITTER + YOUR_TABLE_NAME + URI_SEGMENT_ID_POSTFIX, CURRICULUM_ID);
…
}
…
// Get the account name from a Uri using this.
private String getAccountName(Uri uri) {
String path = uri.getPath();
return path.substring(0, path.indexOf(URI_SEGMENT_SPLITTER));
}
// Get your DatabaseOpenHelper using this.
private YourDatabaseHelper getDatabaseHelper(Uri uri) {
return YourDatabaseHelper.getInstance(getContext(), getAccountName(uri));
}
}
A final note, remember to check for the existence of the active account read from SharedPrefs, avoiding creating invalid databases. Personally I wrote a short utility fuction to do this.
Hi Udi,
Related question. I looked at the content provider you all published at Any.DO.
You have a parent_task_id in the NotesColumns. Does this parent_task_id uniquely identify the task across all devices and the server, or is it a task rowId local to the device’s sqlite db?
If the former, how and when do you generate the uuid? Is it a string?
If the latter, how do you reconcile the parent_task_id of a task that was created and assigned its id on a different device?
I assume much of this coordination occurs during the syncing process with the server.
Thanks in advance,
Matt
Hey Matt,
I’m not working at Any.do anymore[1].
You can email the developers at the email on the bottom of the page[2] about this content provider.
Cheers,
Udi
[1] https://plus.google.com/113156787654356910368/posts/EG9cFa4Zjhs
[2] http://tech.any.do/content-provider-for-any-do/
Thanks Udi.
I was merely referencing the Any.DO content provider as an example.
I’m more curious about the best approach for building a content provider backed by a local sqlite db and synced, using a sync adapter, with a remote db. Is it ok to use String uuids to reference all records through the Content Provider/db, and sync based on those same uuids with the server, or is there a reason to only work with local long ids on the device and somehow map those local ids with the right uuids during the sync? If so, how would that work?
Regards,
Matt
What I should have included in my previous comment is that it’s straightforward to map long ids to String uuids unless you have tables to join. Do you join on the id or uuid?
This might not be the best forum for my question, and I admit it’s not directly regarding the sync adapter, even if related.
Thanks again for all your great posts.
Hi again Udinic,
Thank you for your previous response (emaleavil:on December 4, 2013 at 17:38 said:) but I’d like to ask you another question. My question is in this thread on stackOverflow http://stackoverflow.com/questions/22743052/syncadapter-and-rest-server-with-paginated-response
and I’d like to know if you could help me with sync adapter and rest paginated response.
Thank you again.
hey i am looking for auto sync broadcast receiver please help
Pingback: Sync Adapter Sample App怎么样|Sync Adapter Sample App好用吗|Sync Adapter Sample App用户评论 - 就要问
Hi, thank you for great tutorial.
I just have one question. I would like to save data to Parse.com which will be linked with the user. How can I get the user credentials in another activity?
Thank you for the response.
Hi, thank you for great tutorial.
I just have one question. When A account is synchronising, user starts to sync of B account. And then after 5 min, onSyncCanceled() method on the sync adapter of A account is called automatically and A account is stoped to sync although the contents which should be synced remained. And the B account is started to sync. How can I maintain the sync of A account continually?
Hey
I think you need to allow parallel syncing for that to work. There’s an attribute for that somewhere, I don’t remember where exactly (could be in the XML?).
Post back if you manage to solve the problem.
Cheers,
Udi
Hi,
can you please tell me how to build account screen in Android account setting and how to work if i check the checkbox (Sync TV Shows) from account & sync screen?
Regards,
Ruhul
Pingback: علیرضا هستم! | روش پیشنهادی من برای یادگیری برنامهنویسی اندروید
Hi, Thank you for your great tutorial. I am getting one problem to handle sync error. When server or database is unavailable, how I can get error from my activity or sync adapter.
Regards,
Hossain
If you wish for to obtain much from this paragraph then you have to apply these
strategies to your won weblog.
I love what you guys are usually up too. This sort of clever work and exposure!
Keep up the superb works guys I’ve added you guys to blogroll.
Hey! Do you use Twitter? I’d like to follow you if that would be ok.
I’m absolutely enjoying your blog and look forward to new updates.
I love looking through an article that can make men and women think.
Also, thank you for allowing for me to comment!
Hey would you mind letting me know which webhost you’re working with?
I’ve loaded your blog in 3 completely different browsers and I must say this blog loads a lot quicker then most.
Can you suggest a good internet hosting provider at a reasonable price?
Thanks, I appreciate it!
Awesome blog! Do you have any suggestions for aspiring writers?
I’m planning to start my own blog soon but I’m a little lost on everything.
Would you recommend starting with a free platform like WordPress
or go for a paid option? There are so many options out there that I’m completely overwhelmed ..
Any suggestions? Thanks a lot!
Howdy are using WordPress for your blog platform?
I’m new to the blog world but I’m trying to get started and create
my own. Do you require any coding expertise to make your own blog?
Any help would be really appreciated!
Everything is very open with a precise explanation of the issues.
It was really informative. Your site is very useful. Many thanks for sharing!
Oh my goodness! Impressive article dude! Many thanks, However I am having issues with your
RSS. I don’t understand why I cannot join it. Is there anybody else having
similar RSS issues? Anybody who knows the solution will you kindly respond?
Thanks!!
Pingback: Droidcon Paris 2014: talks - Feel out the Form
thank you.
can you attach server Side source code of this app.
or send to my email.please!
Pingback: Android Sync Adapter | PipisCrew Official Homepage
Hello udnic,
I want to only send images ,media and contact to server. Is sync adapter is helpful?If yes then how can we implement this? For uploading data on server by using sync adapter is it necessary to firstly populate local database?
Pingback: Download aplikasi Sync Adapter Sample App gratis untuk android
Hello Udnic,
Thanks for your tutorial, I had one simple question. I intent to use ContentResolver.setSyncAutomatically(syncAccount, AUTHORITY, true);
Do I need to call this every time the phone shuts down and boots again ?
Thanks in advance
That configuration should be persistent, so once it enough.
Hello Udinic,
Nice Implementation of SyncAdapter.
Have a question, i am using syncadapter to store ExtendedProperties for Calendar app syncing with an Exchange Server, it works fine(Strorage of ExtendedPropeties) but i didn’t find extended prop into the Exchange Server, when i use my C# app to fetch data, i am wondering why the syncing did not get ExtendedProperties into the Exchange Server, any idea pls