Développer une app Android en 2015

Développer une app Android en 2015

Je développe sur Android depuis 2009, année de naissance de Cupcake, Donut et Eclair et époque où un écran 3.2” et une trackball étaient la norme. Depuis, l’écosystème Android a beaucoup évolué apportant son lot de nouveautés et de challenges. Nous verrons ensemble ce qui se cache derrière une application Android moderne : RxJava, Material Design, RecyclerView... seront notamment de la partie.

Talk donnée au Paris Android User Group le 03/03/2015 chez BlaBlaCar
http://www.meetup.com/Android-Paris/events/220665795/
https://www.youtube.com/watch?v=vJtDozdLEg8

B802469678e56fdee0a95c5e7f27d460?s=128

Florian Mierzejewski

March 03, 2015
Tweet

Transcript

  1. Développer une app Android en 2015

  2. Mais avant Petit retour en arrière…

  3. None
  4. Requêtes HTTP Parsing Gestion des données Exécution en background Gestion

    des crashs Design
  5. Florian Mierzejewski @florianmski

  6. None
  7. None
  8. Données Obtenir, Manipuler, Stocker

  9. Retrofit, OkHttp A ▪ Production

  10. OkHttp An HTTP & SPDY client for Android and Java

    applications • Support de HTTP/2 et SPDY • Support de GZIP • Réponses en cache
  11. Retrofit A type-safe REST client for Android and Java Utilisation

    d’annotations pour décrire les requêtes HTTP
  12. List<Comment> comments( String movieId, Integer page, Integer limit ); /movies/{id}/comments?page={page}&limit={limit}

  13. @GET("/movies/{id}/comments") List<Comment> comments( String movieId, Integer page, Integer limit );

  14. @GET("/movies/{id}/comments") List<Comment> comments( @Path("id") String movieId, Integer page, Integer limit

    ); /movies/{id}/comments
  15. @GET("/movies/{id}/comments") List<Comment> comments( @Path("id") String movieId, @Query("page") Integer page, @Query("limit")

    Integer limit ); /movies/{id}/comments?page={page}&limit={limit}
  16. public interface Movies { ! @GET("/movies/{id}/comments") List<Comment> comments(/*…*/); ! @GET("/movies/popular")

    List<Movie> popular(/*…*/); }
  17. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .build();

  18. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .build(); ! Movies movies

    = restAdapter.create(Movies.class);
  19. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .build(); ! Movies movies

    = restAdapter.create(Movies.class); ! List<Comment> comments = movies.comments(42, 1, 10);
  20. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .setConverter(new SimpleXMLConverter()) .build();

  21. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .setClient(new OkClient()) .build();

  22. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .setLogLevel(RestAdapter.LogLevel.FULL) .build();

  23. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .setErrorHandler(new MyErrorHandler()) .build();

  24. RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(“https://api-v2launch.trakt.tv”) .setRequestInterceptor(new RequestInterceptor() { @Override

    public void intercept(RequestFacade request) { request.addHeader(“trakt-api-key”, apiKey); } }) .build();
  25. @GET("/movies/{id}/comments") List<Comment> comments(/*…*/); ! @GET("/movies/{id}/comments") void comments(/*…*/, Callback<List<Comment>> cb); !

    @GET("/movies/{id}/comments") Response comments(/*…*/); ! @GET("/movies/{id}/comments") Observable<List<Comment>> comments(/*…*/);
  26. @GET("/movies/{id}/comments") List<Comment> comments(/*…*/); ! @GET("/movies/{id}/comments") void comments(/*…*/, Callback<List<Comment>> cb); !

    @GET("/movies/{id}/comments") Response comments(/*…*/); ! @GET("/movies/{id}/comments") Observable<List<Comment>> comments(/*…*/);
  27. @GET("/movies/{id}/comments") List<Comment> comments(/*…*/); ! @GET("/movies/{id}/comments") void comments(/*…*/, Callback<List<Comment>> cb); !

    @GET("/movies/{id}/comments") Response comments(/*…*/); ! @GET("/movies/{id}/comments") Observable<List<Comment>> comments(/*…*/);
  28. @GET("/movies/{id}/comments") List<Comment> comments(/*…*/); ! @GET("/movies/{id}/comments") void comments(/*…*/, Callback<List<Comment>> cb); !

    @GET("/movies/{id}/comments") Response comments(/*…*/); ! @GET("/movies/{id}/comments") Observable<List<Comment>> comments(/*…*/);
  29. RxJava A Netflix Original

  30. Implémentation des Reactive Extensions pour la JVM

  31. • Pas de dépendance • Jar < 700KB • Java

    6+ & Android 2.3+ • Support des lambdas de Java 8 • Polyglotte (Scala, Groovy, Clojure et Kotlin) • Exécution synchrone ou asynchrone
  32. Composer des programmes asynchrones et basés sur des évènements en

    utilisant des séquences observables
  33. Observable<T> Observer<T>

  34. public interface Observer<T> { ! public abstract void onNext(T t);

    ! public abstract void onCompleted(); ! public abstract void onError(Throwable e); ! }
  35. public interface Observer<T> { ! public abstract void onNext(T t);

    ! public abstract void onCompleted(); ! public abstract void onError(Throwable e); ! }
  36. public interface Observer<T> { ! public abstract void onNext(T t);

    ! public abstract void onCompleted(); ! public abstract void onError(Throwable e); ! }
  37. Créer un Observable

  38. Observable<T> Observable.just(T value)

  39. Observable<T> Observable.from(T… values)

  40. Observable<Integer> Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer>

    observer) { try { if (!observer.isUnsubscribed()) { for (int i = 1; i < 5; i++) { observer.onNext(i); } observer.onCompleted(); } } catch (Exception e) { observer.onError(e); } } } );
  41. S’abonner

  42. Observable<T> getObservable().subscribe(myObserver)

  43. Composer

  44. None
  45. Transformer Observable<R> map(Func1<T, R> func)

  46. Combiner merge(Observable<T> t1, Observable<T> t2)

  47. Filtrer Observable<T> distinct()

  48. Gestion des erreurs onErrorResumeNext(Observable<T> o)

  49. Et concrètement ?

  50. compile 'io.reactivex:rxandroid:0.23.0'

  51. Observable.just(1, 2, 3) .subscribe(new Subscriber<Integer>() { @Override public void onNext(Integer

    item) { System.out.println("Next: " + item); } ! @Override public void onError(Throwable error) { System.err.println("Error: " + error.getMessage()); } ! @Override public void onCompleted() { System.out.println("Sequence complete."); } });
  52. Observable.just(1, 2, 3) .subscribe(new Subscriber<Integer>() { @Override public void onNext(Integer

    item) { System.out.println("Next: " + item); } ! @Override public void onError(Throwable error) { System.err.println("Error: " + error.getMessage()); } ! @Override public void onCompleted() { System.out.println("Sequence complete."); } });
  53. Observable.just(1, 2, 3) .subscribe(new Subscriber<Integer>() { @Override public void onNext(Integer

    item) { System.out.println("Next: " + item); } ! @Override public void onError(Throwable error) { System.err.println("Error: " + error.getMessage()); } ! @Override public void onCompleted() { System.out.println("Sequence complete."); } });
  54. > Next: 1 > Next: 2 > Next: 3 >

    Sequence complete.
  55. Hum, un vrai exemple ?

  56. comments(42, 1, 10) .filter(c -> !c.spoiler) .toSortedList((c1, c2) -> Integer.compare(c2.likes,

    c1.likes)) .lift(flattenList()) .take(3) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(myObserver);
  57. createObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(myObserver);

  58. createObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(myObserver);

  59. createObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(myObserver);

  60. @Override public void onDestroy() { super.onDestroy(); ! subscription.unsubscribe(); }

  61. Mais aussi Les subjects Observables “hot” ou “cold” …

  62. ContentProvider Like a boss

  63. Pourquoi un ContentProvider ? ! • Système de notification des

    URIs • Reste assez “bas niveau” pour faire ce que l’on veut
  64. Ce qui est moins cool ! • Beaucoup de boilerplate

    • Difficile de ne pas faire d’erreurs
  65. Schematic à la rescousse ! ! • Génération du ContentProvider

    • Annotations, annotations everywhere • Permet de se concentrer sur le schéma et la bonne gestion des URIs https://github.com/SimonVT/schematic
  66. Par contre ! • Très peu de documentation • “La

    documentation c’est le code” • SimonVT/cathode - ProviderSchematic.java https://github.com/SimonVT/schematic
  67. public interface MovieColumns { @DataType(INTEGER) @PrimaryKey @AutoIncrement String ID =

    “_id”; /* … */ } ! Supporte également @DefaultValue, @NotNull, @References et @Unique https://github.com/SimonVT/schematic
  68. DatabaseX.java ! @Table(MovieColumns.class) public static final String MOVIES = “movies";

    /* … */ ! @ExecOnCreate // indexes, triggers… https://github.com/SimonVT/schematic
  69. ProviderX.java ! @TableEndpoint(table = DatabaseSchematic.MOVIES) public static class Movies {

    ! } https://github.com/SimonVT/schematic
  70. ProviderX.java - Movies ! @ContentUri( path = Path.MOVIES, type =

    "vnd.android.cursor.dir/movie") public static final Uri CONTENT_URI = buildUri(Path.MOVIES); https://github.com/SimonVT/schematic
  71. ProviderX.java - Movies ! @InexactContentUri( path = Path.MOVIES + "/#",

    name = "MOVIE_ID", type = "vnd.android.cursor.item/movie", whereColumn = MovieColumns.ID_TRAKT, pathSegment = 1) public static Uri withId(String traktId) { return buildUri(Path.MOVIES, traktId); } https://github.com/SimonVT/schematic
  72. https://github.com/SimonVT/schematic content://.../movies content://.../movies/42 content://.../movies/1337

  73. ProviderX.java - Movies ! ! @NotifyUpdate(paths = Path.MOVIES + "/#")

    public static Uri[] notifyUpdate(Context context, Uri uri, String where, String[] whereArgs) { int id = Integer.valueOf(uri.getPathSegments().get(1)); return new Uri[]{CONTENT_URI, withId(id)} } https://github.com/SimonVT/schematic
  74. Du Rx dans votre base https://github.com/square/sqlbrite ! • Wrap SQLiteOpenHelper

    • Requêtes en base deviennent des Observables • Supporte les notifications quand une table est modifiée
  75. User Interface Material, Animations, RecyclerView

  76. Material YHolo!

  77. None
  78. Material is the metaphor Bold, graphic, intentional Motion provides meaning

  79. Material is the metaphor Bold, graphic, intentional Motion provides meaning

  80. Material is the metaphor Bold, graphic, intentional Motion provides meaning

  81. None
  82. None
  83. Un doute ? https://github.com/google/iosched

  84. Animations et autres tricks

  85. None
  86. None
  87. None
  88. None
  89. Spans, a Powerful Concept. http://flavienlaurent.com/blog/2014/01/31/spans/

  90. RecyclerView R.I.P {List/Grid/…}View

  91. RecyclerView =/= ListView

  92. Gère le recyclage des vues, point Un composant flexible

  93. Dessus, on plug : LayoutManager ItemDecoration ItemAnimator

  94. Ce qui est cool : ! • 3 LayoutManager de

    base • Animations • Modifier l’orientation • Layout depuis le haut ou le bas • Gestion des spans
  95. Ce qui l’est moins : ! • Dividers ? •

    Headers / Footers ? • Réécrire les adapters • OnItemClickListener ? • ChoiceModes ?
  96. Headers / Footers ! 1. Wrapper l’adapter dans un autre

    2. Gérer les headers / footers à la main 3. Ecrire son propre LayoutManager 4. Padding et Translation
  97. recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new RecyclerUserAdapter());

  98. recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new RecyclerUserAdapter());

  99. recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); recyclerView.setAdapter(new RecyclerUserAdapter());

  100. Deux “astuces”

  101. Une ligne de code pour supporter tous les types d’écrans

    ! new GridLayoutManager( getActivity(), getResources().getInteger(R.integer.grid_poster_columns)); res/values integers.xml <integer name="grid_poster_columns">3</integer> res/values-land … res/values-sw600dp … …
  102. None
  103. None
  104. manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return

    getAdapter().isHeader(position) ? manager.getSpanCount() : 1; } });
  105. Pour aller plus loin https://github.com/lucasr/twoway-view

  106. Pas d’inspiration ? MaterialUp & Android Niceties

  107. None
  108. None
  109. Conclusion

  110. Q&A speakerdeck.com/ github.com/ @florianmski