Providing synchronization between an android equipment and a server will make your app more useful, as users can save data locally or remotely. However, users not always has a data connection.

You can implement your own algorithm and rules to do the data transfer by yourself, but you should check this great framework provided by android called Sync Adapter. It will optimize your app synchronization by a lot and will handle the some/most essentials tasks needed to synchronize like finding the best time to execute the synchronization, checking if there is network available on the device and much more.

It isn’t that complicated but there are many people trying to implement this framework and running into problems. Hopefully this guide will make things clear to you.

Some of the advantages using this framework, as stated on android developers website:

  • Plug-in architecture.
  • Automated execution.
  • Automated network checking.
  • Improved battery performance.
  • Account management and authentication.
  • Run the sync on a background thread, so you don’t have to setup your own background processing.

At the end of this guide you will have a fully working Sync Adapter. Don’t forget that you’ll still have to write your own data sync logic within the framework. We will help you get started on that too.

This framework expects 3 components in order to work:

  • Stub Authentication.
  • Content provider. We won’t be using a content provider so we will just make a stub content provider. (if you already have one, skip this part).
  • Sync Adapter.

Create a new class Authenticator that extends AbstractAccountAuthenticator

*
/**
* Created by mobiware on 29-03-2017.
*/

public class Authenticator extends AbstractAccountAuthenticator {
public Authenticator(Context context) {
super(context);
}

@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null;
}

@Override
public Bundle addAccount(AccountAuthenticatorResponse r, String s, String s2, String[] strings, Bundle bundle) throws NetworkErrorException {

return null;
}

@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
return null;
}

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}

@Override
public String getAuthTokenLabel(String authTokenType) {
return null;
}

@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
return null;
}

@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
return null;
}
}

Create a new class AuthenticatorService that extends service

/**
* Created by mobiware on 29-03-2017.
*/

public class AuthenticatorService extends Service {

// Instance field that stores the authenticator object
private Authenticator mAuthenticator;

@Override
public void onCreate() {
// Create a new authenticator object
mAuthenticator = new Authenticator(this);
}

@Override
public IBinder onBind(Intent intent) {
return mAuthenticator.getIBinder();
}
}

Authenticator Metadata file

“This metadata declares the account type you’ve created for your sync adapter and declares user interface elements that the system displays if you want to make your account type visible to the user.”

Create a new .xml resource file under res/xml (if you don’t have this directory, create it) and give it a name, usually authenticator.xml but you can give it any name.

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
   android:accountType="com.example.mobiware.myapp" 
   android:icon="@drawable/ic_launcher" 
   android:smallIcon="@drawable/ic_launcher"
   android:label="@string/app_name">

And lastly, declare your authenticator on manifest.xml:

<service android:name=".Sync.AuthenticatorService">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator"/>
    </intent-filter>
    <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" />
</service>

Stub content provider

Even if you don’t have a content provider in your app, the sync adapter still expects one in order to work. If you don’t have one, the sync adapter will crash. Content providers are one way to store data on android systems, however you can use any other way of storing data, but you still have to create a content provider. Just make a stub content provider where all the required methods will return “null” or “0”, then you can ignore this content provider and use whatever you wish like SQLite.

If you already have one content provider, skip this part and jump to the next part, Creating a Sync Adapter Class.

Create a new class StubProvider that extends contentProvider

/**
* Created by mobiware on 30-03-2017.
*/

public class StubProvider extends ContentProvider {
/*
* Always return true, indicating that the
* provider loaded correctly.
*/
@Override
public boolean onCreate() {
return true;
}

/*
* Return no type for MIME type
*/
@Override
public String getType(Uri uri) {
return null;
}

/*
* query() always returns no results
*
*/
@Override
public Cursor query(
Uri uri,
String[] projection,
String selection,
String[] selectionArgs,
String sortOrder) {
return null;
}

/*
* insert() always returns null (no URI)
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}

/*
* delete() always returns "no rows affected" (0)
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}

/*
* update() always returns "no rows affected" (0)
*/
public int update(
Uri uri,
ContentValues values,
String selection,
String[] selectionArgs) {
return 0;
}
}

The sync adapter framework verifies if your app has a content provider by checking its manifest, so declare it:

<provider android:name="com.example.mobiware.myapp.Sync.StubProvider"
    android:authorities="com.example.mobiware.myapp.Sync" 
    android:exported="false" 
    android:syncable="true"/>

Create a Sync Adapter Class

This is the class that encapsulates your data syncronization code. Create a new class SyncAdapter that extends AbstractThreadedSyncAdapter.

public class SyncAdapter extends AbstractThreadedSyncAdapter {
private AccountManager mAccountManager;

public SyncAdapter(Context context, boolean autoInitialize) {
   super(context, autoInitialize);
}

@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
   Log.e("IvaAPP -&gt;", "onPerformSync for account[" + account.name + "]");
}
}

On the OnPerformSync method is where your sync code will be implemented. This method is called to execute a sync, while the code implemented on this method is specific to your app. There are some tasks that you should always take in consideration. Android developers site resumes it perfectly:

  • Connecting to a server

Although you can assume that network is available when your data transfer starts, the sync adapter framework doesn’t automatically connect to a server.

  • Downloading and uploading data

A sync adapter doesn’t automate any data transfer tasks. If you want to download data from a server and store it in a content provider, you have to provide the code that requests the data, downloads it, and inserts it in the provider. Similarly, if you want to send data to a server, you have to read it from a file, database, or provider, and send the necessary upload request. You also have to handle network errors that occur while your data transfer is running.

  • Handling data conflicts or determining how current the data is

A sync adapter doesn’t automatically handle conflicts between data on the server and data on the device. Also, it doesn’t automatically detect if the data on the server is newer than the data on the device, or vice versa. Instead, you have to provide your own algorithms for handling this situation.

  • Clean up

Always close connections to a server and clean up temp files and caches at the end of your data transfer.

Create a new class SyncService that extends Service

public class SyncService extends Service {
// Storage for an instance of the sync adapter
private static SyncAdapter sSyncAdapter = null;

public SyncService() {
super();
}

@Override
public void onCreate() {
if (sSyncAdapter == null)
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}

@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}

Add the Account Required by the Framework

The Sync Adapter expects to have an account type, you declared one earlier on authenticator.xml file. Now you have to setup this account and the best local to do it’s on the opening activity of your application.

Sync Adapter metadata file

Create a new .xml resource file under res/xml (if you don´t have this directory, create it) and give it a name, usually syncadapter.xml.

<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" 
   android:contentAuthority="com.example.mobiware.myapp.Sync" 
   android:accountType="com.example.mobiware.myapp" 
   android:userVisible="false" 
   android:supportsUploading="false" 
   android:allowParallelSyncs="false" 
   android:isAlwaysSyncable="true" />

And that’s it. The 3 components are implemented (stub authenticator, stub provider and sync adapter). Now you just need to run it and tell android when to run it:

  • Run the Sync Adapter when content provider data changes.
  • Run the Sync Adapter periodically.
  • Run the Sync Adapter on demand.

Please go to Android developers Sync Adapter to read about each one and how to implement it. We will implement the On Demand one just as an example you can follow.

Just make a method with the following code that is called on button click:

 

public void onRefreshButtonClick(View v) {

   // Pass the settings flags by inserting them in a bundle
   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(mAccount, AUTHORITY, settingsBundle);
}

Troubleshooting

My onPerformSync is not executed

  1. If you are using the log util to check if it’s executed or not, on the android monitor, choose “No filters” and then try again.
  2. Check if your manifests and .xml names and paths are correct.