sync on android
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.TRANSCRIPT
SyncON ANDROID
Jerzy Chalupski
chalup
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
ContentResolver requestSync() vs. notifyChange()
requestSync(account, authority, extras)
notifyChange(uri, observer, syncToNetwork)
ContentResolver requestSync() vs. notifyChange()
requestSync(account, authority, extras)
notifyChange(uri, observer, syncToNetwork)
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.