Introduction à Kotlin pour Android

Kotlin est le nouveau langage de programmation Open Source supporté par Google pour les applications Android (mais aussi pour le back-end Java et les moteurs JavaScript des navigateurs web). Il a pour but d’améliorer la productivité des développeurs, tout en restant compatible avec le code existant. Il est également possible de le compiler en natif.

Les pré-requis

Android Studio 3 intègre par défaut le support d’outils pour Kotlin, il est disponible en version stable depuis octobre 2017.

Si vous ne souhaitez pas mettre à jour Android Studio, vous pouvez à partir de la version 2.2 installer le plugin, vous permettant d’utiliser ce nouveau langage.

Cet article est principalement destiné aux personnes ayant quelques notions en développement d’applications Android.

Introduction

Le créateur de Kotlin n’est autre que JetBrains, l’éditeur de l’IDE IntelliJ IDEA qui sert de base à Android Studio (notre IDE favori pour développer les applications Android). Langage créé en 2011, ce n’est qu’en mai 2017 lors de la conférence Google I/O que Google annonce officiellement son support. Kotlin est maintenant sur le devant de la scène. C’est une année décisive qui pourrait bien lui permettre de se faire une place.

C’est un langage de programmation orienté objet et fonctionnel que l’on peut également appeler langage multi-paradigme tout comme Scala ou Python par exemple.

Il a l’avantage d’être interopérable avec le code Java car il est compilé en bytecode pour la JVM, mais il est surtout bien pensé au niveau syntaxique puisque que toute classe Java peut être manipulée directement en Kotlin. Il est en effet possible d’avoir du code Java et Kotlin au sein d’un même projet, mais aussi de convertir un projet Java en Kotlin.

L’infographie disponible ici présente les atouts de Kotlin, son adoption et ses ambitions.

En avant !

Google Developers met à disposition plusieurs tutoriels sur Kotlin sur son site codelabs.developers.google.com dont deux particulièrement intéressants qui sont “Build Your First Android App in Kotlin” et “Taking Advantage of Kotlin”. Je vous conseille de les mettre en pratique car ils permettent de comprendre les avantages de Kotlin par rapport à Java, sensibilisent sur les bonnes pratiques et proposent des astuces.

Pour perfectionner votre syntaxe Kotlin, vous pouvez vous entraîner sur kotlinlang.org ou sur Android Studio avec le plugin EduTools. Ce sont les mêmes exercices mais l’un est sur navigateur et l’autre sur Android Studio.

1. Découverte

C’est un détail qui peut avoir son importance, les fichiers Kotlin possèdent le suffixe .kt et non plus .java

  • val et var
    Le mot clé var permet, comme en Java, de déclarer une variable ré-assignable.

    //Java
    String firstName = "Hello world";
    
    //Kotlin
    var firstName: String = "Hello world"
    

    Le mot clé val rend l’assignation définitive comme final de Java.

    //Java
    final String firstName = "Hello world";
    
    //Kotlin
    val firstName: String = "Hello world"
    
  • data
    Le mot clé data indique au compilateur que la classe est utilisée pour représenter une entité. Mais il permet aussi de générer un certain nombre de méthodes utiles comme les méthodes toString(), copy(), equals(), hashCode() ou les déclarations déstructurées de l’objet par exemple.

    Exemple :

    data class Contact (val firstName: String, val lastName: String)
    

    Une déclaration déstructurée génère plusieurs variables à la fois. Nous avons déclaré deux nouvelles variables : nom et âge que nous pouvons utiliser indépendamment.

    val (firstname, lastname) = Contact("john", "rambo")
    println(firstname)
    println(lastname)
    
  • String
    Kotlin inclut un support pour String qui permet l’utilisation de templates au sein des String.

    var a = 1
    val s1 = "a is $a" 
    
    a = 2
    val s2 = "${s1.replace("is", "was")}, but now is $a"
    

2. Null Safety

Ah cette fameuse erreur NullPointerException que l’on aime tant (ou pas). Avec Kotlin, il est maintenant possible et facile de réduire ce nombre d’erreurs. Toutes les variables sont non nulles par défaut mais il est possible d’appeler l'exception avec throw NullPointerException().

Cette variable n’accepte pas la valeur null, par conséquent, cette ligne ne compilera pas.

val x: Int = null

Si vous souhaitez faire accepter la valeur null, vous devez ajouter un ? avec votre type

val x: Int? = null
  • L’opérateur Safe Call (?.)
    Si vous essayez de compiler cette ligne et que x est null, vous risquez d’avoir des problèmes de compilation.

    val y = x.toDouble()
    

    Pour éviter de rajouter une condition if(x != null), il est possible d’ajouter ? qui informe que x peut être null. Si c’est le cas, la méthode toDouble() ne sera pas exécutée et retournera un null. La variable y sera alors de type Double?

    val y = x?.toDouble()
    

    Si vous avez besoin d'exécuter un certain nombre d’instructions lorsque la valeur de l’objet est différent de null, vous pouvez utiliser l’opérateur Safe Call ?. suivi de let.

    val listWithNulls: List<String?> = listOf("A", null)
    for (item in listWithNulls) {
         item?.let { println(it) } // prints A and ignores null
    }
    
  • L’opérateur Elvis (?:)
    Elvis permet de ne pas retourner de valeur null, si, comme dans l’exemple précédent, on a la possibilité d’obtenir un résultat null.

    Elvis se compose donc de ?: et permet ainsi d’attribuer une valeur au résultat si celui-ci est null.

    val y = x?.toDouble() ?: 0.0
    
  • L’opérateur !!
    Il évitera de vérifier que votre variable vaut null. Il est donc possible d’obtenir un NullPointerException dans ce cas.

    val x: Int? = null
    val y = x!!.toDouble()
    
  • lateinit
    Le mot clé lateinit est utilisé lorsque l’on sait que la variable ne sera pas nulle (à l'exception d’un booléen) lors de son utilisation, mais qu’il n’est pas possible de l’initialiser lors de sa déclaration. lateinit permet de contourner le blocage produit par Null Safety mis en place dans Kotlin.

    //Sans
    var mContacts: ArrayList<Contact>? = null
    // Avec
    lateinit var mContacts: ArrayList<Contact>
    

3. Kotlin Android Extensions

Le principale avantage de Kotlin Android Extensions est de se passer de la déclaration des différentes vues (TextView, Button ...) que l’on voit dans le code Java. Pour ce faire,vous pouvez importer le plugin kotlin-android-extensions.

kotlin-1
Ajout du plugin kotlin-android-extensions

Si vous voulez utiliser un composant graphique venant d’un fichier XML, il est nécessaire de le déclarer avec findViewById lorsque vous codez en Java.

Une première amélioration avait été mise en place pour Java avec l’utilisation des AndroidAnnotations. @ViewById faisait la liaison entre la vue et la variable au moment de sa déclaration dans la classe.

Java - AndroidAnnotations

@ViewById(R.id.textView)
TextView search;

Grâce au plugin kotlin-android-extensions, il est possible de diminuer le nombre de lignes et d’augmenter la lisibilité du code en utilisant le composant directement avec son Id. La méthode findViewById sera remplacée par un import. Il n’est donc plus nécessaire de déclarer une variable lorsque l’on veut utiliser une vue.

Fichier Xml

<TextView
   android:id="@+id/textView"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content" />

Kotlin sans Kotlin Android Extensions

fun countMe (view: View) {
   // Get the text view
   val showCountTextView = findViewById(R.id.textView) as TextView
  
  // Get the value of the text view.
   val countString = showCountTextView.text.toString()
}

Kotlin avec Kotlin Android Extensions

// Using R.layout.activity_main from the main source set
import kotlinx.android.synthetic.main.activity_main.textView
…

fun countMe (view: View) {
   // Get the value of the text view.
   val countString = textView.text.toString()
}

La documentation est disponible ici.

4. Les lambdas

Une lambda est une expression permettant de décrire une fonction anonyme définie par des paramètres d’entrée et un type de retour. Elle est entourée de {} et une -> sépare les paramètres de la fonction de sa définition.

Exemple :

{ x: Int, y: Int -> x + y }

Les paramètres : x: Int, y: Int
La définition : x + y

  • Mise en situation
    Plutôt que de créer une fonction pour dire Bonjour toto !, on va utiliser une expression lambda.

    Pour un paramètre

    val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        var result = list.filter { element -> element > 5 }.map { element -> element * 2 }
    println(result) //Output : [12, 14, 16, 18, 20]
    

    Pour plusieurs paramètres

    val sum: (Int, Int) -> Int = { x, y -> x + y }
    println(sum(2, 3)) //Output : 5
    

    Lorsqu’une expression lambda n’a qu’un seul paramètre, on peut utiliser le mot clé it comme dans l’exemple ci-dessous.

    val list = listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    var result = list.filter { it > 5 }.map { it * 2 }
    println(result) // Output : [12, 14, 16, 18, 20]
    

    Les lambdas sont très pratiques lorsqu’il s’agit d’alléger le code comme pour la méthode setOnClickListener().

    Avant

    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            showAddContactDialog(-1);
        }
    });
    

    Après

    fab.setOnClickListener { showAddContactDialog(-1) }
    
  • Kotlin Standard Library
    Les lambdas peuvent également être passées en paramètre des fonctions issues de la librairie Kotlin comme sortBy(), map(){} ou mapTo(){}

    Exemple avec sortBy() pour trier un tableau.

    mContacts.sortBy { it.firstName }
    

5. Les fonctions d’extension

Kotlin prend en charge les fonctions d’extension et les propriétés d'extension, ce qui offre la possibilité d'étendre une classe avec de nouvelles fonctionnalités sans avoir à hériter de la classe.

Il est conseillé de créer un nouveau fichier Extensions.kt afin pouvoir y placer toutes les extensions.

Nous avons ici une extension d’un objet EditText que l’on a nommée validateWith qui permet d’afficher une image à la gauche du composant. Elle sera différente si la saisie est valide ou non.

internal inline fun EditText.validateWith(passIcon: Drawable, 
                        failIcon: Drawable,
                        validator: TextView.() -> Boolean): Boolean {
    
    setCompoundDrawablesWithIntrinsicBounds(null, null,
            if (validator()) passIcon else failIcon, null)

    return validator()
}
var mEntryValid = mFirstNameEdit.validateWith(passIcon, failIcon, notEmpty)

La documentation des fonctions d’extension est disponible ici.

6. Les classes d’objets s’allègent

En Java, la classe d’un objet peut s’avérer relativement grande avec la génération d’un ou de plusieurs constructeurs ainsi que des getters et setters.

La syntaxe Kotlin ne nous oblige pas à les coder, ils sont optionnels, ce qui rend la classe plus lisible.

Java - Classe Contact

class Contact {

    private String firstName;
    private String lastName;
    private String email;


    Contact(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }

    String getFirstName() {
        return firstName;
    }

    String getLastName() {
        return lastName;
    }

    String getEmail() {
        return email;
    }

    void setEmail(String email) {
        this.email = email;
    }
}

Java - Modifier ou récupérer l’email

Contact contact = new Contact("John", "Doe", "mail@ippon.fr");
contact.setEmail("nouveau_mail@ippon.fr");
String myEmail = contact.getEmail();

Kotlin - Classe Contact

internal data class Contact(var firstName: String, var lastName: String, var email: String?)

Kotlin - Modifier ou récupérer l’email

var contact = Contact("John", "Doe", "mail@ippon.fr")
contact.email = "nouveau_mail@ippon.fr"
var myEmail = contact.email

7. Les interfaces

Les interfaces de Kotlin sont très similaires à Java. Elles peuvent contenir des déclarations de méthodes abstraites, ainsi que des implémentations de méthodes.

Contrairement aux classes abstraites, les interfaces ne peuvent pas stocker l'état. Elles peuvent avoir des propriétés mais celles-ci doivent être abstraites ou fournir des implémentations d'accesseurs.

Kotlin

interface MyInterface {
    val name: String // abstract
    val age: Int
        get() = 25

    fun foo() {
        println("$name")
    }
}

interface MySecondInterface {
    fun foo() {
        println("Hello world")
    }    
    fun bar() {
        println("Good night")
    }
}

class Person : MyInterface, MySecondInterface {
    override val name: String = "Joe"
    
    override fun foo() {
        super<MyInterface>.foo()
        super<MySecondInterface>.foo()
    }
}

fun main(args: Array<String>) {
    val person = Person()
   
    println(person.name)
    println(person.age)
    println("---")
    println(person.foo())
    println(person.bar())
}

Sortie console

Joe
25
---
Joe
Hello world
Good night

8. ConstraintLayout

L’arrivée de Kotlin signe l'utilisation (recommandée) d’Android Studio 3 et par conséquent, de ConstraintLayout.

Cela vous permet de créer des mises en pages complexes sans imbriquer des groupes de vues les uns dans les autres. C’est assez similaire au RelativeLayout car les vues sont organisées en fonction des relations entre les vues voisines et de la mise en page parente. ConstraintLayout étant plus souple que RelativeLayout, il est également plus facile à utiliser grâce à l’éditeur de mise en page Android Studio.

Exemple :

sans-constraintlayout-copie
LinearLayout

avec-constraintlayout-copie
ConstraintLayout (on peut voir les relations entre les vues)

Vous pouvez en découvrir un peu plus sur ConstraintLayout sur le site Android Developers.

Convertir du code Java en Kotlin

Le tutoriel “Taking advantage of Kotlin” de Codelabs vous permet de convertir votre code Java en Kotlin tout en étant guidé avec des explications et des astuces.

Rien de plus simple pour convertir un fichier. Dans Android Studio, ouvrez le fichier que vous souhaitez convertir puis exécutez l’action suivante.

Code > Convert Java File to Kotlin File
cenversion

C’est magique, votre code Java est devenu du code Kotlin. Un fois converti, il faut bien sûr y jeter un coup d’oeil afin d’y apporter les optimisations spécifiques au langage Kotlin. Même un simple copier/coller d’une classe Java vers Kotlin convertira automatiquement votre code.

Des info-bulles sont là pour vous aider, n'hésitez donc pas à regarder ce qu’elles proposent.
tips

Dans le cas ci-dessus, l’aide propose de simplifier la boucle for avec la fonction mapTo() de Kotlin Standard Library. Cette méthode permet, dans notre exemple, de simplifier la sauvegarde de la liste de Contact.

Avec la boucle for

val contacts = ArrayList<Contact>()
for (contactString in contactSet) {
   contacts.add(Gson().fromJson(contactString, Contact::class.java))
}

Avec la fonction mapTo()

val contacts = contactSet.mapTo(ArrayList<Contact>()) { Gson().fromJson(it, Contact::class.java) }

Conclusion

Kotlin permet d’éclaircir et d’alléger le code, et donc d’en améliorer la maintenabilité, voire d’augmenter la vélocité des développeurs. Il a pour but d’éviter les limitations du langage Java en mettant en place divers dispositifs qui facilitent la vie des développeurs. On remarque notamment la disparition des NullPointerException, l’inférence de type, les déclarations déstructurées, les fonctions d'extensions …

La migration des applications Android (natives) de Java vers Kotlin va pouvoir être effectuée en douceur grâce à la compatibilité des deux langages et la possibilité d’une conversion des fichiers.

De nombreux projets open source et librairies sont disponibles ici.

Sources