Android SyncAdapter sync Local to Web Server Demo

Android have its own implementation to let local database to synchronize with the web server which using SyncAdapter. The SyncAdapter able to synchronize between local database and web server to make both have the same database. Why you want synchronize? it is able to let the user backup their local database in the web server so they will not lost the important data in the application. Besides that, user also able to use the application without network connection after synchronize the latest database. By using SyncAdapter, you can set how long u want to synchronize the database. In this tutorial, I will teach you how to use android syncadapter in the application, this application wont do any local database and web server synchronize. This is just let you have basic concepts on it.

Creating a New Project

1. Open Android Studio IDE in your computer.
2. Create a new project and Edit the Application name to “SynAdapterExample”.
(Optional) You can edit the company domain or select the suitable location for current project tutorial. Then click next button to proceed.
3. Select Minimum SDK (API 15:Android 4.0.3 (IceCreamSandwich). I choose the API 15 because many android devices currently are support more than API 15. Click Next button.
4. Choose “Empty Activity” and Click Next button
5. Lastly, press finish button.

Create a AbstractThreadedSyncAdapter class

Add a new class and name it to SyncAdapter in your java package and this class will extends the AbstractThreadedSyncAdapter object to perform sync in the application.

class SyncAdapter extends AbstractThreadedSyncAdapter {
    private static final String TAG = "SYNC";


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

    @Override
    public void onPerformSync(
        Account account,
        Bundle bundle,
        String s,
        ContentProviderClient contentProviderClient,
        SyncResult syncResult)
    {
        Log.d(TAG, "sync: " + account);
        getContext().getContentResolver().notifyChange(SyncProvider.URI, null, false);
    }
}

Create a ContentProvider class

Add a new class for contentprovider name as SyncProvider, this is a dummy contentprovider without any database. This is to easy for demonstrate the feature. You can add your SQLite to the ContentProvider in the following link. https://questdot.com/android-contentprovider/

public class SyncProvider extends ContentProvider {
    private static final String TAG = "CP";

    public static final String AUTHORITY = "com.questdot.synadapterexample";
    public static final Uri URI
        = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build();


    @Override
    public boolean onCreate() { return true; }

    @Override
    public String getType(Uri uri) {
        Log.d(TAG, "type: " + uri);
        return null;
    }

    @Override
    public Cursor query(Uri uri, String[] proj, String sel, String[] selArgs, String sort) {
        Log.d(TAG, "query: " + uri);
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        Log.d(TAG, "insert: " + uri);
        return uri;
    }

    @Override
    public int delete(Uri uri, String s, String[] strings) {
        Log.d(TAG, "update: " + uri);
        return 0;
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings){
        Log.d(TAG, "delete: " + uri);
        return 0;
    }
}

Create an Authentication account Service

This service is to authenticate the account in the application so you can deny unauthentication account to use this application. The following source code are the sample of implementation.

public class GenericAccountService extends Service {
    private static final String TAG = "GenericAccountService";
    //account type same as xml
    private static final String ACCOUNT_TYPE = "com.questdot.synadapterexample";
    public static final String ACCOUNT_NAME = "sync";
    private Authenticator mAuthenticator;


    public static Account GetAccount() {
        final String accountName = ACCOUNT_NAME;
        return new Account(accountName, ACCOUNT_TYPE);
    }

    @Override
    public void onCreate() {
        Log.i(TAG, "Service created");
        mAuthenticator = new Authenticator(this);
    }

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

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

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

        @Override
        public Bundle editProperties(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                     String s) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle addAccount(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                 String s, String s2, String[] strings, Bundle bundle)
                throws NetworkErrorException {
            return null;
        }

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

        @Override
        public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                   Account account, String s, Bundle bundle)
                throws NetworkErrorException {
            throw new UnsupportedOperationException();
        }

        @Override
        public String getAuthTokenLabel(String s) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle updateCredentials(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                        Account account, String s, Bundle bundle)
                throws NetworkErrorException {
            throw new UnsupportedOperationException();
        }

        @Override
        public Bundle hasFeatures(AccountAuthenticatorResponse accountAuthenticatorResponse,
                                  Account account, String[] strings)
                throws NetworkErrorException {
            throw new UnsupportedOperationException();
        }
    }

}

Add xml package and create a new xml for account

Create a xml account layout for authentication account. The account type of following code should same as GenericAccountService class account type.

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

Create Sync Service

Add a new class for synchronize service and it will be bind with the syncadapter. You can copy the source code.

public class SyncService extends Service {
    private static final String TAG = "SyncService";

    private static final Object sSyncAdapterLock = new Object();
    private static SyncAdapter sSyncAdapter = null;


    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "Service created");
        synchronized (sSyncAdapterLock) {
            if (sSyncAdapter == null) {
                sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
            }
        }
    }

    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "Service destroyed");
    }


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

Create a new xml file in xml folder

Create a xml for sync service, you can follow the source code in the bottom. AccountType should be same and contentAuthority is same as contentProvider in your SyncProvider class.

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

Create a Application class

Add a new class in your java package. This application class to initialize the new account in your application. It is dummy and without authentication.

public class SyncApp extends Application {
    private static final String TAG = "APP";
    private static final long SYNC_FREQUENCY = 60 * 60;  // 1 hour (in seconds)

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "create");

        Account account = GenericAccountService.GetAccount();

        AccountManager accountManager = (AccountManager) getApplicationContext().getSystemService(Context.ACCOUNT_SERVICE);
        if (accountManager.addAccountExplicitly(account, null, null)) {
            // Inform the system that this account supports sync
            ContentResolver.setIsSyncable(account, SyncProvider.AUTHORITY, 1);
            // Inform the system that this account is eligible for auto sync when the network is up
            ContentResolver.setSyncAutomatically(account, SyncProvider.AUTHORITY, true);
            // Recommend a schedule for automatic synchronization. The system may modify this based
            // on other scheduled syncs and network utilization.
            ContentResolver.addPeriodicSync(
                    account, SyncProvider.AUTHORITY, new Bundle(), SYNC_FREQUENCY);

            ContentResolver.requestSync(account,
                    SyncProvider.AUTHORITY, new Bundle());

        }



    }
    public static void TriggerRefresh() {
        Bundle b = new Bundle();
        // Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
        b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
        b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
        ContentResolver.requestSync(
                GenericAccountService.GetAccount(),      // Sync account
                SyncProvider.AUTHORITY, // Content authority
                b);                                      // Extras
    }



}

Edit activity_main.xml layout

Edit your activity_main layout and this layout i will add the button to perform sync in the application.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.questdot.synadapterexample.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Syn Adapter"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="99dp" />

    <Button
        android:text="Syn Now"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/btnSyn" />
</RelativeLayout>

Edit MainActivity.java class

Go to your mainactivity class and copy paste the following code in this class to perform sync.

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    Button btnSyn;
    private ContentObserver mObserver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnSyn= (Button)findViewById(R.id.btnSyn);

        btnSyn.setOnClickListener(this);
        mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
            public void onChange(boolean selfChange) {
                // Do something.
            }
        };
        getContentResolver().registerContentObserver(SyncProvider.URI, true,mObserver);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        getContentResolver().unregisterContentObserver(mObserver);
    }

    @Override
    public void onClick(View v) {
        switch(v.getId()){
            case R.id.btnSyn:
                Log.d("ACT", "sync called");


                SyncApp.TriggerRefresh();
                break;
        }
    }
}

Edit AndroidManifest.xml

I will add four permmissions in the androidmanifest to perform sync which you can see in the source code below. Also add the components in the manifest file, you can see the example below.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.questdot.synadapterexample">
    <!-- Required for fetching feed data. -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <!-- Required to register a SyncStatusObserver to display a "syncing..." progress indicator. -->
    <uses-permission android:name="android.permission.READ_SYNC_STATS"/>
    <!-- Required to enable our SyncAdapter after it's created. -->
    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS"/>
    <!-- Required because we're manually creating a new account. -->
    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"/>

    <application
        android:name=".SyncApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".SyncService"
            android:exported="false">

            <intent-filter>
                <action android:name="android.content.SyncAdapter"/>
            </intent-filter>

            <meta-data
                android:name="android.content.SyncAdapter"
                android:resource="@xml/sync"/>
        </service>


        <service android:name=".GenericAccountService">
            <!-- Required filter used by the system to launch our account service. -->
            <intent-filter>
                <action android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
            <!-- This points to an XMLf ile which describes our account service. -->
            <meta-data android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/account" />
        </service>
        <!-- authorities same as syn.xml -->
        <provider
            android:exported="false"
            android:authorities="com.questdot.synadapterexample"
            android:name=".SyncProvider" />
    </application>

</manifest>

Edit strings.xml

<resources>
    <string name="app_name">SynAdapterExample</string>
    <string name="action_sync">sync</string>
    <string name="account_type">com.questdot.synadapterexample</string>
</resources>

Run your project

Finally, you can try how the synchronize work in the sample project. Note : check the log in the Android Monitor to understand how its work.

(Android SyncAdapter sync Local to Web Server)

Source Code

(Visited 2,606 times, 1 visits today)
Advertisements

Yong Loon Ng

Ng Yong Loon, better known as Kristofer is a software engineer and computer scientist who doubles up as an entrepreneur.

You may also like...

1 Response

  1. CHEFOUETMEKA says:

    Merci pour ce Post. J’ai quelques question :
    1- comment recuperer les donnee du Thread principale dans notre fonction public void onPerformSync(Account account,
    Bundle extras,
    String authority,
    ContentProviderClient provider,
    SyncResult syncResult) ?

Leave a Reply

Your email address will not be published. Required fields are marked *