Sync on Android

Preview:

DESCRIPTION

Talk about standard synchronisation pattern on Android (SyncAdapter + Authenticator + ContentProvider). Presentation given at KrakDroid 2012 (http://www.krakdroid.pl). Video from the talk (in Polish): http://www.youtube.com/watch?v=8Oti4qf7P84.

Citation preview

SyncON ANDROID

Jerzy Chalupski

chalup

jerzy@futuresimple.com

What we do in general...

What we do ALL THE TIME

sync, sync, sync, sync

SyncON ANDROID

OVERVIEW ARCHITECTURE

CONTENT PROVIDER PITFALLS & PROTIPS

SyncON ANDROID

OVERVIEW ARCHITECTURE

CONTENT PROVIDER PITFALLS & PROTIPS

Not exactly the fresh topic...

„Developing Android REST client applications”

by Virgil Dobjanschi@Google I/O 2010

Not exactly the fresh topic...

„Synchronizacja danych z serwisamiwebowymi w Androidzie”

by Bartosz Filipowicz@KrakDroid 2011

...but there is still much confusion

Not exactly the fresh topic...

„Synchronizacja danych z serwisamiwebowymi w Androidzie”

by Bartosz Filipowicz@KrakDroid 2011

ContentProvider

ContentResolver

SyncManager

SyncAdapter

Authenticator

AccountManager

UI

„Wat? Why do I need to write all this crap, sync is just fetching data from server”

typical reaction

Sync provides data sharing

Sync provides data sharing

offline mode

Sync provides data sharing

offline mode

responsive UI

Sync provides data sharing

offline mode

responsive UI

GREAT UX

SyncON ANDROID

OVERVIEW ARCHITECTURE

CONTENT PROVIDER PITFALLS & PROTIPS

ContentProvider 101: CRUD

CREATE

READ

UPDATE

DELETE

insert()

query()

update()

delete()

ContentProvider 101: URI

content://com.futuresimple.krakdroid.Provider/datasets/1

ContentProvider 101: URI

content://com.futuresimple.krakdroid.Provider/datasets/1

ContentProvider 101: URI

content://com.futuresimple.krakdroid.Provider/datasets/1

ContentProvider 101: URI

content://com.futuresimple.krakdroid.Provider/datasets/1

ContentProvider 101: notifications

content://com.futuresimple.krakdroid.Provider/datasets/1content://com.futuresimple.krakdroid.Provider/solutionscontent://com.futuresimple.krakdroid.Provider/solutions/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/2content://com.futuresimple.krakdroid.Provider/solutions/2/answers/1

ContentResolver.notifyChange(“content://com.futuresimple.krakdroid.Provider/datasets/1”);

content://com.futuresimple.krakdroid.Provider/datasets/1content://com.futuresimple.krakdroid.Provider/solutionscontent://com.futuresimple.krakdroid.Provider/solutions/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/2content://com.futuresimple.krakdroid.Provider/solutions/2/answers/1

ContentProvider 101: notifications

ContentResolver.notifyChange(“content://com.futuresimple.krakdroid.Provider/solutions/1/answers/1”);

content://com.futuresimple.krakdroid.Provider/datasets/1content://com.futuresimple.krakdroid.Provider/solutionscontent://com.futuresimple.krakdroid.Provider/solutions/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/1content://com.futuresimple.krakdroid.Provider/solutions/1/answers/2content://com.futuresimple.krakdroid.Provider/solutions/2/answers/1

ContentProvider 101: notifications

SyncON ANDROID

OVERVIEW ARCHITECTURE

CONTENT PROVIDER PITFALLS & PROTIPS

ContentProvider

ContentResolver

SyncManager

SyncAdapter

Authenticator

AccountManager

UI

AccountManager

addAccount()

ContentResolverSyncManager

SyncAdapter ContentProvider

Authenticator

UI

Sync architectureCREATING NEW ACCOUNT

AccountManager

addAccount()

addAccount()

ContentResolverSyncManager

SyncAdapter ContentProvider

Authenticator

UI

Sync architectureCREATING NEW ACCOUNT

AccountManager

addAccount()

addAccount()

ContentResolverSyncManager

SyncAdapter ContentProvider

Authenticator

UI

Sync architectureCREATING NEW ACCOUNT

AccountManager

ContentResolver

UI

ContentProvider

Sync architecture SHOW SYNCED DATA

Authenticator

query()

query()

SyncManager

SyncAdapter

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture REQUEST SYNC

Authenticator

getAccountsByType()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture REQUEST SYNC

Authenticator

getAccountsByType()

requestSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture REQUEST SYNC

Authenticator

getAccountsByType()

requestSync()

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture EDIT DATA

Authenticator

insert()

insert()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture EDIT DATA

Authenticator

insert()

insert()notifyChange()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture EDIT DATA

Authenticator

insert()

insert()notifyChange()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture EDIT DATA

Authenticator

getAccountsByType()

insert()

insert()notifyChange()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture EDIT DATA

Authenticator

getAccountsByType()

onPerformSync()

insert()

insert()notifyChange()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture POST NEW DATA

Authenticator

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture POST NEW DATA

Authenticator

getAuthToken()

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture POST NEW DATA

Authenticator

getAuthToken()

query()query()onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture POST NEW DATA

Authenticator

getAuthToken()

query()query()

POST

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

getAuthToken()

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

getAuthToken()

GET

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

getAuthToken()

applyBatch()

GET

applyBatch()

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

getAuthToken()

applyBatch()notifyChange()

GET

applyBatch()

onPerformSync()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Sync architecture GET NEW DATA

Authenticator

getAuthToken()

applyBatch()notifyChange()

GET

applyBatch()

onPerformSync()

onLoaderFinished()

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Authenticator Sync architecture WIN #1: NO REMOTE I/O IN UI

AccountManager

ContentResolver

UI

SyncManager

SyncAdapter ContentProvider

Authenticator Sync architecture WIN #2: SYNC SCHEDULING

SyncON ANDROID

OVERVIEW ARCHITECTURE

CONTENT PROVIDER PITFALLS & PROTIPS

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/1

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/1

Plan A: Use local IDs! (BaseColumns._ID)

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/1

Plan A: Use local IDs! (BaseColumns._ID)

ISSUE: still need server-side IDs for relations

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/1

Plan A: Use local IDs! (BaseColumns._ID)

ISSUE: still need server-side IDs for relationsISSUE: conversions between server and local IDs

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/15002900

Plan B: OK, use server-side IDs!

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/15002900

Plan B: OK, use server-side IDs!

ISSUE: what about offline mode?

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/-42

Plan C: OK, let’s mix things up!

content://com.futuresimple.krakdroid.Provider/datasets/15002900

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/-42

Plan C: OK, let’s mix things up!

ISSUE: very tricky implementation

content://com.futuresimple.krakdroid.Provider/datasets/15002900

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/GUID

Plan D: GUID

IDs in ContentProvider URIs

content://com.futuresimple.krakdroid.Provider/datasets/GUID

Plan D: GUID

ISSUE: needs support on the backend

Deleting an account

From your app

From system settings

From 3rd party app

Deleting an account

From your app

From system settings

From 3rd party app

“Where should I perform user

data cleanup?”

Deleting an account

@Override getAccountRemovalAllowed()

Deleting an account

@Override getAccountRemovalAllowed()

CONS: depends on current Settings implementation.

PROS: exactly the thing you need.

Deleting an account

<receiver android:name=".auth.AccountBroadcastReceiver" android:enabled="true" > <intent-filter> <action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" /> </intent-filter></receiver>

Deleting an account

<receiver android:name=".auth.AccountBroadcastReceiver" android:enabled="true" > <intent-filter> <action android:name="android.accounts.LOGIN_ACCOUNTS_CHANGED" /> </intent-filter></receiver>

CONS: only info about existing accounts.

PROS: peace of mind.

Sync lifecycle

Happy case:

Sync lifecycle

Happy case:

requestSync()

onPerformSync()

Sync lifecycle

requestSync()

Happy case:

onPerformSync()

Sync lifecycle

requestSync() onStatusChanged()

Happy case:

onPerformSync()

Sync lifecycle

Not-so-happy case:

requestSync() onStatusChanged()

onPerformSync()

Sync lifecycle

Not-so-happy case:

onStatusChanged()requestSync()

onPerformSync()

Sync lifecycle

Not-so-happy case:

requestSync() cancelSync()+

onStatusChanged()

Futureproof your sync

Futureproof your sync

1. Be lenient on GET

Futureproof your sync

1. Be lenient on GET

2. Be strict on POST

Futureproof your sync

3. Have resync in v1.0

1. Be lenient on GET

2. Be strict on POST

?

Thanks.

Recommended