Temps de lecture : 6 minutes

Le 23 & 24 avril 2018 a eu lieu la deuxième session d’Android Makers à Paris.

J’ai eu la chance grâce à Ineat d’y participer (une première pour moi !!!) et en plus en bonne compagnie entourée de mes gardes du corps (Mehdi Slimani venait pour représenter ILab et Maxime Jallu en tant que deuxième développeur Android).

Ce qui était très intéressant c’est qu’étant tous en mission chez des clients différents nous avons pu (en plus de suivre les conférences) discuter ensemble des pratiques mises en place chez nos clients (Leroy Merlin, Decathlon).

Speakers & talks

Au cours de ces deux jours, nous avons eu l’occasion d’assister à une quinzaine de conférences avec une vingtaine de speakers différents (Dur dur de faire un choix pour l’article).

Sans assister à tous les talks, il était assez facile de savoir ce qu’il se passait dans les autres conférences grâce aux réseaux sociaux et notamment au twitter de l’événement.

Voici les conférences er les speakers qui ont retenus mon attention :

  • Romain Guy (Google) et Chet Haasse (Google) pour la Keynote d’ouverture et conférence de fermeture
  • Vadim Caen (Google) et Jérome Gaillard (Google) pour la conférence Creation Layouts doesn’t have to be painful
  • Kirill Suslov (Shopify) avec une conférence matinale sur les Collections Kotlin
  • Florina Muntenescu (Google) et Nick Butcher (Google) pour un duo dev/design lors de la conférence Typesetting: designing and building beautiful text

Il est possible à tout le monde de (re)visualiser les différentes conférences en se rendant sur la page youtube de Android Makers.

Au travers de cet article, je vais vous faire part des petits conseils qui nous ont été donnés concernant la preview d’Android Studio afin d’utiliser au maximum ses options. Je vais aussi me focaliser sur Kotlin et les extensions, montrer quels avantages elles apportent et comment elles rendent Kotlin encore plus intéressant.

Tips

Lors des deux talks sur les layouts, le design et XML, beaucoup de conseils nous ont été communiqués, des petites astuces qui aident beaucoup dans la preview d’Android Studio 🙂

  • Layout
 Layout activity_region 
<android.support.constraint.ConstraintLayout
  ...>
     <include layout="@layout/content_region"/>
 </android.support.constraint.ConstraintLayout>

Layout content_region
 <android.support.constraint.ConstraintLayout 
 	xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:showIn="@layout/activity_region">
    ... </android.support.constraint.ConstraintLayout>

showIn : permet de pointer vers un layout qui utilise ce layout comme include, il est possible ainsi de visualiser comme il apparaitrait dans le layout parent.

Dans l’exemple, la preview d’Android Studio montre le layout content_region comme s’il apparaissait à l’intérieur du layout activity_region.

 

  • TextView
  <TextView
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:breakStrategy="balanced"
        android:hyphenationFrequency="none"
        tools:text="@tools:sample/lorem/random"/>

breakStrategy : stratégie appliquée pour les passages à la ligne des paragraphe. 3 valeurs possible (high_quality, simple, balanced).

hyphenationFrequency : fréquence à utiliser pour déterminer les ruptures de mots en fin de ligne. 3 valeurs possibles (none, normal, full).

autoSizeTextType : définit si le redimensionnement du texte se fait de façon automatique dans le container. 2 valeurs possibles (none, uniform). Dans le cas du uniform, la taille du texte se mettra à jour toute seule pour rentrer dans le texte.

text=@tools:sample/… : il existe des samples data déjà disponibles dans Android Studio qui peuvent nous permettre de mettre des valeurs dans un textview et selon le cas d’usage. Différentes valeurs possibles (first_name, full_name, cities , lorem, lorem/random, avatars).

 

  • RecyclerView
Layout item
 <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:text="@tools:sample/cities"/>

<android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:itemCount="4"
        tools:listitem="@layout/item"/>
 => Si le premier layout utilisé, ça affichera 4 textviews avec 4 noms de villes différentes

listItem: spécifie quel layout afficher dans la preview dans le cas d’une liste (ou recyclerView). Il existe aussi les attributs listHeader et listFooter.

itemCount: pour un recycler view donné, cet attribut spécifie le nombre d’items à afficher dans la preview.

Dans l’exemple, la preview affichera un recycler view avec 4 items (text view) et les textes seront des noms de villes différentes (grâce aux sample datas).

Il existe beaucoup d’attributs (consultables sur la doc) qui peuvent servir à mieux pré-visualiser nos layouts dans Android Studio. Il ne faut surtout pas hésiter à les utiliser. Cela rend le visuel plus sympa et aide énormément quand un autre développeur est censé effectuer des modifications un xml qui n’est pas le sien à l’origine.

En passant du java au Kotlin, pas mal de développeurs se sont frottés au dilemme sur les listes. Un nouveau concept a été introduit avec Kotlin sur les list, map, … (Mutable).

L’utilisation du mutable permet de rendre modifiable. La question que se sont posés beaucoup de personnes: quoi choisir entre arrayListOf et mutableListOf (MutableMap et HashMap) ?

  •  List, ArrayList, MutableList

ListOf: il s’agit d’une liste non modifiable qui vient de Collections.singletonList(…).

MutableListOf: il s’agit d’une interface qui va retourner une ArrayList() et qui est donc modifiable. On peut parler ici de fonction inline: en effet, quand on va écrire mutableListOf, à la compilation ça mettra ArrayList().

public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

 

ArrayListOf:  il s’agit d’un alias de ArrayList.java et elle est donc modifiable.

 

SO WHAT ???                                                                On choisit donc mutableListOf<T>()
MAIS POURQUOI, si les deux sont modifiables ?? Comme ça si un jour, le SDK vient à changer, tu ne seras pas impacté, ça se fera en toute transparence.

 

  • HashMap VS. MutableMap

Je ne vous refais pas tout le speech mais l’explication est la même et le résultat en découle !!!        mutableMapOf<K, V>()

« Do something today that your future self will thank you for? » (Sean Patrick Flanery)

Love Kotlin

Avec l’arrivée de Kotlin, la notion d’extensions est apparue. Cela donne la possibilité d’étendre des fonctionnalités à une classe sans avoir besoin d’en hériter. C’est un concept déjà connu de certains développeurs puisqu’il est présent en C#.

  • Exemple extension

Il est possible dans certains cas d’ajouter des fonctions.

/* 
      Pattern : 
      fun [type].[method]: [Type] { /* ... */ } 
*/
fun String.toDate(): Date {
        val formatter = SimpleDateFormat("yyyy-mm-dd")
        return formatter.parse(this)
    }

val date = "2018-04-23".toDate()

Cependant il est important de noter qu’il n’est pas possible d’ajouter une extension de méthode sur une méthode existante. Celle-ci ne sera jamais prise en compte.

En java, on utilisait les classes nommées *Utils.

 

Il est également possible d’ajouter des propriétés en Read-Only sur vos classes en utilisant l’opérateur val. L’instanciation s’effectue par l’utilisation de l’opérateur get comme ceci :

val Any.LOGGER: Logger
    get() = LoggerFactory.getLogger(this.javaClass)

// ou encore 

val Any.LOGGER = LoggerFactory.getLogger(this.javaClass)

// ou enfin 

val Any.LOGGER: Logger
    get() {
        return LoggerFactory.getLogger(this.javaClass)
    }

 

  • Exemples d’extensions au travers des collections

Il existe différents type de collections (List, Map, Set, etc…). Certaines de ces collections possèdent déjà des extensions propores à elles.

arrayOf(7.3, 7.6, 7.4, 7.6, 8.1, 7.7).average()
arrayOf(7.3, 7.6, 7.4, 7.6, 8.1, 7.7).sum()

val movieList = mutableListOf<Movie>()
val newList = mutableListOf<Movie>()

movieList.maxBy { it.averageRating }

movieList.sumByDouble { it.averageRating }

movieList.first { it.averageRating < 7.5 } //No SuchElementException si pas de résultat
movieList.find { it.averageRating < 7.5 } // Null si pas de résultat
//movieList.find() <=> movieList.firstOrNull()

movieList.last { it.averageRating < 7.5 } //No SuchElementException si pas de résultat
movieList.findLast { it.averageRating < 7.5 } // Null si pas de résultat
//movieList.findLast() <=> movieList.lastOrNull()

movieList.single { it.averageRating == 7.5 } //IllegalArgumentException si pas de résultat
movieList.singleOrNull { it.averageRating == 7.5 } //Null si pas de résultat

movieList.filter { it.averageRating > 7.5 }
movieList.filterNot { it.averageRating > 7.5 }
movieList.filterTo(newList, { it.averageRating > 7.5 })
movieList.filterNotTo(newList, { it.averageRating > 7.5 })
movieList.filterNotNullTo(newList)
//Filtre selon le prédicat et met le résultat dans une nouvelle liste

movieList.map { it.title } // [title_0, title_1]
movieList.mapIndexed { index, movie -> index to movie.title } // (0, title_0), (1, title_1)
movieList.map { it.genre } // [[action],[action, drama], [romance]]

movieList.flatMap { it.genre } // [action, action, drama, romance]
movieList.flatMap { it.genre }.distinct() // [action, drama, romance]
// "distinct()" supprime les doublons

movieList.associate { it.title to it.averageRating }
movieList.associateBy { { it.title } to { it.averageRating } } // {Title_0=7.1, Title_1=7.2}

movieList.groupBy({ it.genre }, { it.title }) // {action=[Title_0,Title_1], drama = [Title_1], romance = [Title_2]}

 

  • Différence d’utilisation des collections avec ou sans extension

Sans extension

var count = 0
var total = 0.0
        for (movie in movieList) {
            for (genre in movie.genre) {
                if (genre == "action") {
                    count++
                    total += movie.averageRating
                }
            }
        }

val averageOld = total / count

 

Avec extension

val averageNew = movieList.filter { it.genre == "action" }.map { it.averageRating }.average()

 

On remarque avec cet exemple l’avantage de l’utilisation des extensions sur les collections en Kotlin. Le code est beaucoup plus compacté. Imaginez le gain sur une classe entière ou un projet au complet. En rendant le code plus compact, il est à la fois plus lisible et plus maintenable (à partir du moment où tous les développeurs du projet sont familiers avec les collections: si ce n’est pas le cas, pas de soucis, on peut toujours les retrouver ici). 🙂

 

  •  A vos claviers !!! A votre avis, combien de lignes fallait-il pour écrire cette fonction ?
movieList
                .groupBy { it.genre }
                .mapValues {
                    it.value
                            .map { it.averageRating }
                            .average()
                }