How to use Sync Adapter In Android

Posted By : Ankita Singh | 10-Apr-2017

Why do we use SyncAdapter and not Alarm Manger to Sync?

In sync adapter sync can be requested based on variety of criteria,including data changes,elapsed time,or time of the day. And system only runs data trasfer when the device has network connectivity. Allows you to centralize all of your app's data transfer tasks in one place,so that they run at the same time.Your data transfer is also scheduled in conjuction with data transfer from the other app which reduce the battery usages.

Synchronizing data between your application and web server is a very basic need for any mobile apps, for that we use sync adapter.for example, transferring data to web server make a very good backup system and receiving data from the web server make data available to end user.There are cases in which user want to edit his/her data in the web interface and then make it available to his/her device or want to collect data from the server time to time to stay updated.


Although, we can design our own system for doing the data synchronization, but you have to consider android sync adapter framework for doing this because it helps manage and automate data transfer between server and mobile. We can take many advantage of using android sync adapter framework which will be not there when we create our own system for doing data sync.for example -

  1. Plug in Architecture
  2. Automated exceution.
  3. Automated network checking
  4. Good battery performance
  5. Account management and authentication

Note: Sync adapters run asynchronously,we have to use them with the condition that they transfer data regularly and efficiently,but not instantaneously.If we need to do real-time data transfer, we should use an AsyncTask or an IntentService.

Sync adapter consist of the following components:

  1. A content-provider that extends content provider
  2. An adapter that extend AbstractThreadedSyncAdapter
  3. And a service that extend service.
  4. Authenticator and authenticator service.

and we have to declare content provider and sync adapter in manifest files.

 

Content Provider:

These are the best ways to provide access to your data from one process to another. We will not use provider because we don't have to share data with other application. But of we use sync adapter will be be a good practice.

 

SyncProvider -

public class StubProvider extends ContentProvider {
    DataBaseCurdOperation dataBaseCurdOperation;
    Context mContext;
    public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + SyncUtils.AUTHORITY);
    public static final int ROUTE_ENTRIES = 1;
    public static final int ROUTE_ENTRIES_ID = 2;
    public static final String CONTENT_TYPE =
            ContentResolver.CURSOR_DIR_BASE_TYPE + "/vnd.android.entries";
    /**
     * MIME type for individual entries.
     */
    public static final String CONTENT_ITEM_TYPE =
            ContentResolver.CURSOR_ITEM_BASE_TYPE + "/vnd.android.entry";

    private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        sUriMatcher.addURI(SyncUtils.AUTHORITY, "entries", ROUTE_ENTRIES);
        sUriMatcher.addURI(SyncUtils.AUTHORITY, "entries/*", ROUTE_ENTRIES_ID);
    }

   /*  * Always return true, indicating that the
     * provider loaded correctly.
     */

    @Override
    public boolean onCreate() {
        dataBaseCurdOperation = new DataBaseCurdOperation(getContext());
        Logger.LogError("BASE_CONTENT_URI", BASE_CONTENT_URI.toString() + "   uriMatcher" + sUriMatcher);
        return true;
    }

    /* * Return no type for MIME type
     */
    @Override
    public String getType(Uri uri) {
        Logger.LogError("uri",uri.toString());
      final int match = sUriMatcher.match(uri);
        switch (match) {
            case ROUTE_ENTRIES:
                return CONTENT_TYPE;
            case ROUTE_ENTRIES_ID:
                return CONTENT_ITEM_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }
    /**
     * query() always returns no results
     **/
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Logger.LogError(" uri ", "" + uri.toString() + "  selection  " + selection + "  selectionArgs  " + selectionArgs + "sortOrder" + sortOrder);
        return null;
    }
   /*  * insert() always returns null (no URI)
     */
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        Logger.LogError("uri", "" + uri.toString() + "content" + values);
        return null;
    }
   /*  * delete() always returns "no rows affected" (0)*/
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        Logger.LogError("uri", "" + uri.toString() + "selection" + selection + "selectionArgs" + selectionArgs);
        return 0;
    }
    /* * update() always returns "no rows affected" (0)
     */
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        Logger.LogError(" uri ", "" + uri.toString() + "  contentvalues  " + values + "  selection  " + selection + "  selectionArgs  " + selectionArgs);
        return 0;
    }
}

Authenticator and Authenticator Service -


Sync requires an authenticator.An authenticator plugs into the android accounts and authentication framework.Even if an app doesn't use accounts authenticator is still required and also its not difficult to add no-op authenticator component,also called a stub authenticator in android
for ex-
Authenticator-

public class Authenticator extends AbstractAccountAuthenticator {

    // Simple constructor
    public Authenticator(Context context) {
        super(context);
    }
    // Editing properties is not supported
    @Override
    public Bundle editProperties(
            AccountAuthenticatorResponse r, String s) {
        throw new UnsupportedOperationException();
    }
    // Don't add additional accounts
    @Override
    public Bundle addAccount(AccountAuthenticatorResponse r, String s, String s2,
            String[] strings, Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Ignore attempts to confirm credentials
    @Override
    public Bundle confirmCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            Bundle bundle) throws NetworkErrorException {
        return null;
    }
    // Getting an authentication token is not supported
    @Override
    public Bundle getAuthToken(
            AccountAuthenticatorResponse r,
            Account account,
            String s,
            Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }
    // Getting a label for the auth token is not supported
    @Override
    public String getAuthTokenLabel(String s) {
        throw new UnsupportedOperationException();
    }
    // Updating user credentials is not supported
    @Override
    public Bundle updateCredentials(
            AccountAuthenticatorResponse r,
            Account account,
            String s, Bundle bundle) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }


    // Checking features for the account is not supported
    @Override
    public Bundle hasFeatures(
            AccountAuthenticatorResponse r,
            Account account, String[] strings) throws NetworkErrorException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account) {
        Bundle result = new Bundle();
        boolean allowed = true; // or whatever logic you want here
        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, allowed);
        return result;
    }
}

In order to use or access your authenticator,you must create a bound service for it.This service provides an android object binder and allow framework to call authenticator and pass data between authenticator.
Android generally require a faur amount of the xml to wire the bita n pieces,no exception  that we need xml for the authenticator meta data and we need to pint to that metadata.
Authenticator meta data -

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accountType="app.com"
    android:icon="@mipmap/ic_launcher"
    android:smallIcon="@mipmap/ic_launcher"
    android:label="@string/app_name"/>

account type is required and should be the domain that is under your control.Add account authenticator to the app manifest

<service android:name=".syncadaptercontact.AuthenticatorService">
    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator" />
    </intent-filter>

    <meta-data
        android:name="android.accounts.AccountAuthenticator"
        android:resource="@xml/authenticator" />
</service>

AuthenticatorService -

ublic class AuthenticatorService extends Service {

    public static final String ACCOUNT = "WeOne";

    public static Account GetAccount() {
        // Note: Normally the account name is set to the user's identity (username or email
        // address). However, since we aren't actually using any user accounts, it makes more sense
        // to use a generic string in this case.
        //
        // This string should *not* be localized. If the user switches locale, we would not be
        // able to locate the old account, and may erroneously register multiple accounts.
        final String accountName = ACCOUNT;
        return new Account(accountName, SyncUtils.ACCOUNT_TYPE);
    }
    // Instance field that stores the authenticator object
    private Authenticator mAuthenticator;
    @Override
    public void onCreate() {
        Log.i("APP", "Service created");
        // Create a new authenticator object
        mAuthenticator = new Authenticator(this);
    }

     /** When the system binds to this Service to make the RPC call
     * return the authenticator's IBinder.
     */

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

    @Override
    public void onDestroy() {
        Log.i("APP", "Service destroyed");
    }

}

Android will trigger the intent action android.accounts.AccountAuthenticator which will cause the authenticator service to start.

SyncAdapter and sync service - Now there are two more classes one is Sync adapter which do the work of syncing data between server and local database and sync service which tie the sync adapter to the android sync framework.
Whenever sync happend the sync adapter onperformsync method is called and anything you will write in that overridden method will be perform to sync the data.

SyncAdapter
public class SyncAdapter extends AbstractThreadedSyncAdapter {
    ContentResolver mContentResolver;
  

    public SyncAdapter(Context context, boolean autoInitialize) {
        super(context, autoInitialize);
        mContext = context;
        mContentResolver = context.getContentResolver();
        myPrefs = new MyPrefs(mContext);
        apiInterface = ServiceGenerator.createService(ApiInterface.class);
    }

    @Override
    public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
        Log.e("inPerformSync", "syncing" + myPrefs.isContactUploaded());
       // sync data or perform your action
    }
}
SyncService - 
public class SyncService extends Service {
    private static SyncAdapter sSyncAdapter = null;
    private static final Object sSyncAdapterLock = new Object();

    @Override
    public void onCreate() {
        Log.e("SyncService", "Service created");
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                Logger.LogError("ISNOT", "NULL");
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }

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

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e("SyncService", "Service destroyed");
    }
}

And also XML is needed for the sync adapter and Sync service should be registered in the manifest.

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

accountType must match the authenticator.xml accountType

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

Final step is to add account and start syncing data -

public static void CreateSyncAccount(Context context) {
    boolean newAccount = false;
    boolean setupComplete = PreferenceManager
            .getDefaultSharedPreferences(context).getBoolean(PREF_SETUP_COMPLETE, false);
    Account account = AuthenticatorService.GetAccount();
    AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
    if (accountManager.addAccountExplicitly(account, null, null)) {
        ContentResolver.setIsSyncable(account, AUTHORITY, 1);
        ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
        ContentResolver.addPeriodicSync(account,
                AUTHORITY,
                Bundle.EMPTY,
                Config.SYNC_PERIODIC_TIME);
        newAccount = true;
    }
    if (newAccount || !setupComplete) {
        PreferenceManager.getDefaultSharedPreferences(context).edit()
                .putBoolean(PREF_SETUP_COMPLETE, true).commit();
    }
}

About Author

Author Image
Ankita Singh

Ankita is an Android Application Developer. She has experience in developing android application by using Java and xml.

Request for Proposal

Name is required

Comment is required

Sending message..