⚙Introduction to Kotlin (French)

⚙Introduction to Kotlin (French)

Notes from Openclassrooms course

Premier pas en Kotlin

Histoire de Kotlin

  • created by JetBrain 2010
  • concis, sur, pragmatique et 100% interopérable avec Java
  • Kotlin peut être utilisé dans la création d’application mobile native sur Android, mais également dans la création d’application web (J2EE)

image.png

Les classes java et kotlin compilé par le compilateur sont identique et donc lisible par la jvm

statiquement typé : chaque variable d’un programme est connu au moment de la compilation.

val zero = 0
val message = "Hellow world !"
fun sayHello() = "Hello everyone !"

C'est le compilateur Kotlin, qui va "déduire" automatiquement le type de vos variables. On appelle cela, "l’inférence de type" (ou "type inference" en anglais)

Pour résumer, un langage "statiquement typé" comme Kotlin (ou Java) présente plusieurs avantages majeurs, comme :

  • Performance : Comme les types sont connus au moment de la compilation, pas besoin de les "déduire" au moment de l’exécution du programme.
  • Fiabilité : Le compilateur vous indique beaucoup plus d’erreurs au moment de la compilation, minimisant les chances que votre application crashe durant son exécution.
  • Efficacité : Un langage statiquement typé permet de réaliser beaucoup plus facilement des "refactoring" et permet également la mise en place d’outils performants au sein de l’IDE comme "l’autocomplétion".

aussi un langage dit "fonctionnel".

  • First-class function : fonctions en tant que valeurs. Nous pourrons par exemple, stocker des fonctions dans des variables, passer en paramètres des fonctions, ou encore retourner des fonctions ! des lambdas
  • Immuabilité : L’approche fonctionnelle encourage l’utilisation de variables dites "immuables", c’est-à-dire que leur état ne pourra pas changer après leur création

Cette technique permet d’améliorer la fiabilité et les performances d’un programme, en le rendant notamment résistant aux problèmes liés au multi-threading

Déclarez et initialisez des variables

  • val sera obligatoirement immuable
  • Vous devez initialiser ce genre de variable dans le même bloc de code que là où vous l’avez déclarée

Exercice

private lateinit var answer: String
private val question = "What is your name ?"

fun main(args: Array<String>) {
  println(question)
  answer = "Leo"
  println("Your name is $answer")
}

Implementez differentes fonctions

En Kotlin, if est une expression et non pas une instruction comme en Java. D’ailleurs, en Java, toutes les structures de contrôles sont des instructions !

une expression est toute partie de votre code qui peut retourner une valeur. Ainsi, 1+1 est une expression, maxOf(5, 7) est une expression.

une instruction est un bloc de code qui ne retourne aucune valeur. Par exemple, var username = "Phil" est une instruction

https://lh4.googleusercontent.com/gLL8OfVQAL3KWD3LfXtB8JVeBftkftMzntOw7ue7Ixu5JWzyQOfBJBYGQs-g8S7uSZDOrcnXmIa0FLnTLbU6cXDs9-r4W-6tqnBmHMOPx-vegGkzd0Iq_eqPiQCHnoJCiJu3HFhQ

https://lh4.googleusercontent.com/eu57r8g69zPfMlfMG-lWr-s7ZRnnRnLL6alyVN8CeDHArmoAluXN1WgCVwL1NFRRUsY6YOrUgxVb1YD9IfwuthJ85QI3fwVx6jB70xzRNP3wgGUXuAij0o-vBlr53ru3gnatL6Nu

Exercice

fun main(args: Array<String>) {
  println("Hello Openclassrooms students !")
  println(getResult(1,2))
  println(getUsernameUpperCase("Sam"))
  println(isUsernameOfTeacher("Phil"))
  println(isUsernameOfTeacher("Ambroise"))
  println(isUsernameOfTeacher("Andre"))
}

fun getResult(a:Int, b:Int) = a + b
fun getUsernameUpperCase(username:String) = username.toUpperCase()
fun isUsernameOfTeacher(username:String) = if(username == "Phil" || username == "Ambroise") true else false
fun isUsernameOfTeacher(username: String) = username == "Phil" || username == "Ambroise"

Generez vos premiere classes

  • private : Un membre déclaré comme private sera visible uniquement dans la classe où il est déclaré.
  • protected : Un membre déclaré comme protected sera visible uniquement dans la classe où il est déclaré ET dans ses sous-classes (via l’héritage).
  • internal : Un membre déclaré comme internal sera visible par tous ceux du même module. Un module est un ensemble de fichiers compilés ensemble (comme une librairie Gradle ou Maven, par exemple).
  • public : Un membre déclaré comme public sera visible partout et par tout le monde.

Exercice

fun main(args: Array<String>) {
  println("Hello Openclassrooms students !")
  val kotlinForTroll = Course("KT0", "Kotlin for troll - Be a better troll.", 10, false)
  println("Check out my new course : ${kotlinForTroll.title}")
}

class Course(private val id: String, var title: String, val duration: Int, var isActive: Boolean)

Decouvrez les structures de controle en Kotlin

Gerez des choix et des conditions

image.png

image.png

Exercice

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("${mult(8,10)}")
val c = Color.RED
writeName(c)
}

enum class Color(val label:String){
BLUE(label="BLUE"),
RED(label="RED"),
YELLOW(label="YELLOW")
}

fun writeName(c:Color) = println(c.label)

fun mult(a:Int, b:Int) =
if(a<b){
a*10
}else {
b*10
}

- --

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("Min of those two numbers : ${minOf(10, 11)}")
println("Red color to string: ${colorToString(Color.RED)}")
}

// --- FUNCTIONS ---

private fun minOf(a: Int, b: Int) = (if (a < b) a else b) * 10

private fun colorToString(color: Color) = when (color) {
Color.RED -> "RED"
Color.BLUE -> "BLUE"
Color.GREEN -> "GREEN"
}

// --- ENUM ---

private enum class Color { RED, BLUE, GREEN }

Iterez grace aux boucles

La boucle for s’utilise désormais sous la forme in

Nous allons ainsi pouvoir appeler les méthodes génériques suivantes :

  • listOf: Permet de créer une liste d’éléments ordonnée et immuable.
  • mutableListOf: Permet de créer une liste d’éléments ordonnée et muable.
  • setOf: Permet de créer une liste d’éléments désordonnée et immuable.
  • mutableSetOf: Permet de créer une liste d’éléments désordonnée et muable.

Euh, quelle est la différence entre list et set, déjà ?

Une liste de type list contiendra des éléments uniques et/ou différents de manière ordonnée. Ses éléments seront accessibles grâce à un index. À l’inverse, une liste de type set contiendra des éléments uniques et distincts de manière non ordonnée. Ses éléments ne pourront pas être accessibles via un index.

Exercice

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
while20Items()
val t = listOf("a", "b", "c")
showArrayContent(t)
}

// FUNCTION

fun while20Items() {
var i = 20
while(i>0){
i--
println(i)
}
}

fun showArrayContent(t:Collection<String>){
for(e in t) {
println(e)
}
}

- --

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
while20Items()
println("-------------------------------")
showArrayContent(arrayOf("K", "O", "T", "L", "I", "N"))
}

// ---

private fun while20Items(){
var i = 0
while (i < 20){
println("$i")
i++
}
}

private fun showArrayContent(strings: Array<String>){
for (string in strings) print(string)
}

Decouvrez le Smart Cast

en Kotlin, le cast est implicite avec le mot-clé is : c’est ce que l’on appelle le Smart Cast

Nous convertissons ici le contenu de la variable anyObject en une variable de type String grâce au mot-clé as : ce type de conversion est appelée "Conversion non sécurisée" (ou "Unsafe cast" en anglais).

Exerice

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
guessTheType('c')
guessTheType("c")
guessTheType(1)
guessTheType(true)
guessTheType(listOf("a","b","c"))
guessTheType(arrayOf("a","b","c"))
}

fun guessTheType(o:Any) = when(o){
is Int->println("int $o")
is String->println("string $o")
is List<*>->println("list $o")
is Boolean->println("bool $o")
is Array<*>->println("array $o")
else->println("other $o")
}

//---

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
guessTheType(10)
guessTheType("Troll")
guessTheType(arrayOf(1, 2))
guessTheType(listOf(1, 2))
guessTheType(false)
guessTheType(9.0) // It's a Double...
}

private fun guessTheType(any: Any) = when (any){
is Int -> println("It's an Integer !")
is String -> println("It's a String !")
is Array<*> -> println("It's an Array !")
is List<*> -> println("It's a List !")
is Boolean -> println("It's a Boolean !")
else -> println("Error ! Type not recognized...")
}

Maitrisez les exceptions

en Kotlin, le try/catch ainsi que le throw sont des expressions !

On notera que les propriétés de la classe User sont toutes susceptibles de contenir une valeur nulle

Cette fonction ne retournant rien du tout, si ce n’est une exception, son type de retour sera alors Nothing .

Exercice

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")

try{
println("-1 = ${isUserOld(-1)}")
}catch(e: Exception){
println(e.message)
}

try{
println("101 = ${isUserOld(101)}")
}catch(e: Exception){
println(e.message)
}
println("true = ${isUserOld(70)}")
println("false = ${isUserOld(40)}")
}

fun isUserOld(age:Int):Boolean{
if(age<0)throw IllegalArgumentException("too young")
if(age>100)throw IllegalArgumentException("too old")
if(age<65) return true
else return false
}

//---

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
println("Is user is old ? ${isUserOld(64)}")
println("Is user is old ? ${isUserOld(67)}")
println("Is user is old ? ${isUserOld(-2)}")
println("Is user is old ? ${isUserOld(102)}")
}

// ---

private fun isUserOld(age: Int) = when {
age > 100 -> throw Exception("too old !")
age > 65 -> true
age < 0 -> throw Exception("too young !")
else -> false
}

Maitrisez la puissance de Koltin

Ameliorer vos fonctions

Compatibilité avec Java : Les valeurs par défaut de paramètres n’étant pas compatibles avec Java, vous devrez ajouter l’annotation @JvmOverloads devant la méthode souhaitée afin que le compilateur puisse générer automatiquement les méthodes de surcharges pour Java.

Comme nous avons pu le voir dans la première partie de ce cours, Kotlin essaie d’être un langage de programmation le plus fonctionnel possible. Ainsi, nous allons pouvoir créer des fonctions dites "locales", c’est-à-dire se trouvant à l’intérieur d’une autre fonction.

image.png

Decouvrez les extensions

Une extension se déclare dans un fichier à part(1), que l’on nomme généralement d’après le nom de la classe que l’on souhaite étendre, suivi du mot "Extensions" ou "Ext".

Sachez également qu’une extension ne pourra pas être surchargée : impossible donc d’utiliser le mot-clé override pour modifier le comportement d’une extension déjà créée.

Exercice

import stringExt.toPlural

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
var word:String = "table"
println("${word.toPlural()}")
}

package stringExt
fun String.toPlural() = this + "s"

Enrichissez vos classes

toutes les classes et méthodes sont "fermées" en Kotlin

D’ailleurs, voici un petit récapitulatif des modificateurs d’accès disponibles en Kotlin (à ne pas confondre avec les modificateurs de visibilité vus dans un précédent chapitre !) :

  • final : Classe/Méthode/Propriété ne pouvant pas être redéfinie. C’est l’état par défaut de tous les éléments en Kotlin.
  • open : Classe/Méthode/Propriété pouvant être redéfinie. Ce modificateur d’accès doit être indiqué explicitement.
  • abstract : Classe/Méthode/Propriété devant être redéfinie. Ce modificateur d’accès peut être utilisé uniquement dans des classes abstraites.

Et bien entendu, le mot-clé override sera utilisé pour redéfinir un élément d’une classe parente (ou d’une interface).

  • Afficher son contenu sous forme de texte via la méthode toString() (dans une console de log par exemple)
  • Comparer deux objets de même type (via la méthode equals() de sa superclasse)
  • Dupliquer un objet de manière distincte en un second objet (via la méthode copy() ou clone() de sa superclasse)

En Java, cela nous obligeait à redéfinir les méthodes toString() , hashCode() , equals() et copy() dans le corps de notre classe

le mot-clé static n’existe plus

Singleton

Sachez également que le Singleton créé grâce au simple mot-clé object sera naturellement "thread safe"... Pas besoin de gérer ce cas si particulier dans notre code !

Exercice

open class Button(var title:String="init", var bgColor:String="#FFF") {
}

class CircleButton(title:String="cb", bgColor:String="#FFF", var radius:Int=10):Button(title,bgColor){
}

data class User(var email:String, var password:String,var urlImage:String?){
companion object {
fun newInstance(email:String, password:String) = User(email, password, null)
}
}

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")

var u1 = User("user1", "password1", "image1")
println("u1.email = ${u1.email}")
u1.email = "emailChanged"
println(u1)

var u2 = User.Companion.newInstance("user2", "password2")
println(u2)

var b = Button()
b = Button("title")
b = Button(bgColor="color")
b = Button("title2","color2")
var cb = CircleButton()
cb = CircleButton("title")
cb = CircleButton(bgColor="color")
cb = CircleButton(radius=20)
cb = CircleButton("title2","color2",30)
}

// ---

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
val user = User("toto@gmail.com", "azerty", "https://url.com")
println(user)
val button = Button()
val circleButton = CircleButton()
}

// ---

data class User (var email: String,
var password: String,
var urlImage: String){
companion object {
fun newInstance(email: String, password: String, urlImage: String) = User(email, password, urlImage)
}
}

// ---

open class Button(var title: String = "button",
var bgColor: String = "#FFF")

class CircleButton(_title: String = "circleButton",
_bgColor: String = "#FFF",

var radius: Float = 1.0f): Button(_title, _bgColor)

Tirez parti des lambdas

Maintenant, nous allons créer une fonction pouvant contenir en paramètre une autre fonction : on appelle ce genre de fonction les "fonctions d’ordre supérieur" (ou "Higher-Order Functions", en anglais).

Le mot-clé inline va permettre d’indiquer à Kotlin de ne pas créer d’objet Function représentant la lambda, mais plutôt de déplacer le code de celle-ci directement là où elle sera appelée

Ainsi, de manière générale, si vous décidez de créer une "fonction d’ordre supérieur" dans votre programme, pensez toujours à rajouter le mot-clé inline pour optimiser la mémoire de votre programme.

Exercice

inline fun List<User>.maxUser(selector: (User) -> Int): User {
var maxUser = this.first()
var maxValue = selector(maxUser)
var possibleMaxValue: Int

for (user in this) {
possibleMaxValue = selector(user)
if (possibleMaxValue > maxValue) {
maxValue = possibleMaxValue
maxUser = user
}
}

return maxUser
}

fun main(args: Array<String>) {
println("Hello Openclassrooms students !")
val users: List<User> = listOf(
User("toto@gmail.com", 20, 2000),
User("hello@gmail.com", 18, 0),
User("oc@gmail.com", 35, 1000))
println(users.maxUser { it.experience })
println(users.maxUser { it.age })
}

Entraînez-vous à développez une API en Kotlin

créer une application web en Kotlin : Ktor

Ainsi, votre mission est de créer une application web, OCWebServer, qui devra :

  • Fonctionner en local et répondre à l'adresse http://127.0.0.1:8080/
  • Implémenter la navigation suivante :
    • / : Afficher le message : "Welcome to OpenClassrooms brand new server !"
    • /course/top : Afficher une réponse Json correspondant au meilleur cours sorti par OpenClassrooms
    • /course/{id} : Afficher les informations d'un cours. Pour la démo, vous devrez gérer uniquement les cours possédant l'identifiant 1, 2 et 3. Vous renverrez un message d'erreur si d'autres identifiants sont renseignés.

Attention, toutefois, vous devrez respecter les règles suivantes dans votre code :

  • Utilisez au moins une fois une extension.
  • Créer au moins une classe data.