$30 off During Our Annual Pro Sale. View Details »

Mode offline : Notre application Android fonctionne au niveau -5 d’un parking

Mode offline : Notre application Android fonctionne au niveau -5 d’un parking

Votre application Android fonctionne-t-elle parfaitement en mode offline ?

Pour Virtuo, c’est indispensable. Virtuo est la startup française qui propose une nouvelle expérience de location de voiture. Tout se fait depuis un mobile : la création de compte, la réservation, l’état des lieux et l’ouverture de la voiture. Grâce à ça, plus de file au guichet pour récupérer sa clé. Parce qu’on n’a pas peur des challenges, nos clients n’ont jamais de clé et utilisent leur smartphone ou leur smartwatch pour ouvrir et démarrer leur véhicule pendant toute la durée de la location. Et ça fonctionne, puisque Virtuo totalise déjà plus de 7.000 jours de locations qui commencent toujours de la même manière : dans le sous-sol d’un parking, sans accès à la 3G.

Au programme : BLE : La seule solution rapide et vraiment offline Cache HTTP : Connaissez vous le header max-stale Synchronisation et conflits : Quelle stratégie adopter Programmation réactive : Pourquoi elle nous a aidé à nous améliorer UX/Design : Comment un bandeau permet d’améliorer l'expérience Stratégie de cache en fonction du contexte : Pourquoi ne peut-on pas appliquer la même stratégie avant, pendant ou après une réservation

Mathieu Hausherr

April 11, 2017
Tweet

More Decks by Mathieu Hausherr

Other Decks in Technology

Transcript

  1. #VirtuoOffline #AndroidMakers
    AndroidMakers
    Mathieu Hausherr @mhausherr
    Romain Potier @potierromain
    1

    View Slide

  2. #VirtuoOffline #AndroidMakers
    Mode offline
    Notre application fonctionne au
    niveau -5 d’un parking
    2

    View Slide

  3. #VirtuoOffline #AndroidMakers 3

    View Slide

  4. #VirtuoOffline #AndroidMakers 4

    View Slide

  5. #VirtuoOffline #AndroidMakers 5

    View Slide

  6. #VirtuoOffline #AndroidMakers 6

    View Slide

  7. #VirtuoOffline #AndroidMakers 7

    View Slide

  8. #VirtuoOffline #AndroidMakers 8

    View Slide

  9. #VirtuoOffline #AndroidMakers 9

    View Slide

  10. #VirtuoOffline #AndroidMakers 10

    View Slide

  11. #VirtuoOffline #AndroidMakers 11

    View Slide

  12. #VirtuoOffline #AndroidMakers 12

    View Slide

  13. #VirtuoOffline #AndroidMakers 13

    View Slide

  14. #VirtuoOffline #AndroidMakers 14
    Version 0.0.1-alpha

    View Slide

  15. #VirtuoOffline #AndroidMakers 15

    View Slide

  16. #VirtuoOffline #AndroidMakers 16
    Plus jamais
    ça !

    View Slide

  17. #VirtuoOffline #AndroidMakers 17
    Cache Http
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Type: application/json; charset=utf-8
    Date: Thu, 23 Feb 2017 16:31:56 GMT
    Cache-Control: max-age=600
    Cache-Control: max-stale=2419200

    View Slide

  18. #VirtuoOffline #AndroidMakers 18
    Cache Http
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Type: application/json; charset=utf-8
    Date: Thu, 23 Feb 2017 16:31:56 GMT
    Cache-Control: max-age=600
    Cache-Control: max-stale=2419200

    View Slide

  19. #VirtuoOffline #AndroidMakers 19
    Cache Http
    Connection: keep-alive
    Content-Encoding: gzip
    Content-Type: application/json; charset=utf-8
    Date: Thu, 23 Feb 2017 16:31:56 GMT
    Cache-Control: max-age=600
    Cache-Control: max-stale=2419200

    View Slide

  20. #VirtuoOffline #AndroidMakers 20
    Cache Http
    Download
    Cache
    Expiration
    max-age max-stale
    Real cache
    Expiration

    View Slide

  21. #VirtuoOffline #AndroidMakers 21
    OkHttp Interceptor
    public class VInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws
    IOException {
    Request.Builder builder =
    chain.request().newBuilder();
    return chain.proceed(builder.build());
    }
    }

    View Slide

  22. #VirtuoOffline #AndroidMakers 22
    OkHttp Interceptor
    if (isNetworkAvailable && !forceCache) {

    } else {
    addCacheControl(builder);
    response = chain.proceed(builder.build());
    }

    View Slide

  23. #VirtuoOffline #AndroidMakers 23
    OkHttp Interceptor
    if (isNetworkAvailable && !forceCache) {
    try {
    response = chain.proceed(builder.build());
    } catch (Exception e) {
    if (GET.equals(chain.request().method()) {
    addCacheControl(builder);
    response = chain.proceed(builder.build());
    } else {
    throw e;
    }
    }
    }

    View Slide

  24. #VirtuoOffline #AndroidMakers 24
    OkHttp Interceptor
    if (isNetworkAvailable && !forceCache) {
    try {
    response = chain.proceed(builder.build());
    } catch (Exception e) {
    if (GET.equals(chain.request().method()) {
    addCacheControl(builder);
    response = chain.proceed(builder.build());
    } else {
    throw e;
    }
    }
    }

    View Slide

  25. #VirtuoOffline #AndroidMakers 25
    OkHttp Interceptor
    if (isNetworkAvailable && !forceCache) {
    try {
    response = chain.proceed(builder.build());
    } catch (Exception e) {
    if (GET.equals(chain.request().method()) {
    addCacheControl(builder);
    response = chain.proceed(builder.build());
    } else {
    throw e;
    }
    }
    }

    View Slide

  26. #VirtuoOffline #AndroidMakers 26
    OkHttp Interceptor
    void addCacheControl(Request.Builder builder) {
    builder.cacheControl(new CacheControl.Builder()
    .onlyIfCached()
    .maxStale(28, TimeUnit.DAYS)
    .build());
    }

    View Slide

  27. #VirtuoOffline #AndroidMakers 27

    View Slide

  28. #VirtuoOffline #AndroidMakers 28
    Chuck Interceptor
    OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new ChuckInterceptor(context))
    .addInterceptor(new VInterceptor(context))
    .build();

    View Slide

  29. #VirtuoOffline #AndroidMakers 29
    Bien joué
    les gars !

    View Slide

  30. #VirtuoOffline #AndroidMakers 30
    Version 1.0.0

    View Slide

  31. #VirtuoOffline #AndroidMakers 31

    View Slide

  32. #VirtuoOffline #AndroidMakers 32
    Déclaration la plus rapide possible, image avec sha1
    POST /inspection
    [{

    "created_at": …,

    "zone_id": …,

    "image_hash": …

    }]

    View Slide

  33. #VirtuoOffline #AndroidMakers 33

    View Slide

  34. #VirtuoOffline #AndroidMakers 34
    JobScheduler sur Android
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public class InspectionJobService extends JobService
    {
    @Override public boolean onStartJob(final
    JobParameters params) {}
    @Override public boolean onStopJob(JobParameters
    params) {}
    }

    View Slide

  35. #VirtuoOffline #AndroidMakers 35
    JobScheduler sur Android
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public class InspectionJobService extends JobService
    {
    @Override public boolean onStartJob(final
    JobParameters params) {}
    @Override public boolean onStopJob(JobParameters
    params) {}
    }

    View Slide

  36. #VirtuoOffline #AndroidMakers 36
    JobScheduler sur Android, Notation de la location
    @Override public boolean onStartJob(final
    JobParameters params) {
    inspectionService.sendReport(map).subscribeOn(…).observeOn(…)
    .subscribe((Action1) (bookingResult) -> {
    jobFinished(params, false);
    }, (throwable) -> {
    jobFinished(params,
    NetworkUtil.noNetworkError(throwable));
    }
    );

    View Slide

  37. #VirtuoOffline #AndroidMakers 37
    JobScheduler sur Android, Notation de la location
    @Override public boolean onStartJob(final
    JobParameters params) {
    inspectionService.sendReport(map).subscribeOn(…).observeOn(…)
    .subscribe((Action1) (bookingResult) -> {
    jobFinished(params, false);
    }, (throwable) -> {
    jobFinished(params,
    NetworkUtil.noNetworkError(throwable));
    }
    );

    View Slide

  38. #VirtuoOffline #AndroidMakers 38
    JobScheduler sur Android, Notation de la location
    @Override public boolean onStartJob(final
    JobParameters params) {
    inspectionService.sendReport(map).subscribeOn(…).observeOn(…)
    .subscribe((Action1) (bookingResult) -> {
    jobFinished(params, false);
    }, (throwable) -> {
    jobFinished(params,
    NetworkUtil.noNetworkError(throwable));
    }
    );

    View Slide

  39. #VirtuoOffline #AndroidMakers 39
    JobScheduler sur Android, Ajout dans le scheduler
    PersistableBundle persistableBundle = new
    PersistableBundle();
    persistableBundle.putString(…);
    JobScheduler scheduler = (JobScheduler)
    getActivity().getSystemService(Context.JOB_SCHED
    ULER_SERVICE);

    View Slide

  40. #VirtuoOffline #AndroidMakers 40
    JobScheduler sur Android, Ajout dans le scheduler
    PersistableBundle persistableBundle = new
    PersistableBundle();
    persistableBundle.putString(…);
    JobScheduler scheduler = (JobScheduler)
    getActivity().getSystemService(Context.JOB_SCHED
    ULER_SERVICE);

    View Slide

  41. #VirtuoOffline #AndroidMakers 41
    JobScheduler sur Android, Ajout dans le scheduler
    JobInfo jobInfo = new JobInfo.Builder(99,
    new ComponentName(context,
    InspectionJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setPersisted(true)
    .setExtras(persistableBundle)
    .build();
    scheduler.schedule(jobInfo);

    View Slide

  42. #VirtuoOffline #AndroidMakers 42
    JobScheduler sur Android, Ajout dans le scheduler
    JobInfo jobInfo = new JobInfo.Builder(99,
    new ComponentName(context,
    InspectionJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setPersisted(true)
    .setExtras(persistableBundle)
    .build();
    scheduler.schedule(jobInfo);

    View Slide

  43. #VirtuoOffline #AndroidMakers 43
    JobScheduler sur Android, Ajout dans le scheduler
    JobInfo jobInfo = new JobInfo.Builder(99,
    new ComponentName(context,
    InspectionJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setPersisted(true)
    .setExtras(persistableBundle)
    .build();
    scheduler.schedule(jobInfo);

    View Slide

  44. #VirtuoOffline #AndroidMakers 44
    JobScheduler sur Android, Ajout dans le scheduler
    JobInfo jobInfo = new JobInfo.Builder(99,
    new ComponentName(context,
    InspectionJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .setPersisted(true)
    .setExtras(persistableBundle)
    .build();
    scheduler.schedule(jobInfo);

    View Slide

  45. #VirtuoOffline #AndroidMakers 45
    Version 1.1.0

    View Slide

  46. #VirtuoOffline #AndroidMakers 46

    View Slide

  47. #VirtuoOffline #AndroidMakers 47

    View Slide

  48. #VirtuoOffline #AndroidMakers 48

    View Slide

  49. #VirtuoOffline #AndroidMakers 49

    View Slide

  50. #VirtuoOffline #AndroidMakers 50
    Fetch de Picasso
    new Picasso.Builder(context)
    .build()
    .load(imageUrl)
    .fetch(callback);

    View Slide

  51. #VirtuoOffline #AndroidMakers 51
    Premiers retours utilisateurs

    View Slide

  52. #VirtuoOffline #AndroidMakers 52

    View Slide

  53. #VirtuoOffline #AndroidMakers 53

    View Slide

  54. #VirtuoOffline #AndroidMakers 54

    View Slide

  55. #VirtuoOffline #AndroidMakers 55

    View Slide

  56. #VirtuoOffline #AndroidMakers 56
    Optimiser le code

    View Slide

  57. #VirtuoOffline #AndroidMakers 57
    Programmation réactive, clés partenaire
    Mise à jour
    du serveur
    Téléchargement
    des photos
    Téléchargement
    de la clé
    Mise à jour d’un
    compteur

    View Slide

  58. #VirtuoOffline #AndroidMakers 58
    Programmation réactive, clés partenaire
    Mise à jour
    du serveur
    Téléchargement
    des photos
    Téléchargement
    de la clé
    Mise à jour d’un
    compteur
    Mise à jour
    du serveur
    Téléchargement
    des photos
    Téléchargement
    de la clé
    Mise à jour d’un
    compteur
    Mise à jour
    du serveur
    Téléchargement
    des photos
    Téléchargement
    de la clé
    Mise à jour d’un
    compteur

    View Slide

  59. #VirtuoOffline #AndroidMakers 59
    Programmation réactive, clés partenaire
    Observable.from(Lists.newArrayList(id1, id2, id3)).subscribeOn(…)
    .flatMap((Func1) (interventionId) -> {
    return interventionService.create(param); })
    .flatMap((Func1) (interventionResult) -> {
    return downloadDamagesPictures(interventionResult.damages);})
    .flatMap((Func1) (result) -> {
    return carConnector.downloadKey(…); })

    View Slide

  60. #VirtuoOffline #AndroidMakers 60
    Programmation réactive, clés partenaire
    Observable.from(Lists.newArrayList(id1, id2, id3)).subscribeOn(…)
    .flatMap((Func1) (interventionId) -> {
    return interventionService.create(param); })
    .flatMap((Func1) (interventionResult) -> {
    return downloadDamagesPictures(interventionResult.damages);})
    .flatMap((Func1) (result) -> {
    return carConnector.downloadKey(…); })

    View Slide

  61. #VirtuoOffline #AndroidMakers 61
    Programmation réactive, clés partenaire
    Observable.from(Lists.newArrayList(id1, id2, id3)).subscribeOn(…)
    .flatMap((Func1) (interventionId) -> {
    return interventionService.create(param); })
    .flatMap((Func1) (interventionResult) -> {
    return downloadDamagesPictures(interventionResult.damages);})
    .flatMap((Func1) (result) -> {
    return carConnector.downloadKey(…); })

    View Slide

  62. #VirtuoOffline #AndroidMakers 62
    Programmation réactive, clés partenaire
    Observable.from(Lists.newArrayList(id1, id2, id3)).subscribeOn(…)
    .flatMap((Func1) (interventionId) -> {
    return interventionService.create(param); })
    .flatMap((Func1) (interventionResult) -> {
    return downloadDamagesPictures(interventionResult.damages);})
    .flatMap((Func1) (result) -> {
    return carConnector.downloadKey(…); })

    View Slide

  63. #VirtuoOffline #AndroidMakers 63
    Programmation réactive, clés partenaire
    Observable.from(Lists.newArrayList(id1, id2, id3)).subscribeOn(…)
    .flatMap((Func1) (interventionId) -> {
    return interventionService.create(param); })
    .flatMap((Func1) (interventionResult) -> {
    return downloadDamagesPictures(interventionResult.damages);})
    .flatMap((Func1) (result) -> {
    return carConnector.downloadKey(…); })

    View Slide

  64. #VirtuoOffline #AndroidMakers 64
    Programmation réactive, clés partenaire
    .doOnNext((Action1) (aVoid) -> {
    updateView(++index); })
    .toList()
    .subscribe(
    (Action1) (aVoid) -> {okTransfert();},
    (throwable) -> {manageErrors(throwable);}
    );

    View Slide

  65. #VirtuoOffline #AndroidMakers 65
    Programmation réactive, clés partenaire
    .doOnNext((Action1) (aVoid) -> {
    updateView(++index); })
    .toList()
    .subscribe(
    (Action1) (aVoid) -> {okTransfert();},
    (throwable) -> {manageErrors(throwable);}
    );

    View Slide

  66. #VirtuoOffline #AndroidMakers 66
    Programmation réactive, clés partenaire
    .doOnNext((Action1) (aVoid) -> {
    updateView(++index); })
    .toList()
    .subscribe(
    (Action1) (aVoid) -> {okTransfert();},
    (throwable) -> {manageErrors(throwable);}
    );

    View Slide

  67. #VirtuoOffline #AndroidMakers 67
    Pour tester

    View Slide

  68. #VirtuoOffline #AndroidMakers 68
    Code promo
    ANDROID15

    View Slide

  69. #VirtuoOffline #AndroidMakers 69
    THANKS

    View Slide