Cet article retrace les sujets Kotlin présentés lors de la Droidcon de New York 2019.

Performance in a Kotlin world

Speakers : Sean McQuillan & Daniel Galpin 

La conférence a démarré avec l’assertion suivante :

Measuring is performing

Afin d’avoir une application la plus performante possible, il est dans l’idéal de mesurer au mieux. Une application est considérée comme non performante si elle répond lentement, qu’elle freeze, que les animations sont saccadées.

Profiling

Il faut savoir qu’Android Studio a une liste d’outils pour obtenir des métriques différentes : mémoire, batterie utilisée, CPU (Central Processing Unit), network, etc…

Les intervenants se sont surtout focalisés sur le profiler de CPU d’Android Studio, notamment à la fonctionnalité qui permet d’effectuer une capture entre un instant T et un instant T+1. Grâce à cette capture, il est possible de regarder tout ce qu’il s’y passe (appels, class loading, database, tags, onCreate, inflate, menu, etc…) et de définir si des éléments peuvent être déplacés en cas de non-nécessité immédiate.

Les conseils des intervenants pour améliorer la performance:

  1. Trouver un but (exemple: un écran est lent pour afficher ses images)
  2. Mesurer la période entre le moment où on fait l’action de lancer l’écran et où il est affiché totalement
  3. Trouver ce qui peut être amélioré, faire la modification
  4. Retourner au point II.

Petit conseil : quand vous êtes en train d’analyser votre application, désactivez Instant Run si ce n’est pas encore fait. Il y a un petit impact sur la performance qui pourrait entrer en interférence avec les informations remontées par le profiler.

Benchmarking

Cette partie a été rapidement abordée mais dans leur exemple, ils ont voulu analyser leur code à l’aide de la librairie Jetpack Benchmark au sein d’Android Studio.

Le but de la librairie est de mesurer la performance du code en se basant sur les standards des différents langages (Java, Kotlin) et d’afficher à la fin les résultats dans la console.

Ces mesures sont principalement utiles pour un travail sur le CPU. Le meilleur exemple pour le benchmarking est sans doute un RecyclerView qui en plus du scroll doit récupérer de la donnée, la convertir et la renvoyer à la vue de façon répétitive. Puisque le benchmark fonctionne dans une boucle, il est difficile de mesurer des autres types de codes que l’exemple des RecyclerView.

How to do great work

Speaker : John Li

Devenu récemment Manager d’une nouvelle équipe après avoir été TL (Technical Leader) pendant quelques années, le speaker a fait part de son expérience pour nous énumérer les notions qui lui paraissent fondamentales pour faire un bon boulot. En voici la liste:

  • Android-first Mindset
  • Ce que tu écris est compréhensible
  • Le code change parce que les besoins changent
  • Les retours sont un cadeau : review de code, conversations, etc.
  • Se baser sur les 5 principes de bases du concept SOLID pour la programmation orientée objet
  • Faire les interfaces les plus petites possibles
  • Eviter la nullabilité (même si Null est ton ami en Kotlin)
  • ReadOnly permet de réduire les defects
  • Eviter la duplication
  • Utiliser des noms pleins de sens

« If you want your code easy to write, make it easy to read » (Uncle Bob)

  • Lutter contre les bouts de codes obsolètes (en comparaison : « laisse le campement encore plus propre que ce que tu as trouvé »)
  • Ecrire des tests unitaires … pour être plus confiant en ton code
  • Séparer les modèles au lieu de réutiliser les mêmes entre la couche Data et UI
  • Coder par opportunité est un piège !!!

Making a mountain out of a module

Speaker : Joe Birch

Suite à son retour d’expérience, plus son application grossissait et grandissait, plus il ressentait les désavantages d’une architecture monolithique. Pas seulement parce que cela rendait la maintenabilité du code difficile mais aussi car cela impactait l’équipe (dans certains cas, beaucoup plus de réflexion).

Avec des modules clairement définis et les principes de la clean architecture, tout parait plus simple : séparation claire entre les features, beaucoup plus facile à tester (uniquement les fonctionnalités modifiées).

La modularisation a de nombreux avantages:

  • à la demande : il n’est pas nécessaire de tout migrer immédiatement, la tâche peut être réalisée au fur et à mesure selon les besoins, la plus-value. Il n’est pas obligé non plus de modulariser toutes les fonctionnalités.
  • instantané : du moment où un module est crée, où la séparation est créée, c’est instantané. Il n’y a pas à attendre plus longtemps, faire multitudes de choses.
  • conditionnel : les modules ne sont pas obligés d’être tous identiques. Ils peuvent permettre de tester des nouvelles choses (module en Flutter), des nouveaux patterns d’architecture. Bien sûr il n’est pas recommandé de ne faire que des modules différents mais cela fait partie d’un de ces avantages.

Si on devait associer un adjectif à la modularisation ça serait: F-L-E-X-I-B-L-E.

Effective Multiplateforme Architecture

Speaker : Ryan harter

Le sujet de multiplateforme est de plus en plus abordé ces derniers mois. En effet, beaucoup de projets se retrouvent dans la même situation : une application iOS, une application Android, parfois une application Web. Quasiment le même parcours fonctionnel. Juste quelques différences liées aux spécificités du front. Beaucoup de temps de passé pour tous les maintenir, tous les ajuster quand un changement est effectué sur l’un des fronts. Le but initial du multiplateforme est d’arriver à réduire le temps de maintenance.

Comment arriver à une architecture multiplateforme ?

#1 – Modularisation

L’étape nécessaire est de modulariser la ou les applications. A minima, il en faut une sur l’ensemble de modularisée.

Pourquoi ? Parce que cela va demander moins de temps de migrer un module à la fois et sera aussi plus facile pour tester que l’implémentation du multiplateforme fonctionne bien sans devoir tester toutes les applications de A à Z.

La modularisation est aussi importante car pour mettre en place une architecture modulaire, il est impératif d’avoir une clean architecture (aussi nécessaire pour le multi-plateforme).

Pour ses exemples, Ryan s’est basé sur un pattern MVI (Model View Intent).

Pourquoi pas du MVVM (Model View ViewModel) ? Le pattern ViewModel, comme annoncé à la Google I/O il y a quelques années, est très fortement lié au LiveData. Le LiveData est parfait pour Android mais n’a pas réellement sa place pour du multiplateforme. Le choix s’est donc orienté pour cette raison vers le pattern MVI.

#2 – Multiplatforme

Pour mettre en place le multiplateforme, il faut avoir conscience qu’il existe différents cas et savoir dans quel cas se situe le projet en question. Voici les différents cas possibles et comment procéder pour implémenter le multiplateforme dans chacun de ces cas :

  • Le projet comporte une seule application pour le moment

Partir avec l’idée que le multiplateforme sera comme une nouvelle application.
Reproduire toutes les fonctionnalités les unes après les autres.

  • Le projet possède deux applications avec des architectures différentes

Le travail est similaire au travail de modularisation.
Convertir en MPP (Model Platform Patten) pendant la modularisation. Prendre une fonctionnalité à la fois. Démarrer du bas vers le haut.

  • Le projet possède deux applications avec un haut niveau d’architecture qui sont les mêmes

Même si l’architecture est la même, garder en tête qu’il y aura toujours des différences. 
Plusieurs approches : nouvelle fonctionnalité, migration horizontale ou migration verticale. Le choix dépend vraiment du projet, de l’architecture, de la roadmap. Le travail peut être fait sur des petites PR’s (réduit les risques, plus facile à tester, etc…).

Approche nouvelle fonctionnalité :
Le gros avantage de cette méthode est qu’il n’est pas nécessaire de convertir toute l’application avant de se lancer dans du multiplateforme. On démarre la nouvelle fonctionnalité et elle sera utilisée directement par les deux applications (dans un premier temps une seule puis quand tout sera validé, la seconde).

Approche migration verticale
Démarrer du bas vers le haut, commencer la conversion sur une seule plateforme uniquement. Quand c’est ok, faire la migration sur une autre plateforme.
Puis continuer ainsi de suite, jusqu’à atteindre le haut et que toute la fonctionnalité soit migrée.

Approche migration horizontale :
Meilleure approche (subjectivité du speaker). 
Démarrer au niveau de la couche feature : là où la business value est la plus complexe. Le challenge est de convertir la couche en entière (views + repositories / usecases). Quand toute la couche est finie, passer à la suivante.

Préconisation du speaker : utiliser ReactiveStreams. L’objectif principal de ReactiveStreams est de fournir une norme pour le traitement de flux asynchrone sans avoir de pression bloquante du back.

ReactiveStreams API comporte 4 interfaces :

  • Subscription : interface qui agit comme une sorte de description et possède deux méthodes : cancel() et request(long).
  • Publisher : tout est là, la force de Reactive Streams, la capacité pour un publisher d’adapter son flux de données en fonction du subscriber.
  • Subscriber : interface avec 4 méthodes: onComplete(), onError(t: Throwable), onNext(t: T), onSubscribe(s: Subscription). Très similaire aux observables que nous connaissons.
  • Processor : interface qui étend de Publisher et de Subcriber. Peut donc se comporter comme l’un ou l’autre.

L’avantage de Reactive Streams est qu’il fonctionne très bien avec beaucoup de choses : liveData, rxJava, reactor, coroutines, … et qu’il n’est pas nécessaire d’apporter énormément de modifications pour le faire fonctionner sur toutes les plateformes (juste sur iOS où il faudra venir redéfinir les interfaces).

Coroutine + Flow = MVI

Speaker : Etienne Caron

Gérer les états dans les applications Android peut être douloureux. Depuis quelques années, beaucoup de patterns d’architectures ont évolué pour essayer de s’y rapprocher: MVC, MVP, MVVM. Récemment, le pattern MVI (Model View Intent) a vu le jour, il s’agit d’une évolution de ces patterns. Combiner le pouvoir de MVI avec les coroutines et la librairie récemment sortie de Kotlin Flows donne le résultat suivant : une réelle magie !! Plus besoin d’utiliser RxJava.

Le but du talk était de montrer comment construire une application Android en suivant le pattern MVI, en utilisant à la fois des Coroutines et des Flows.

  • Coroutines

On ne va pas rentrer dans les détails de ce que sont les Coroutines en Kotlin car un article, rédigé par Mehdi Slimani à la suite des Android Makers 2018, est déjà disponible sur le blog.

Juste pour résumer, voici un passage cet article (si vous n’avez pas envie de tout lire !!!).

Les coroutines sont une approche différente dans l’écriture de code asynchrone ; contrairement à de la programmation réactive qui manipule un flux, les coroutines favorisent un type séquentiel de programmation asynchrone. Elles peuvent donc exécuter plusieurs traitements en parallèle et le code reste quant à lui séquentiel et donc plus facile à lire.

  • Flow

Le design des Flows en Kotlin est basé sur des suspending fonctions qui sont complétées séquentiellement (normalement ou avec une exception). On peut voir le Flow comme un « courant froid » (cold stream).

Un Flow en Kotlin est représenté par est une interface avec une seule fonction collect qui prend un paramètre un FlowCollector lui aussi une interface avec une unique fonction emit.

interface Flow<out T> { suspend fun collect(collector: FlowCollector<T>) }
interface FlowCollector<in T> { suspend fun emit(value: T) }

Les fonctions emit et collect des deux interfaces précédentes sont des supsend fonctions.

Si le consumer consomme les éléments plus lentement que le producer les produit, l’appel fait à emit sera simplement suspendu jusqu’à ce que le consumer soit capable de recevoir le prochain élément.

Pour créer un Flow, nous allons avoir besoin d’un Flow Builder. Grâce à ce builder et à la méthode collect, il est possible de commencer à écrire des operators qui vont permettre de transformer les flux.

Signature d’un Flow Builder:

fun <T> flow(block: suspend FlowCollector<T>.() -> Unit): Flow<T>

Flow n’est pour le moment pas aussi riche que RxJava pour ce qui est des opérateurs mais on en compte déjà une trentaine dont les plus connus d’entre eux sont : map, flatMapConcat, flatMapMerge, zip, filter, filterNot, take, takeWhile, drop, dropWhile, etc…

  • Channel

Quand deux coroutines souhaitent communiquer entre elles, c’est là que nous allons voir intervenir des Channels.

On pourrait leur trouver beaucoup de similitudes avec l’interface Java blockingQueue. Rappel rapide sur le concept (un thread qui essaie de mettre un élément en liste d’attente va se retrouver bloqué jusqu’à ce qu’un autre thread finisse son traitement et fasse de la place ou qu’un, ou plusieurs éléments, soit retiré de la file d’attente ou encore en supprimant totalement tous les éléments de cette file d’attente). Une des différences est qu’au lieu d’avoir les opérations put et take, on trouve des suspend fonctions appelées send et receive.

val channel = Channel<Int>()
launch {
    channel.send(2 * 3)
}
 println(channel.receive()) 

Une bonne nouvelle avec les Channel en Kotlin c’est que le backpressure est géré. En RxJava, on parle de backpressure quand les éléments sont émis par l’observable (plus rapidement) qu’ils ne sont consommés par le subscriber.

Petit tableau comparatif présenté au cours du talk.

RxCoroutines
Single / Completablesuspend function
Flowable / ObservableFlow
BehaviorSubjectConflatedBroadcastChannel (DataFlow proposal)
SchedulersDispatchers
DisposablesScopes

  • Model View Intent
MVI est une approche très propre qui permet de gérer les états et les changements d’UI au sein d’une application.

Les trois éléments-clés de ce pattern sont les suivants : flux de données unidirectionnel, états prévisibles et immutabilité. Pour redéfinir la notion d’immutabilité, en programmation orientée objet et fonctionnel, on dit d’un objet qu’il est immuable quand son état ne peut pas être modifié après sa création.

Hands on MvRx: Building Real Apps

Speaker: Gabriel Peal

MvRx est un framework Android, développé par Airbnb, a prononcé de la façon suivante « Mavericks ». Il s’agit d’un nouveau pattern d’architecture : ModelView + Rx, créé initialement pour leurs besoins personnels tel que builder les produits plus facilement, plus rapidement et avec plus de fun.

Le framework est 100% en Kotlin ce qui permet d’hériter des fonctionnalités pour une API plus propre.

Série d’articles