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.
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) ?