55 comments on “Write your own Android Sync Adapter

  1. 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

  2. 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?

  3. 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.

  4. 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 :)

  5. 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.

  6. 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.

  7. 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.

  8. 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..

  9. 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.

  10. Pingback: Android Tutorial: Writing your own Content Provider | Grokking Android

  11. Pingback: Android Sync Adapter | tediscript.wordpress.com

  12. 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”.

  13. 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.

  14. 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.

  15. 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.

  16. 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?

  17. 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!

  18. 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.

  19. 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.

  20. 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)

  21. 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 :P
        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.

  22. 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

      • 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.

  23. Pingback: Sync Adapter Sample App怎么样|Sync Adapter Sample App好用吗|Sync Adapter Sample App用户评论 - 就要问

  24. 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.

  25. 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

  26. 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

  27. Pingback: علیرضا هستم! | روش پیشنهادی من برای یادگیری برنامه‌نویسی اندروید

  28. 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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s