Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Mode offline : Notre application mobile fonctio...

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

Votre application mobile 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 06, 2017
Tweet

More Decks by Mathieu Hausherr

Other Decks in Technology

Transcript

  1. #DevoxxFR #VirtuoDevoxx Cache Http 17 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
  2. #DevoxxFR #VirtuoDevoxx Cache Http 18 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
  3. #DevoxxFR #VirtuoDevoxx Cache Http 19 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
  4. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 21 public class VInterceptor implements Interceptor

    { @Override public Response intercept(Chain chain) throws IOException { Request.Builder builder = chain.request().newBuilder(); return chain.proceed(builder.build()); } }
  5. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 22 if (isNetworkAvailable && !forceCache) {

    … } else { addCacheControl(builder); response = chain.proceed(builder.build()); }
  6. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 23 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; } } }
  7. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 24 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; } } }
  8. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 25 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; } } }
  9. #DevoxxFR #VirtuoDevoxx OkHttp Interceptor 26 void addCacheControl(Request.Builder builder) { builder.cacheControl(new

    CacheControl.Builder() .onlyIfCached() .maxStale(28, TimeUnit.DAYS) .build()); }
  10. #DevoxxFR #VirtuoDevoxx Cache Swift func getData() -> Data? { try?

    Data(contentsOf: cacheURL) } func set(data: Data) { try? data.write(to: cacheURL, options: [.atomic]) } var isCacheStillValid: Bool { guard let attributes = try? FileManager.default.attributesOfItem(atPath: pathForCacheFile) else { return false } guard let modificationDate = attributes[FileAttributeKey.modificationDate] as? Date else { return false } return self.cacheDuration - modificationDate.timeIntervalSinceNow > 0 } 27
  11. #DevoxxFR #VirtuoDevoxx Cache Swift func getData() -> Data? { try?

    Data(contentsOf: cacheURL) } func set(data: Data) { try? data.write(to: cacheURL, options: [.atomic]) } var isCacheStillValid: Bool { guard let attributes = try? FileManager.default.attributesOfItem(atPath: pathForCacheFile) else { return false } guard let modificationDate = attributes[FileAttributeKey.modificationDate] as? Date else { return false } return self.cacheDuration - modificationDate.timeIntervalSinceNow > 0 } 28
  12. #DevoxxFR #VirtuoDevoxx Cache Swift switch cacheMode { case .standard: if

    !isNetworkReachable && readFromCache(handler) { return } case .forceCache: _ = readFromCache(handler); return case .twoStepLoading: _ = readFromCache(handler) } 29
  13. #DevoxxFR #VirtuoDevoxx Cache Swift switch cacheMode { case .standard: if

    !isNetworkReachable && readFromCache(handler) { return } case .forceCache: _ = readFromCache(handler); return case .twoStepLoading: _ = readFromCache(handler) } 30
  14. #DevoxxFR #VirtuoDevoxx Cache Swift switch cacheMode { case .standard: if

    !isNetworkReachable && readFromCache(handler) { return } case .forceCache: _ = readFromCache(handler); return case .twoStepLoading: _ = readFromCache(handler) } 31
  15. #DevoxxFR #VirtuoDevoxx Chuck Interceptor 33 OkHttpClient client = new OkHttpClient.Builder()

    .addInterceptor(new ChuckInterceptor(context)) .addInterceptor(new VInterceptor(context)) .build();
  16. #DevoxxFR #VirtuoDevoxx Déclaration la plus rapide possible, image avec sha1

    38 POST /inspection [{
 "created_at": …,
 "zone_id": …,
 "image_sha1": …
 }]
  17. #DevoxxFR #VirtuoDevoxx JobScheduler sur Android 40 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) public

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

    class InspectionJobService extends JobService { @Override public boolean onStartJob(final JobParameters params) {} @Override public boolean onStopJob(JobParameters params) {} }
  19. #DevoxxFR #VirtuoDevoxx JobScheduler sur Android, Notation de la location 42

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

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

    @Override public boolean onStartJob(final JobParameters params) { inspectionService.sendReport(map).subscribeOn(…).observeOn(…) .subscribe((Action1) (bookingResult) -> { jobFinished(params, false); }, (throwable) -> { jobFinished(params, NetworkUtil.noNetworkError(throwable)); } );
  22. #DevoxxFR #VirtuoDevoxx JobScheduler sur Android, Ajout dans le scheduler 45

    PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putString(…); JobScheduler scheduler = (JobScheduler) getActivity().getSystemService(Context.JOB_SCHED ULER_SERVICE);
  23. #DevoxxFR #VirtuoDevoxx JobScheduler sur Android, Ajout dans le scheduler 46

    PersistableBundle persistableBundle = new PersistableBundle(); persistableBundle.putString(…); JobScheduler scheduler = (JobScheduler) getActivity().getSystemService(Context.JOB_SCHED ULER_SERVICE);
  24. #DevoxxFR #VirtuoDevoxx JobScheduler sur Android, Ajout dans le scheduler 47

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

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

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

    JobInfo jobInfo = new JobInfo.Builder(99, new ComponentName(context, InspectionJobService.class)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) .setPersisted(true) .setExtras(persistableBundle) .build(); scheduler.schedule(jobInfo);
  28. #DevoxxFR #VirtuoDevoxx Image cache Swift class ImageCache { var inMemoryCache:

    [URL: UIImage] = [:] func image(forURL url: URL) -> UIImage? { if let image = inMemoryCache[url] { return image } if let image = readImageFromCache(url) { inMemoryCache[urlString] = image; return image } else { return nil } } } 58
  29. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 68 Mise

    à jour du serveur Mise à jour du cache Téléchargement de la clé 1 seul feedback utilisateur
  30. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 69 bookingService.update(params).subscribeOn(…).observeOn(…)

    .flatMap((Func1) (bookingResult) -> { return bookingService.list();}) .flatMap((Func1) (bookingListResult) -> { return carConnector.downloadKey(…); }) .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );
  31. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 70 bookingService.update(params).subscribeOn(…).observeOn(…)

    .flatMap((Func1) (bookingResult) -> { return bookingService.list();}) .flatMap((Func1) (bookingListResult) -> { return carConnector.downloadKey(…); }) .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );
  32. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 71 bookingService.update(params).subscribeOn(…).observeOn(…)

    .flatMap((Func1) (bookingResult) -> { return bookingService.list();}) .flatMap((Func1) (bookingListResult) -> { return carConnector.downloadKey(…); }) .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );
  33. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 72 bookingService.update(params).subscribeOn(…).observeOn(…)

    .flatMap((Func1) (bookingResult) -> { return bookingService.list();}) .flatMap((Func1) (bookingListResult) -> { return carConnector.downloadKey(…); }) .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );
  34. #DevoxxFR #VirtuoDevoxx Programmation réactive, transfert depuis le badge 73 bookingService.update(params).subscribeOn(…).observeOn(…)

    .flatMap((Func1) (bookingResult) -> { return bookingService.list();}) .flatMap((Func1) (bookingListResult) -> { return carConnector.downloadKey(…); }) .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );
  35. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 74 Mise à jour

    du serveur Téléchargement des photos Téléchargement de la clé Mise à jour d’un compteur
  36. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 75 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
  37. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 76 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(…); })
  38. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 77 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(…); })
  39. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 78 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(…); })
  40. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 79 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(…); })
  41. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 80 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(…); })
  42. #DevoxxFR #VirtuoDevoxx Programmation réactive, clés partenaire 81 .doOnNext((Action1) (aVoid) ->

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

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

    { updateView(++index); }) .toList() .subscribe( (Action1) (aVoid) -> {okTransfert();}, (throwable) -> {manageErrors(throwable);} );