In my last post, I showed how to implement an Android Account Manager, which utilizes the internal Android’s system for managing user accounts. Once you’ve implemented that, you are ready to take your app to the next level with Android Sync Adapter ( SyncAdapter ).

What is Android Sync Adapter?

A SyncAdapter is a plug-in provided by Android to help out with most of the scenarios regarding the syncing abilities of your app. This service is managed by the platform, which is in charge of running it when requested or scheduled.

You can roll out a similar service of your own by writing custom Service classes and calling them as and when required. But there are many advantages of using the android sync adapter like :-

Battery Efficiency – The system schedules your syncs as and when it is convenient for the phone, thus optimizing to efficiently use battery, and other scarce resources. Error Handling – The Android Sync Adapter knows what screens to show when the server responds with an authentication error, or how to retry with exponential back offs if something goes wrong. Disable Background Sync – The user gets to disable sync in background via settings, which is difficult to create in your own implementation. This also plays nice with other apps your users use to save battery/data in their phones. Easier Code Management – Everyone will organize their code in their own style, leading to problems of getting productive or understanding code structure. Android Sync Adapter will have a similar interface for most of the projects, thus helping your developers to know where to look for things they need. This also why frameworks work better for teams.

All these advantages come at a price though, you need to learn it! Google has tried hard to make it easier for you to learn by providing a training chapter and a sample implementation (by the name SampleSyncAdapter ), but it is still not simple to understand. There are many unanswered questions about how some properties work, and how does it react under certain circumstances.

Building our Sync Adapter

Making the SyncAdapter class

Our SyncAdapter is a subclass of AbstractThreadedSyncAdapter which implements its abstract method onPerformSync .

public class SyncAdapter extends AbstractThreadedSyncAdapter { private AccountManager mAccountManager ; public SyncAdapter ( Context context, boolean autoInitialize ) { super ( context, autoInitialize ) ; mAccountManager = AccountManager. get ( context ) ; } public SyncAdapter ( Context context, boolean autoInitialize, boolean allowParallelSyncs ) { super ( context, autoInitialize, allowParallelSyncs ) ; mAccountManager = AccountManager. get ( context ) ; } @Override public void onPerformSync ( Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult ) { try { String authToken = mAccountManager. blockingGetAuthToken ( account, AccountConstants. AUTH_TOKEN_TYPE , true ) ; // Use the authToken and write your sync logic. Skip the previous call if authToken is not required } catch ( OperationCanceledException e ) { e. printStackTrace ( ) ; } catch ( IOException e ) { e. printStackTrace ( ) ; } catch ( AuthenticatorException e ) { e. printStackTrace ( ) ; } } } public class SyncAdapter extends AbstractThreadedSyncAdapter { private AccountManager mAccountManager; public SyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); mAccountManager = AccountManager.get(context); } public SyncAdapter(Context context, boolean autoInitialize, boolean allowParallelSyncs) { super(context, autoInitialize, allowParallelSyncs); mAccountManager = AccountManager.get(context); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { try { String authToken = mAccountManager.blockingGetAuthToken(account, AccountConstants.AUTH_TOKEN_TYPE, true); // Use the authToken and write your sync logic. Skip the previous call if authToken is not required } catch (OperationCanceledException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (AuthenticatorException e) { e.printStackTrace(); } } }

This is a sample implementation, but it is not enough in itself because you’ll have to register your Sync Adapter with Android’s OS. Also note that the adapter doesn’t care what code it runs, its sole responsibility is to run that code. So you’ll have to write your syncing logic in the little comment I’ve left above.

Sync Adapter’s thread can make synchronous network calls as it runs in its very own background thread. This is why we make a blockingGetAuthToken call in the onPerformSync method.

Making the SyncService

The sync adapter also needs a Service to run in. We create this service from within the app so it has access to all the resources that our app has access to while running. The Service is a simple one, where we initialize our adapter when the service starts, and bind it to our adapter.

/** * Define a Service that returns an IBinder for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ public class SyncService extends Service { // Storage for an instance of the sync adapter private static SyncAdapter sSyncAdapter = null ; // Object to use as a thread-safe lock private static final Object sSyncAdapterLock = new Object ( ) ; /* * Instantiate the sync adapter object. */ @Override public void onCreate ( ) { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized ( sSyncAdapterLock ) { if ( sSyncAdapter == null ) { sSyncAdapter = new SyncAdapter ( getApplicationContext ( ) , true ) ; } } } /** * Return an object that allows the system to invoke * the sync adapter. */ @Override public IBinder onBind ( Intent intent ) { /* * Get the object that allows external processes * to call onPerformSync(). The object is created * in the base class code when the SyncAdapter * constructors call super() */ return sSyncAdapter. getSyncAdapterBinder ( ) ; } } /** * Define a Service that returns an IBinder for the * sync adapter class, allowing the sync adapter framework to call * onPerformSync(). */ public class SyncService extends Service { // Storage for an instance of the sync adapter private static SyncAdapter sSyncAdapter = null; // Object to use as a thread-safe lock private static final Object sSyncAdapterLock = new Object(); /* * Instantiate the sync adapter object. */ @Override public void onCreate() { /* * Create the sync adapter as a singleton. * Set the sync adapter as syncable * Disallow parallel syncs */ synchronized (sSyncAdapterLock) { if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext(), true); } } } /** * Return an object that allows the system to invoke * the sync adapter. */ @Override public IBinder onBind(Intent intent) { /* * Get the object that allows external processes * to call onPerformSync(). The object is created * in the base class code when the SyncAdapter * constructors call super() */ return sSyncAdapter.getSyncAdapterBinder(); } }

Of course, when you create a service, you need to declare it in the Manifest file.

The manifest file looks something like this :

<service android:name = "com.pilanites.streaks.SyncService" android:exported = "true" android:process = ":sync" > <intent-filter > <action android:name = "android.content.SyncAdapter" /> </intent-filter > <meta-data android:name = "android.content.SyncAdapter" android:resource = "@xml/syncadapter" /> </service > <service android:name="com.pilanites.streaks.SyncService" android:exported="true" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service>

It looks like your run of the mill service, but there are some important differences.

android:process=":sync" is a required attribute. This tells the system that this service is a part of the sync process.

The intent-filter provided let’s the system know when to run this service (like when a specific intent is provided).

Then, in the meta data, you have the name, same as intent-filter and something called android:resource . This is a vital piece of information which describes our Android Sync Adapter.

Just like how we created a authenticator.xml file when we used the Android Account Manager, we’ll create a syncadapter.xml file here.

Creating the syncadapter.xml file

Logically, next we have to create a syncadapter.xml file.

<?xml version = "1.0" encoding = "utf-8" ?> <sync-adapter xmlns:android = "http://schemas.android.com/apk/res/android" android:accountType = "@string/account_type" android:allowParallelSyncs = "true" android:contentAuthority = "@string/content_authority" android:isAlwaysSyncable = "true" android:supportsUploading = "true" android:userVisible = "false" /> <?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/account_type" android:allowParallelSyncs="true" android:contentAuthority="@string/content_authority" android:isAlwaysSyncable="true" android:supportsUploading="true" android:userVisible="false" />

The two things I would like to explain in this small snippet are accountType and contentAuthority .

accountType is the value that you’d have provided in your authenticator.xml file. This values is used to associate between your account and it’s sync.

contentAuthority is used to associate with a content provider. I use activeandroid as my database ORM, and it provides me with a content provider out of the box (if you need to create your own content provider, this is a good guide), so I simply declare it in my Manifest file as :-

<provider android:name = "com.activeandroid.content.ContentProvider" android:authorities = "@string/content_authority" android:exported = "false" /> <provider android:name="com.activeandroid.content.ContentProvider" android:authorities="@string/content_authority" android:exported="false" />

As you see, I have the same value for authorities here, as I had in syncadapter.xml .

(I have used com.pilanites.streaks.provider as my content authority, but you can use anything, just try not to use something common, in case it clashes.)

Adding relevant permissions

You’ll need to add three permissions to your manifest file to be able to read, write sync status and settings.

<uses-permission android:name = "android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name = "android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name = "android.permission.READ_SYNC_STATS" /> <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.READ_SYNC_STATS" />

Running a Sync

Phew, after going through 1000 words, we should have properly created and configured the Sync mechanism. All that is left is to run it at the right time.

There are multiple ways to run sync, which you can go through in this wonderful guide by udinic. I’ll be focusing on running a sync manually via code.

This means the app will run the sync only when requested by us. Common scenarios will be to run it when a sync button is clicked, or you detect that there were some changes in your local data set and now it needs to be synced.

public void sync ( ) { Account account = UserAccountUtil. getAccount ( this ) ; Bundle settingsBundle = new Bundle ( ) ; settingsBundle. putBoolean ( ContentResolver. SYNC_EXTRAS_MANUAL , true ) ; settingsBundle. putBoolean ( ContentResolver. SYNC_EXTRAS_EXPEDITED , true ) ; /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver. requestSync ( account, getResources ( ) . getString ( R. string . content_authority ) , settingsBundle ) ; } public void sync() { Account account = UserAccountUtil.getAccount(this); Bundle settingsBundle = new Bundle(); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); settingsBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); /* * Request the sync for the default account, authority, and * manual sync settings */ ContentResolver.requestSync(account, getResources().getString(R.string.content_authority), settingsBundle); }

Just call this sync() method whenever you need to sync your data, and it’ll run the service and perform the onPerformSync method. The UserAccountUtil ‘s getAccount method can be seen implemented in the gist.

And… that’s it!

You can find all the relevant code samples in this gist.

Hopefully this helps in implementing your own Android Sync Adapter. If you liked this guide and want more of these, or have a question, I urge you to leave a comment here if you have any questions or tweet them to me, @shobhitic.

It’ll be great if you can get on my mailing list as well.