Using Sync Adapter to transfer data in android

Nyame Bismark
10 min readDec 7, 2020
Photo by Daniel Romero on Unsplash

Data transfer between android app and web server can make your application significantly useful and sometimes compelling to your users depending on the approach taken to implement the transfer. Users may want their data on server to be available on their android app so that the application will be useful in offline.

Although you can design your own framework to make this data transfer and synchronization work, you can make use of android sync adapter framework that can do a whole lot for you than you might do it yourself. Sync Adapter is a plugin that does data synchronization and it is invoked by the android Sync Manager when it is called. This framework is used by different apps on android operating system.

Important features Sync Adapter offers

a. Plugin-architecture: It allows you to add your own data transfer code in the form of callable components to the system.

b. Automated Execution: It allows data transfer execution based on various conditions like data changes, time elapsed or a time of day. The data transfer that were not run are being added to a queue to be ran later when possible.

c. Automated Network Checking: The system only runs your data transfer when your device is connected to a network.

d. Improved Battery Performance: The framework allows all the apps that do data transfer and synchronization to be in one place. So when your data transfer is scheduled, it does that in conjunction with data transfers from other apps. As a result, it reduces the number of times the system has to switch on the network which reduces the battery consumption.

e. Account Management and Authentication: The framework allows you to provide user credentials or server login if it is required by your application. This can be optionally integrated by using the account management and authentication provided by the framework.

Components of Sync Adapters

Now we can look at the components that make up a sync adapter. Before a sync adapter can work out, we need the components but not all components are mandatory but has to be stubbed.

  1. Create Account Authenticator.
  2. Create Content Provider.
  3. Then create Sync Adapter.

Creating Account Authenticator

The Sync Adapter framework assumes that the transfer of data between the device storage associated with the account and the server storage requires login access. As a result, it provides a components called Authenticator which can handle the user credentials for login information.

Even if your app does not need account authentication, you need to provide authenticator component even though the information would not be called when your app does not need server authentication. You can provide authentication component by implementing Authenticator interface and implementing all its stub methods it contains. You also need to provide a bound Service that allows the sync adapter framework to call the authenticator's methods.

What Account Authenticator is about

During account authentication we use authentication token which is authorized to perform any transaction on the server, mostly for android developers, we are not responsible for providing an auth-token unless you are also doing backend stuff for your network communication. Let’s assume we have an already existing API for our token implementation.

AccountManager is provided by android framework to handle all your account interactions. In your device, there might exists other apps that do data synchronization, the AccountManager manages all accounts for fresh token. When your account needs a new token, the AccountManager finds your AccountAuthenticator to provide a new token for your transaction by opening a new Sign-in / Create Account activity or sending previously requested token.

There can be many different services under a single account. For instance, Google’s authenticator on Android is authenticating Google Mail service (Gmail) along with other Google services such as Google Calendar and Google Drive.

This is a brief description of how account authentication works and is created. We need this for our topic, which is the android sync adapter, and as a result, we briefly talked about it but if your understanding was not enhanced on it, I recommend these articles on it which treats the topic fully.
1. Write your own Android Authenticator

2. Synchronization in Android applications. Part one

Let’s create our Account Authenticator and name it as BookAuthenticator.java which extend AbstractAccountAuthenticator.

In order for the sync adapter framework to access the our authenticator created, we need to create a bound service through that the framework can access our authenticator through the service binder object. The binder object allows the framework to pass data between the framework and the authenticator. Let’s begin by creating a bound service named as BookAuthenticatorService.java. The authenticator is initialized in the onCreate method of the service.

To plug your authenticator component into sync adapter and account framework, we need to provide a metadata that describe our authenticator component. The component describes the account type you have created for your sync adapter. We declare this metadata in xml file stored in the /res/xml/ directory in your app project named as book_authenticator.xml

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

Now we can link our authenticator service in the android manifest file as shown below. The <meta-data> element declares the metadata for the authenticator. The android:name attribute links the meta-data to the authentication framework. The android:resource element specifies the name of the authenticator metadata file you created previously.

Creating Content Provider

Content provider is used as a mechanism to store data to our local database. This provides security as the provider manages the data storage. If your sync data framework stores data in another form, we still need to provide a stub content provider as it is required by the sync data framework lest your app will crash when the framework calls your sync adapter. I have already a treated topic on implementation of content provider in android so you can check it out for better understanding of the topic.

After the content provider is created, it should be referenced in the android manifest file as done below.

<provider
android:name="com.example.android.datasync.provider.YourProvider"
android:authorities="com.example.android.datasync.provider"
android:exported="false"
android:syncable="true"/>

Create a sync adapter

The sync adapter framework encapsulates the code for the task that transfer data between device storage and a server. Based on the scheduling and triggers you provide in your app, the sync adapter framework runs the code in the sync adapter component. The SyncAdapter is a plug-in that handles background syncs and it is registered in the platforms SyncManager which takes care of the running of the sync adapter when it is scheduled or triggered.

We can use a simple Service or IntentService to do a background task or sync but with the sync adapter, it provides an additional benefits that come out of the box for your background sync implementation. Below are some of the benefits:

a. Efficient Battery Consumption: The system runs all the sync adapters at the same time. It does not wake the device just for a single sync to be performed.

b. Interface: The system provides an interface in the settings of the device for the user to change the sync options according to his preferences for the sync operation.

c. Content Observing: The system uses a content provider to manage the data access on the device. The system is able to know when the content is changing so that a sync can be triggered.

d. Retry Mechanism: The system is able to retry when a sync fails and it is able to do that with the timeout.

The Sync Adapter has 2 main properties and these are syncable and auto-sync. When the syncable is false, then no manual or automatic sync will be enabled and the sync adapter will not be available in the Settings screen.

  1. Automatic Sync: This makes the system sync your data every 24 hours or when there is a data changes as provided by the content provider. This can be done on the Settings screen or programmatically by calling setSyncAutomatically()
  2. Periodic Sync: This makes the data transfer sync happen periodically and not at the exact time it is supposed to run but according to the battery efficiency of the device. This can only run when the syncable is true. This is set programmatically by calling addPeriodicSync(). Specifies that a sync should be requested with the specified the account, authority, and extras at the given frequency. If there is already another periodic sync scheduled with the account, authority and extras then a new periodic sync won’t be added, instead the frequency of the previous one will be updated. This can be problematic when doing periodic sync since the system needs to check the server to see if there is a data changes but we can go around this by using FCM which sends the system a notification when there is a data change.
  3. Manual Sync: We may like to also do the sync manually and this can be done by calling requestSync().
  4. Canceling: We can cancel our pending sync by calling cancelSync(). When the sync has already started, we can cancel it but a call to onSyncCanceled() method will be invoked.

We can create a sync adapter component by extending AbstractThreadedSyncAdapter and writing its constructors. We use the constructor to initialize our components like the contentResolver when we need to use content provider to store our data. Since a second form of the constructor was added in Android platform version 3.0 to support the parallelSyncs argument, you need to create two forms of the constructor to maintain compatibility.

When the AbstractThreadedSyncAdapter class is extended, we implement a method called OnPerformSync() which provides the following arguments.

a. Account: This refers to the account associated with the event that triggered the sync adapter.

b. Bundle: This contains flags sent by the event that triggered the sync adapter.

c. Authority: This is the authority of the content provider you have created. It uniquely identifies the content provider.

d. Content provider client: This is a light-weight public interface of the content provider. This is like the contentResolver because it has the same method hooks.

e. Sync Results: This is an object that sends information to the sync adapter.

Binding Sync Adapter to the framework

After creating our sync adapter, we need to bind this to our framework by creating a bound Service for it. The framework accesses the sync adapter through the bound Service we create. The Service returns our sync adapter IBinder object which the framework uses to call the onPerformSync().

We create a service called BookSyncAdapterService.java.

Adding Sync Adapter Metadata file in xml resources. Let’s explain the attributes used in the xml.

a . contentAuthority: This is the authority of the content provider you created or you want to use for this sync adapter.

b. accountType: This is the account type you specified in the account authenticator describing the account type you want to use.

c. userVisible: This is a flag to control if your user will see this sync adapter in the Account section of the Settings screen.

d. supportsUploading: This is the flag that indicates whether your sync adapter can upload data to cloud.

e. allowParallelSyncs: This flag show whether a multiple instances of sync adapter should run in parallel mode. This is useful if you have multiple user account linked to your sync adapter then you can set to true.

f. isAlwaysSyncable: This flag is true if you want your sync adapter to automatically run. When this flag is false, then you need to start the sync by calling requestSync() on the contentResolver.

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

Add the sync adapter service to the android manifest as below

<!--Service Initialization for Sync Adapter-->
<service
android:name=".syncAdapter.BookSyncAdapterService"
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/book_sync_adapter" />
</service>

Adding the Account to the Sync Adapter Framework

After setting up all the components of the sync adapter, we need to add the account type to the android system. To set up the account type, add a placeholder account that uses the account type by calling addAccountExplicitly(). We can do this in the onCreate() of our opening activity.

Do not forget to add your permission in the android manifest file.

<uses-permission
android:name="android.permission.USE_CREDENTIALS"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>

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

In this state if you have followed this keenly, you can run your app and when you go to the Settings screen and you locate your Accounts section, you will see your account added with your <account-authenticator> label beneath the account.👌

Up to this point is a setup of the sync adapter but your sync adapter has not started syncing yet. You should trigger your sync adapter in the following conditions in order to get its full benefits.

  1. When server data changes: You can trigger your sync adapter when the server data changes but you need to bear in mind of the battery consumption. In order to achieve this with the battery efficiency as a factor, you can setup a Firebase Cloud Messaging service in the app that will notify whenever the data changes on the server. When the changes happen then you can call ContentResolver.requestSync() to start the sync.
  2. When local data changes: You can also trigger your sync adapter when data in your preferred table changes. This is very easy when you are using content provider to manage your database. You could setup ContentObserver and pass the instance object to contentResolver to watch for any data changes by calling
//registering the observer for content provider
context.getContentResolver().registerContentObserver(Uri,true, ContentObserver);
//start the sync
ContentResolver.requestSync();

3. Periodically Scheduling your Sync Adapter: This can help if you want to trigger your sync adapter periodically. The periodic schedule does not happen at the exact time interval you may want your sync adapter to schedule so in order to achieve this you can use AlarmManager to help you trigger this at your preferred interval.

//Call this if you want to add periodic sync at an interval
ContentResolver.addPeriodicSync(mAccount,AUTHORITY,null,3600L);

4. Sync on Demand: You can trigger your sync adapter in response to some events like refresh button called or button click event. This process is not battery efficient though.

// making the sync adapter run manually and EXPEDITED Flag
// makes the sync run as immediately as it is triggered.
makesBundle syncBundle = new Bundle();
syncBundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL,true);
syncBundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED,true);
ContentResolver.requestSync(newAccount,AUTHORITY,syncBundle);

In conclusion, Sync adapter framework is very helpful when it comes to a complicated situation of syncing your remote data with the local data. But due to its complexity, if your sync you want to achieve is not complex and kind of simple, then I will recommend using IntentService or Service to achieve this. Thanks for being with me through this.

Follow me on my youTube Channel for more on android development.

References

https://developer.android.com/training/sync-adapters

http://blog.udinic.com/2013/07/24/write-your-own-android-sync-adapter/

--

--