SlideShare a Scribd company logo
1 of 63
Download to read offline
The Death 
of the Refresh Button
@Mathieu_Calba 
+MathieuCalba
Once upon a time…
Julien was searching for an app to 
manage his bank account
But he couldn’t find the one 
that meets his needs
They all seemed 
broken
They always needed an internet connection
Even just to check 
his leisure budget
Vincent was very ANGRY
He needed a simple 
debit/credit managing 
app with a good 
backup
So he decided to create his own app, focused on 
user experience and simplicity
What does his app need?
What does his app need? 
A great app backend
Mobile application 
UI App 
backend API Server
With a great app backend, 
you have more flexibility 
at the UI level
What is syncing?
What is syncing? 
Executing a 
BACKGROUND JOB 
to update local data 
WHEN APPROPRIATE
BACKGROUND JOB 
• Non-user facing operation 
• Upload and/or download 
• Requires network connectivity 
• Always leave the data in good state
What is syncing? 
Executing a 
BACKGROUND JOB 
to update local data 
WHEN APPROPRIATE
WHEN APPROPRIATE 
aka scheduling & triggering 
• Only when needed, and when relevant 
(network availability, battery level, etc) 
• Restore after reboot 
• Exponential back-off 
• Be careful with battery life 
• etc.
WHEN APPROPRIATE 
aka scheduling & triggering 
• Best solution: push message 
• Not always available, fallback: periodic 
polling
What is syncing? 
Executing a 
BACKGROUND JOB 
to update local data 
WHEN APPROPRIATE
How?
How? 
The Android way
How? 
The Android ways
AsyncTask & AlarmManager 
Create your AsyncTask: 
! 
public final class PeriodicTaskRunnable extends AsyncTask<Void, Void, Void> { 
@Override 
protected Void doInBackground(Void... voids) { 
// TODO the task ! 
return null; 
} 
} 
Start the periodic run with the AlarmManager: 
private void triggerPeriodicAsyncTask() { 
final Intent intent = new Intent(this, PeriodicTaskReceiver.class); 
final PendingIntent receiverPendingIntent = PendingIntent. 
getBroadcast(this, 140916, intent, PendingIntent.FLAG_UPDATE_CURRENT); 
final AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); 
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, // 
SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR, // 
AlarmManager.INTERVAL_HOUR, // 
receiverPendingIntent); 
}
Create your BroadcastReceiver: 
! 
public class PeriodicTaskReceiver extends BroadcastReceiver { 
public static final String ACTION_SYNC = “com.mathieucalba.tasks.ACTION_SYNC"; 
@Override 
public void onReceive(Context context, Intent intent) { 
if (ACTION_SYNC.equals(intent.getAction())) { 
new PeriodicTaskRunnable().execute(); 
} 
} 
} 
And declare it in the Manifest: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.mathieucalba.testjobscheduler"> 
<application> 
<receiver 
android:name=".tasks.PeriodicTaskReceiver" 
android:exported="false"> 
<intent-filter> 
<action android:name="com.mathieucalba.tasks.ACTION_SYNC" /> 
</intent-filter> 
</receiver> 
</application> 
</manifest> 
AsyncTask & AlarmManager
AsyncTask & AlarmManager 
• AsyncTask & Receiver can be replaced by an 
IntentService for simplicity 
• Restoring periodic sync after a reboot? 
• Handling connectivity availability? 
• Indicate sync state to others 
• etc.
SyncAdapter
Definition 
One method to do the 
UPLOAD & DOWNLOAD SYNC 
(SyncAdapter) 
for one DATA PROVIDER (ContentProvider) 
associated with a USER ACCOUNT 
(AccountAuthenticator) 
which can be TRIGGERED MANUALLY 
or by the SYSTEM.
public final class AccountAuthenticator extends AbstractAccountAuthenticator { 
public static final String ACCOUNT_TYPE = "com.mathieucalba.testjobscheduler"; 
public static final String ACCOUNT_NAME_SYNC = "com.mathieucalba.testjobscheduler"; 
public AccountAuthenticator(Context context) { 
super(context); 
} 
@Override 
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 
String authTokenType, String[] requiredFeatures, Bundle options) 
throws NetworkErrorException { 
return null; 
} 
@Override 
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, 
Bundle options) throws NetworkErrorException { 
return null; 
} 
@Override 
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { 
throw new UnsupportedOperationException(); 
} 
//… 
} 
1- Account
public final class AccountAuthenticator extends AbstractAccountAuthenticator { 
//… 
@Override 
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, 
String authTokenType, Bundle options) throws NetworkErrorException { 
throw new UnsupportedOperationException(); 
} 
@Override 
public String getAuthTokenLabel(String authTokenType) { 
throw new UnsupportedOperationException(); 
} 
@Override 
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, 
String[] features) throws NetworkErrorException { 
throw new UnsupportedOperationException(); 
} 
@Override 
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, 
String authTokenType, Bundle options) 
throws NetworkErrorException { 
throw new UnsupportedOperationException(); 
} 
} 
1- Account
Bind the AccountAuthenticator to the framework: 
! 
public final class AccountAuthenticatorService extends Service { 
private static final Object LOCK = new Object(); 
private static AccountAuthenticator sAuthenticator; 
@Override 
public void onCreate() { 
super.onCreate(); 
synchronized (LOCK) { 
if (sAuthenticator == null) { 
sAuthenticator = new AccountAuthenticator(getApplicationContext()); 
} 
} 
} 
@Override 
public IBinder onBind(Intent intent) { 
synchronized (LOCK) { 
return sAuthenticator.getIBinder(); 
} 
} 
} 
1- Account
Declare your AccountAuthenticatorService into the Manifest: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.mathieucalba.testsyncadapter"> 
<application> 
<service 
android:exported="true" 
android:name=".accounts.AccountAuthenticatorService"> 
<intent-filter> 
<action android:name="android.accounts.AccountAuthenticator" /> 
</intent-filter> 
<meta-data 
android:name="android.accounts.AccountAuthenticator" 
android:resource="@xml/account_authenticator" /> 
</service> 
</application> 
</manifest> 
! 
Configure your AccountAuthenticator: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" 
android:accountType="@string/config_accountType" 
android:icon="@drawable/ic_launcher" 
android:label="@string/app_name" 
android:smallIcon="@drawable/ic_launcher" /> 
1- Account
Add the Account required by the framework: 
! 
private void initAccountAuthenticator() { 
final AccountManager accountManager = AccountManager.get(this); 
final Account[] accounts = accountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); 
for (Account account : accounts) { 
if (AccountAuthenticator.ACCOUNT_NAME_SYNC.equals(account.name)) { 
return; 
} 
} 
accountManager.addAccountExplicitly(new Account(AccountAuthenticator.ACCOUNT_NAME_SYNC, 
AccountAuthenticator.ACCOUNT_TYPE), null, null); 
} 
1- Account 
For more info on implementing an AccountAuthenticator: http:// 
udinic.wordpress.com/2013/04/24/write-your-own-android-authenticator/
Create a stub ContentProvider: 
! 
public class StubProvider extends ContentProvider { 
@Override 
public boolean onCreate() { return true; } 
@Override 
public String getType(Uri uri) { return new String(); } 
@Override 
public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) { 
return null; 
} 
@Override 
public Uri insert(Uri uri, ContentValues contentValues) { return null; } 
@Override 
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { return 0; } 
@Override 
public int delete(Uri uri, String s, String[] strings) { return 0; } 
} 
2- ContentProvider
Declare your ContentProvider in the Manifest: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.mathieucalba.testsyncadapter"> 
<application> 
<provider 
android:authorities="com.mathieucalba.testsyncadapter.provider.stub" 
android:exported="false" 
android:name=".provider.StubProvider" 
android:syncable="true" /> 
</application> 
</manifest> 
2- ContentProvider 
For more details about ContentProviders, 
see my slides at http://bit.ly/ContentProvider
Implement the synchronization mechanism: 
! 
public class SyncAdapter extends AbstractThreadedSyncAdapter { 
public SyncAdapter(Context context) { 
super(context, false, false); // Context, auto initialize, parallel sync 
} 
@Override 
public void onPerformSync(Account account, Bundle extras, String authority, 
ContentProviderClient provider, SyncResult syncResult) { 
// TODO the sync 
} 
} 
3- SyncAdapter
Bind the SyncAdapter to the framework: 
! 
public class SyncAdapterService extends Service { 
private static final Object LOCK = new Object(); 
private static SyncAdapter sSyncAdapter = null; 
@Override 
public void onCreate() { 
super.onCreate(); 
synchronized (LOCK) { 
if (sSyncAdapter == null) { 
sSyncAdapter = new SyncAdapter(getApplicationContext()); 
} 
} 
} 
@Override 
public IBinder onBind(Intent intent) { 
synchronized (LOCK) { 
return sSyncAdapter.getSyncAdapterBinder(); 
} 
} 
} 
3- SyncAdapter
Declare your SyncAdapterService into the Manifest: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.mathieucalba.testsyncadapter"> 
<application> 
<service 
android:exported="true" 
android:name=".sync.SyncAdapterService"> 
<intent-filter> 
<action android:name="android.content.SyncAdapter" /> 
</intent-filter> 
<meta-data 
android:name="android.content.SyncAdapter" 
android:resource="@xml/sync_adapter" /> 
</service> 
</application> 
</manifest> 
3- SyncAdapter
3- SyncAdapter 
Configure your SyncAdapter: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" 
android:accountType="@string/config_accountType" 
android:allowParallelSyncs="false" 
android:contentAuthority="com.mathieucalba.testsyncadapter.provider.stub" 
android:isAlwaysSyncable="true" 
android:supportsUploading="true" 
android:userVisible="false" />
How to trigger a one time sync: 
! 
private static void triggerSyncAdapter(Account account, boolean now) { 
final Bundle extras = new Bundle(); 
if (now) { 
// ignore backoff && settings 
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); 
// put the request at the front of the queue 
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); 
} 
ContentResolver.requestSync(account, StubProvider.AUTHORITY, extras); 
} 
4- Triggering
How to configure an account for periodic sync, auto sync when network is up: 
! 
public static void setupSync(Account account) { 
if (account == null) { 
return; 
} 
// Inform the system that this account supports sync 
ContentResolver.setIsSyncable(account, StubProvider.AUTHORITY, 1); 
// Inform the system that this account is eligible for auto sync when the network is up 
ContentResolver.setSyncAutomatically(account, StubProvider.AUTHORITY, true); 
// Recommend a schedule for automatic synchronisation. The system may modify this based 
// on other scheduled syncs and network utilisation. 
ContentResolver.addPeriodicSync(account, // 
StubProvider.AUTHORITY, // 
new Bundle(), // 
TimeUnit.HOURS.toSeconds(1)); 
} 
4- Triggering 
! 
Periodic & automatic sync doesn’t works 
if user has deactivated it
5- ContentProvider Triggering 
One neat functionality: run the 
SyncAdapter when ContentProvider’s 
data changes
Mark the item to be deleted instead of deleting it: 
! 
@Override 
public int delete(Uri uri, String s, String[] strings) { 
final int count; 
if (StubContract.hasNeedSyncToNetworkParameter(uri)) { 
final int match = URI_MATCHER.match(uri); 
switch (match) { 
case OPTION_ID: 
final ContentValues values = new ContentValues(); 
values.put(SyncColumns.SYNC_DELETED, SyncColumns.SYNC_DELETED_MARKED); 
count = buildSimpleSelection(uri). 
where(selection, selectionArgs). 
update(getDatabaseHelper().getWritableDatabase(), values); 
break; 
default: 
throw new SyncToNetworkUnknownUriException(uri); 
} 
} else { 
count = buildSimpleSelection(uri). 
where(selection, selectionArgs). 
delete(getDatabaseHelper().getWritableDatabase()); 
} 
if (count > 0) { 
notifyChange(uri); 
} 
return count; 
} 
5- ContentProvider Triggering
Notify the change to the SyncAdapter: 
! 
private void notifyChange(Uri uri) { 
getContext().getContentResolver(). 
notifyChange(uri, null, isCallerUriUploadReady(uri) && !isCallerSyncAdapter(uri)); 
} 
public boolean isCallerSyncAdapter(Uri uri) { 
return StubContract.hasCallerIsSyncAdapterParameter(uri); 
} 
private boolean isCallerUriUploadReady(Uri uri) { 
if (StubContract.hasNeedSyncToNetworkParameter(uri)) { 
final int match = URI_MATCHER.match(uri); 
switch (match) { 
case OPTION_ID: 
return true; 
default: 
throw new SyncToNetworkUnknownUriException(uri); 
} 
} 
return false; 
} 
5- ContentProvider Triggering
How we detect an URI if from the SyncAdapter, and when a SyncAdapter trigger is needed: 
! 
interface SyncExtras { 
String IS_CALLER_SYNC_ADAPTER = "is_caller_sync_adapter"; 
String NEED_SYNC_TO_NETWORK = "need_sync_to_network"; 
} 
public static Uri addCallerIsSyncAdapterParameter(Uri uri) { 
return uri.buildUpon().appendQueryParameter(SyncExtras.IS_CALLER_SYNC_ADAPTER, 
Boolean.toString(true)).build(); 
} 
public static boolean hasCallerIsSyncAdapterParameter(Uri uri) { 
final String parameter = uri.getQueryParameter(SyncExtras.IS_CALLER_SYNC_ADAPTER); 
return parameter != null && Boolean.parseBoolean(parameter); 
} 
public static Uri addNeedSyncToNetworkParameter(Uri uri) { 
return uri.buildUpon().appendQueryParameter(SyncExtras.NEED_SYNC_TO_NETWORK, 
Boolean.toString(true)).build(); 
} 
public static boolean hasNeedSyncToNetworkParameter(Uri uri) { 
final String parameter = uri.getQueryParameter(SyncExtras.NEED_SYNC_TO_NETWORK); 
return parameter != null && Boolean.parseBoolean(parameter); 
} 
5- ContentProvider Triggering
5- ContentProvider Triggering 
Be careful, your corresponding query should not 
return the deleted data if it’s not the 
SyncAdapter querying.
SyncAdapter 
• Google way 
• Easily triggered by the system when 
appropriate (network availability, 
change in associated 
ContentProvider) 
• Needs an AccountAuthenticator (at 
least a stub) 
• Needs a ContentProvider (at least a 
stub)
JobScheduler
Definition 
SCHEDULE the execution of a JOB (via a 
Service) 
with VARIOUS PARAMETERS 
about WHEN 
the execution SHOULD HAPPEN 
(during a window of time, periodically, with network 
needed, etc.)
The JobService is where we are awaken: 
! 
public class MyJobService extends JobService { 
private ExecutorService mExecutor; 
private final Handler mHandler = new Handler(Looper.getMainLooper()); 
@Override 
public void onCreate() { 
super.onCreate(); 
mExecutor = Executors.newSingleThreadExecutor(); 
} 
@Override 
public void onDestroy() { 
mExecutor.shutdown(); 
super.onDestroy(); 
} 
@Override 
public boolean onStartJob(JobParameters jobParameters) { 
// We are on the Main thread, so post the Task to a background thread 
mExecutor.execute(new Task(jobParameters)); 
return true; 
} 
@Override 
public boolean onStopJob(JobParameters jobParameters) { 
// TODO interrupt Task 
return true; 
} 
} 
1- JobService
private final class Task implements Runnable { 
private final JobParameters mJobParameters; 
private Task(JobParameters jobParameters) { mJobParameters = jobParameters; } 
@Override 
public void run() { 
// TODO the network call 
mHandler.post(new FinishedTask(mJobParameters, true)); 
} 
} 
private final class FinishedTask implements Runnable { 
private final JobParameters mJobParameters; 
private final boolean mIsSuccess; 
private FinishedTask(JobParameters jobParameters, boolean isSuccess) { 
mJobParameters = jobParameters; 
mIsSuccess = isSuccess; 
} 
@Override 
public void run() { 
// Notify that the job has ended 
jobFinished(mJobParameters, mIsSuccess); 
} 
} 
1- JobService
Declare your MyJobService into the Manifest: 
! 
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
package="com.mathieucalba.testjobscheduler"> 
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> 
<application> 
<service 
android:name=".tasks.MyJobService" 
android:permission="android.permission.BIND_JOB_SERVICE" 
android:exported="true"/> 
</application> 
</manifest> 
1- JobService
2- Triggering 
Exemple for triggering a one time Job: 
! 
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_1, new ComponentName(this, MyJobService.class)). 
setBackoffCriteria(TimeUnit.MINUTES.toMillis(1), 
JobInfo.BackoffPolicy.EXPONENTIAL). 
setMinimumLatency(TimeUnit.SECONDS.toMillis(5)). 
setOverrideDeadline(TimeUnit.HOURS.toMillis(1)). 
setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY); 
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(builder.build()); 
! 
Exemple for triggering a periodic Job: 
! 
builder = new JobInfo.Builder(JOB_ID_PERIODIC, new ComponentName(this, MyJobService.class)). 
setPeriodic(TimeUnit.HOURS.toMillis(1)). 
setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED). 
setRequiresDeviceIdle(true). 
setRequiresCharging(true); 
((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(builder.build());
JobScheduler 
• Configurable scheduling (idle-mode, 
network availability, etc) across all the 
system 
• Simple, based upon a Service 
• Persisted state 
• Android-L+ only
Conclusion
Conclusion 
• SyncAdapter perfect if account and 
ContentProvider, great otherwise but can be 
tricky 
• JobScheduler very promising, but Android L+ 
only, a limited compat library would help 
spread its use (based upon Service & 
AlarmManager).
Conclusion 
• We have the tools to create an app the 
works seamlessly without an internet 
connection so the user never have to worry 
about his internet connection
Questions?
Credits 
• Up & Down icons by Guillaume Berry 
• The Search Underneath the Bed by Arielle Nadel 
• Yorkshire Moors Milky Way 3 by Matthew Savage 
• Injured Piggy Bank With Crutches by Ken Teegardin 
• No Internet by Marcelo Graciolli 
• Balancing The Account By Hand by Ken Teegardin 
• Rage by Amy McTigue 
• Daniel Foster by Online Shopping 
• 3D Bright Idea by Chris Potter 
• Landscape by Ben Simo 
• Christmas Eve Sunrise by Jay Parker

More Related Content

More from Paris Android User Group

Framing the canvas - DroidCon Paris 2014
Framing the canvas - DroidCon Paris 2014Framing the canvas - DroidCon Paris 2014
Framing the canvas - DroidCon Paris 2014
Paris Android User Group
 
Death to passwords - DroidCon Paris 2014
Death to passwords - DroidCon Paris 2014Death to passwords - DroidCon Paris 2014
Death to passwords - DroidCon Paris 2014
Paris Android User Group
 
Google glass droidcon - DroidCon Paris 2014
Google glass droidcon - DroidCon Paris 2014Google glass droidcon - DroidCon Paris 2014
Google glass droidcon - DroidCon Paris 2014
Paris Android User Group
 
Efficient Image Processing - Nicolas Roard
Efficient Image Processing - Nicolas RoardEfficient Image Processing - Nicolas Roard
Efficient Image Processing - Nicolas Roard
Paris Android User Group
 

More from Paris Android User Group (20)

Ingredient of awesome app - DroidCon Paris 2014
Ingredient of awesome app - DroidCon Paris 2014Ingredient of awesome app - DroidCon Paris 2014
Ingredient of awesome app - DroidCon Paris 2014
 
Framing the canvas - DroidCon Paris 2014
Framing the canvas - DroidCon Paris 2014Framing the canvas - DroidCon Paris 2014
Framing the canvas - DroidCon Paris 2014
 
Deep dive into android restoration - DroidCon Paris 2014
Deep dive into android restoration - DroidCon Paris 2014Deep dive into android restoration - DroidCon Paris 2014
Deep dive into android restoration - DroidCon Paris 2014
 
Archos Android based connected home solution - DroidCon Paris 2014
Archos Android based connected home solution - DroidCon Paris 2014Archos Android based connected home solution - DroidCon Paris 2014
Archos Android based connected home solution - DroidCon Paris 2014
 
Porting VLC on Android - DroidCon Paris 2014
Porting VLC on Android - DroidCon Paris 2014Porting VLC on Android - DroidCon Paris 2014
Porting VLC on Android - DroidCon Paris 2014
 
Robotium vs Espresso: Get ready to rumble ! - DroidCon Paris 2014
Robotium vs Espresso: Get ready to rumble ! - DroidCon Paris 2014Robotium vs Espresso: Get ready to rumble ! - DroidCon Paris 2014
Robotium vs Espresso: Get ready to rumble ! - DroidCon Paris 2014
 
Buildsystem.mk - DroidCon Paris 2014
Buildsystem.mk - DroidCon Paris 2014Buildsystem.mk - DroidCon Paris 2014
Buildsystem.mk - DroidCon Paris 2014
 
maximize app engagement and monetization - DroidCon Paris 2014
maximize app engagement and monetization - DroidCon Paris 2014maximize app engagement and monetization - DroidCon Paris 2014
maximize app engagement and monetization - DroidCon Paris 2014
 
Using the android ndk - DroidCon Paris 2014
Using the android ndk - DroidCon Paris 2014Using the android ndk - DroidCon Paris 2014
Using the android ndk - DroidCon Paris 2014
 
Holo material design transition - DroidCon Paris 2014
Holo material design transition - DroidCon Paris 2014Holo material design transition - DroidCon Paris 2014
Holo material design transition - DroidCon Paris 2014
 
Death to passwords - DroidCon Paris 2014
Death to passwords - DroidCon Paris 2014Death to passwords - DroidCon Paris 2014
Death to passwords - DroidCon Paris 2014
 
Google glass droidcon - DroidCon Paris 2014
Google glass droidcon - DroidCon Paris 2014Google glass droidcon - DroidCon Paris 2014
Google glass droidcon - DroidCon Paris 2014
 
Embedded webserver implementation and usage - DroidCon Paris 2014
Embedded webserver implementation and usage - DroidCon Paris 2014Embedded webserver implementation and usage - DroidCon Paris 2014
Embedded webserver implementation and usage - DroidCon Paris 2014
 
Petit design Grande humanité par Geoffrey Dorne - DroidCon Paris 2014
Petit design Grande humanité par Geoffrey Dorne - DroidCon Paris 2014Petit design Grande humanité par Geoffrey Dorne - DroidCon Paris 2014
Petit design Grande humanité par Geoffrey Dorne - DroidCon Paris 2014
 
What's new in android 4.4 - Romain Guy & Chet Haase
What's new in android 4.4 - Romain Guy & Chet HaaseWhat's new in android 4.4 - Romain Guy & Chet Haase
What's new in android 4.4 - Romain Guy & Chet Haase
 
Efficient Image Processing - Nicolas Roard
Efficient Image Processing - Nicolas RoardEfficient Image Processing - Nicolas Roard
Efficient Image Processing - Nicolas Roard
 
Build a user experience by Eyal Lezmy
Build a user experience by Eyal LezmyBuild a user experience by Eyal Lezmy
Build a user experience by Eyal Lezmy
 
Projet aad v2 gefco - DroidCon Paris 18 june 2013
Projet aad v2   gefco  - DroidCon Paris 18 june 2013Projet aad v2   gefco  - DroidCon Paris 18 june 2013
Projet aad v2 gefco - DroidCon Paris 18 june 2013
 
Thinking cpu & memory - DroidCon Paris 18 june 2013
Thinking cpu & memory - DroidCon Paris 18 june 2013Thinking cpu & memory - DroidCon Paris 18 june 2013
Thinking cpu & memory - DroidCon Paris 18 june 2013
 
Rho mobile v4 - DroidCon Paris 18 june 2013
Rho mobile v4 - DroidCon Paris 18 june 2013Rho mobile v4 - DroidCon Paris 18 june 2013
Rho mobile v4 - DroidCon Paris 18 june 2013
 

Recently uploaded

Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
panagenda
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Victor Rentea
 

Recently uploaded (20)

Exploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with MilvusExploring Multimodal Embeddings with Milvus
Exploring Multimodal Embeddings with Milvus
 
Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024Manulife - Insurer Transformation Award 2024
Manulife - Insurer Transformation Award 2024
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Why Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire businessWhy Teams call analytics are critical to your entire business
Why Teams call analytics are critical to your entire business
 
Ransomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdfRansomware_Q4_2023. The report. [EN].pdf
Ransomware_Q4_2023. The report. [EN].pdf
 
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot TakeoffStrategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
Strategize a Smooth Tenant-to-tenant Migration and Copilot Takeoff
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
Apidays New York 2024 - The Good, the Bad and the Governed by David O'Neill, ...
 
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUKSpring Boot vs Quarkus the ultimate battle - DevoxxUK
Spring Boot vs Quarkus the ultimate battle - DevoxxUK
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
Cyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdfCyberprint. Dark Pink Apt Group [EN].pdf
Cyberprint. Dark Pink Apt Group [EN].pdf
 
Corporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptxCorporate and higher education May webinar.pptx
Corporate and higher education May webinar.pptx
 
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin WoodPolkadot JAM Slides - Token2049 - By Dr. Gavin Wood
Polkadot JAM Slides - Token2049 - By Dr. Gavin Wood
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
Modular Monolith - a Practical Alternative to Microservices @ Devoxx UK 2024
 
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
Apidays New York 2024 - Passkeys: Developing APIs to enable passwordless auth...
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 

The.death.of.the.refresh.button mathieu.calba - DroidCon Paris 2014

  • 1. The Death of the Refresh Button
  • 3.
  • 4.
  • 5. Once upon a time…
  • 6. Julien was searching for an app to manage his bank account
  • 7. But he couldn’t find the one that meets his needs
  • 9. They always needed an internet connection
  • 10. Even just to check his leisure budget
  • 12. He needed a simple debit/credit managing app with a good backup
  • 13. So he decided to create his own app, focused on user experience and simplicity
  • 14. What does his app need?
  • 15. What does his app need? A great app backend
  • 16. Mobile application UI App backend API Server
  • 17. With a great app backend, you have more flexibility at the UI level
  • 19. What is syncing? Executing a BACKGROUND JOB to update local data WHEN APPROPRIATE
  • 20. BACKGROUND JOB • Non-user facing operation • Upload and/or download • Requires network connectivity • Always leave the data in good state
  • 21. What is syncing? Executing a BACKGROUND JOB to update local data WHEN APPROPRIATE
  • 22. WHEN APPROPRIATE aka scheduling & triggering • Only when needed, and when relevant (network availability, battery level, etc) • Restore after reboot • Exponential back-off • Be careful with battery life • etc.
  • 23. WHEN APPROPRIATE aka scheduling & triggering • Best solution: push message • Not always available, fallback: periodic polling
  • 24. What is syncing? Executing a BACKGROUND JOB to update local data WHEN APPROPRIATE
  • 25. How?
  • 28. AsyncTask & AlarmManager Create your AsyncTask: ! public final class PeriodicTaskRunnable extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { // TODO the task ! return null; } } Start the periodic run with the AlarmManager: private void triggerPeriodicAsyncTask() { final Intent intent = new Intent(this, PeriodicTaskReceiver.class); final PendingIntent receiverPendingIntent = PendingIntent. getBroadcast(this, 140916, intent, PendingIntent.FLAG_UPDATE_CURRENT); final AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, // SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HOUR, // AlarmManager.INTERVAL_HOUR, // receiverPendingIntent); }
  • 29. Create your BroadcastReceiver: ! public class PeriodicTaskReceiver extends BroadcastReceiver { public static final String ACTION_SYNC = “com.mathieucalba.tasks.ACTION_SYNC"; @Override public void onReceive(Context context, Intent intent) { if (ACTION_SYNC.equals(intent.getAction())) { new PeriodicTaskRunnable().execute(); } } } And declare it in the Manifest: ! <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.testjobscheduler"> <application> <receiver android:name=".tasks.PeriodicTaskReceiver" android:exported="false"> <intent-filter> <action android:name="com.mathieucalba.tasks.ACTION_SYNC" /> </intent-filter> </receiver> </application> </manifest> AsyncTask & AlarmManager
  • 30. AsyncTask & AlarmManager • AsyncTask & Receiver can be replaced by an IntentService for simplicity • Restoring periodic sync after a reboot? • Handling connectivity availability? • Indicate sync state to others • etc.
  • 32. Definition One method to do the UPLOAD & DOWNLOAD SYNC (SyncAdapter) for one DATA PROVIDER (ContentProvider) associated with a USER ACCOUNT (AccountAuthenticator) which can be TRIGGERED MANUALLY or by the SYSTEM.
  • 33. public final class AccountAuthenticator extends AbstractAccountAuthenticator { public static final String ACCOUNT_TYPE = "com.mathieucalba.testjobscheduler"; public static final String ACCOUNT_NAME_SYNC = "com.mathieucalba.testjobscheduler"; public AccountAuthenticator(Context context) { super(context); } @Override public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { return null; } @Override public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { throw new UnsupportedOperationException(); } //… } 1- Account
  • 34. public final class AccountAuthenticator extends AbstractAccountAuthenticator { //… @Override public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public String getAuthTokenLabel(String authTokenType) { throw new UnsupportedOperationException(); } @Override public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { throw new UnsupportedOperationException(); } @Override public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { throw new UnsupportedOperationException(); } } 1- Account
  • 35. Bind the AccountAuthenticator to the framework: ! public final class AccountAuthenticatorService extends Service { private static final Object LOCK = new Object(); private static AccountAuthenticator sAuthenticator; @Override public void onCreate() { super.onCreate(); synchronized (LOCK) { if (sAuthenticator == null) { sAuthenticator = new AccountAuthenticator(getApplicationContext()); } } } @Override public IBinder onBind(Intent intent) { synchronized (LOCK) { return sAuthenticator.getIBinder(); } } } 1- Account
  • 36. Declare your AccountAuthenticatorService into the Manifest: ! <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.testsyncadapter"> <application> <service android:exported="true" android:name=".accounts.AccountAuthenticatorService"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/account_authenticator" /> </service> </application> </manifest> ! Configure your AccountAuthenticator: ! <?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/config_accountType" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:smallIcon="@drawable/ic_launcher" /> 1- Account
  • 37. Add the Account required by the framework: ! private void initAccountAuthenticator() { final AccountManager accountManager = AccountManager.get(this); final Account[] accounts = accountManager.getAccountsByType(AccountAuthenticator.ACCOUNT_TYPE); for (Account account : accounts) { if (AccountAuthenticator.ACCOUNT_NAME_SYNC.equals(account.name)) { return; } } accountManager.addAccountExplicitly(new Account(AccountAuthenticator.ACCOUNT_NAME_SYNC, AccountAuthenticator.ACCOUNT_TYPE), null, null); } 1- Account For more info on implementing an AccountAuthenticator: http:// udinic.wordpress.com/2013/04/24/write-your-own-android-authenticator/
  • 38. Create a stub ContentProvider: ! public class StubProvider extends ContentProvider { @Override public boolean onCreate() { return true; } @Override public String getType(Uri uri) { return new String(); } @Override public Cursor query(Uri uri, String[] strings, String s, String[] strings2, String s2) { return null; } @Override public Uri insert(Uri uri, ContentValues contentValues) { return null; } @Override public int update(Uri uri, ContentValues contentValues, String s, String[] strings) { return 0; } @Override public int delete(Uri uri, String s, String[] strings) { return 0; } } 2- ContentProvider
  • 39. Declare your ContentProvider in the Manifest: ! <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.testsyncadapter"> <application> <provider android:authorities="com.mathieucalba.testsyncadapter.provider.stub" android:exported="false" android:name=".provider.StubProvider" android:syncable="true" /> </application> </manifest> 2- ContentProvider For more details about ContentProviders, see my slides at http://bit.ly/ContentProvider
  • 40. Implement the synchronization mechanism: ! public class SyncAdapter extends AbstractThreadedSyncAdapter { public SyncAdapter(Context context) { super(context, false, false); // Context, auto initialize, parallel sync } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { // TODO the sync } } 3- SyncAdapter
  • 41. Bind the SyncAdapter to the framework: ! public class SyncAdapterService extends Service { private static final Object LOCK = new Object(); private static SyncAdapter sSyncAdapter = null; @Override public void onCreate() { super.onCreate(); synchronized (LOCK) { if (sSyncAdapter == null) { sSyncAdapter = new SyncAdapter(getApplicationContext()); } } } @Override public IBinder onBind(Intent intent) { synchronized (LOCK) { return sSyncAdapter.getSyncAdapterBinder(); } } } 3- SyncAdapter
  • 42. Declare your SyncAdapterService into the Manifest: ! <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.testsyncadapter"> <application> <service android:exported="true" android:name=".sync.SyncAdapterService"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/sync_adapter" /> </service> </application> </manifest> 3- SyncAdapter
  • 43. 3- SyncAdapter Configure your SyncAdapter: ! <?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/config_accountType" android:allowParallelSyncs="false" android:contentAuthority="com.mathieucalba.testsyncadapter.provider.stub" android:isAlwaysSyncable="true" android:supportsUploading="true" android:userVisible="false" />
  • 44. How to trigger a one time sync: ! private static void triggerSyncAdapter(Account account, boolean now) { final Bundle extras = new Bundle(); if (now) { // ignore backoff && settings extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); // put the request at the front of the queue extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); } ContentResolver.requestSync(account, StubProvider.AUTHORITY, extras); } 4- Triggering
  • 45. How to configure an account for periodic sync, auto sync when network is up: ! public static void setupSync(Account account) { if (account == null) { return; } // Inform the system that this account supports sync ContentResolver.setIsSyncable(account, StubProvider.AUTHORITY, 1); // Inform the system that this account is eligible for auto sync when the network is up ContentResolver.setSyncAutomatically(account, StubProvider.AUTHORITY, true); // Recommend a schedule for automatic synchronisation. The system may modify this based // on other scheduled syncs and network utilisation. ContentResolver.addPeriodicSync(account, // StubProvider.AUTHORITY, // new Bundle(), // TimeUnit.HOURS.toSeconds(1)); } 4- Triggering ! Periodic & automatic sync doesn’t works if user has deactivated it
  • 46. 5- ContentProvider Triggering One neat functionality: run the SyncAdapter when ContentProvider’s data changes
  • 47. Mark the item to be deleted instead of deleting it: ! @Override public int delete(Uri uri, String s, String[] strings) { final int count; if (StubContract.hasNeedSyncToNetworkParameter(uri)) { final int match = URI_MATCHER.match(uri); switch (match) { case OPTION_ID: final ContentValues values = new ContentValues(); values.put(SyncColumns.SYNC_DELETED, SyncColumns.SYNC_DELETED_MARKED); count = buildSimpleSelection(uri). where(selection, selectionArgs). update(getDatabaseHelper().getWritableDatabase(), values); break; default: throw new SyncToNetworkUnknownUriException(uri); } } else { count = buildSimpleSelection(uri). where(selection, selectionArgs). delete(getDatabaseHelper().getWritableDatabase()); } if (count > 0) { notifyChange(uri); } return count; } 5- ContentProvider Triggering
  • 48. Notify the change to the SyncAdapter: ! private void notifyChange(Uri uri) { getContext().getContentResolver(). notifyChange(uri, null, isCallerUriUploadReady(uri) && !isCallerSyncAdapter(uri)); } public boolean isCallerSyncAdapter(Uri uri) { return StubContract.hasCallerIsSyncAdapterParameter(uri); } private boolean isCallerUriUploadReady(Uri uri) { if (StubContract.hasNeedSyncToNetworkParameter(uri)) { final int match = URI_MATCHER.match(uri); switch (match) { case OPTION_ID: return true; default: throw new SyncToNetworkUnknownUriException(uri); } } return false; } 5- ContentProvider Triggering
  • 49. How we detect an URI if from the SyncAdapter, and when a SyncAdapter trigger is needed: ! interface SyncExtras { String IS_CALLER_SYNC_ADAPTER = "is_caller_sync_adapter"; String NEED_SYNC_TO_NETWORK = "need_sync_to_network"; } public static Uri addCallerIsSyncAdapterParameter(Uri uri) { return uri.buildUpon().appendQueryParameter(SyncExtras.IS_CALLER_SYNC_ADAPTER, Boolean.toString(true)).build(); } public static boolean hasCallerIsSyncAdapterParameter(Uri uri) { final String parameter = uri.getQueryParameter(SyncExtras.IS_CALLER_SYNC_ADAPTER); return parameter != null && Boolean.parseBoolean(parameter); } public static Uri addNeedSyncToNetworkParameter(Uri uri) { return uri.buildUpon().appendQueryParameter(SyncExtras.NEED_SYNC_TO_NETWORK, Boolean.toString(true)).build(); } public static boolean hasNeedSyncToNetworkParameter(Uri uri) { final String parameter = uri.getQueryParameter(SyncExtras.NEED_SYNC_TO_NETWORK); return parameter != null && Boolean.parseBoolean(parameter); } 5- ContentProvider Triggering
  • 50. 5- ContentProvider Triggering Be careful, your corresponding query should not return the deleted data if it’s not the SyncAdapter querying.
  • 51. SyncAdapter • Google way • Easily triggered by the system when appropriate (network availability, change in associated ContentProvider) • Needs an AccountAuthenticator (at least a stub) • Needs a ContentProvider (at least a stub)
  • 53. Definition SCHEDULE the execution of a JOB (via a Service) with VARIOUS PARAMETERS about WHEN the execution SHOULD HAPPEN (during a window of time, periodically, with network needed, etc.)
  • 54. The JobService is where we are awaken: ! public class MyJobService extends JobService { private ExecutorService mExecutor; private final Handler mHandler = new Handler(Looper.getMainLooper()); @Override public void onCreate() { super.onCreate(); mExecutor = Executors.newSingleThreadExecutor(); } @Override public void onDestroy() { mExecutor.shutdown(); super.onDestroy(); } @Override public boolean onStartJob(JobParameters jobParameters) { // We are on the Main thread, so post the Task to a background thread mExecutor.execute(new Task(jobParameters)); return true; } @Override public boolean onStopJob(JobParameters jobParameters) { // TODO interrupt Task return true; } } 1- JobService
  • 55. private final class Task implements Runnable { private final JobParameters mJobParameters; private Task(JobParameters jobParameters) { mJobParameters = jobParameters; } @Override public void run() { // TODO the network call mHandler.post(new FinishedTask(mJobParameters, true)); } } private final class FinishedTask implements Runnable { private final JobParameters mJobParameters; private final boolean mIsSuccess; private FinishedTask(JobParameters jobParameters, boolean isSuccess) { mJobParameters = jobParameters; mIsSuccess = isSuccess; } @Override public void run() { // Notify that the job has ended jobFinished(mJobParameters, mIsSuccess); } } 1- JobService
  • 56. Declare your MyJobService into the Manifest: ! <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.testjobscheduler"> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <application> <service android:name=".tasks.MyJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"/> </application> </manifest> 1- JobService
  • 57. 2- Triggering Exemple for triggering a one time Job: ! JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_1, new ComponentName(this, MyJobService.class)). setBackoffCriteria(TimeUnit.MINUTES.toMillis(1), JobInfo.BackoffPolicy.EXPONENTIAL). setMinimumLatency(TimeUnit.SECONDS.toMillis(5)). setOverrideDeadline(TimeUnit.HOURS.toMillis(1)). setRequiredNetworkCapabilities(JobInfo.NetworkType.ANY); ((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(builder.build()); ! Exemple for triggering a periodic Job: ! builder = new JobInfo.Builder(JOB_ID_PERIODIC, new ComponentName(this, MyJobService.class)). setPeriodic(TimeUnit.HOURS.toMillis(1)). setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED). setRequiresDeviceIdle(true). setRequiresCharging(true); ((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(builder.build());
  • 58. JobScheduler • Configurable scheduling (idle-mode, network availability, etc) across all the system • Simple, based upon a Service • Persisted state • Android-L+ only
  • 60. Conclusion • SyncAdapter perfect if account and ContentProvider, great otherwise but can be tricky • JobScheduler very promising, but Android L+ only, a limited compat library would help spread its use (based upon Service & AlarmManager).
  • 61. Conclusion • We have the tools to create an app the works seamlessly without an internet connection so the user never have to worry about his internet connection
  • 63. Credits • Up & Down icons by Guillaume Berry • The Search Underneath the Bed by Arielle Nadel • Yorkshire Moors Milky Way 3 by Matthew Savage • Injured Piggy Bank With Crutches by Ken Teegardin • No Internet by Marcelo Graciolli • Balancing The Account By Hand by Ken Teegardin • Rage by Amy McTigue • Daniel Foster by Online Shopping • 3D Bright Idea by Chris Potter • Landscape by Ben Simo • Christmas Eve Sunrise by Jay Parker