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 -
- Plug in Architecture
- Automated exceution.
- Automated network checking
- Good battery performance
- 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:
- A content-provider that extends content provider
- An adapter that extend AbstractThreadedSyncAdapter
- And a service that extend service.
- 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();
}
}
Cookies are important to the proper functioning of a site. To improve your experience, we use cookies to remember log-in details and provide secure log-in, collect statistics to optimize site functionality, and deliver content tailored to your interests. Click Agree and Proceed to accept cookies and go directly to the site or click on View Cookie Settings to see detailed descriptions of the types of cookies and choose whether to accept certain cookies while on the site.
About Author
Ankita Singh
Ankita is an Android Application Developer. She has experience in developing android application by using Java and xml.