
copyToRecursively()
et deleteRecursively(), ...
val number = 123
val message = "Hello world !"
fun sayHello() = "Hello world !"
kotlin-compiler-1.8.21.zip./bin dans le path pour que cela soit plus pratique.hello.kt :notepad hello.kt
fun main() {
println("Hello, World!")
}
Compilons notre code :
kotlinc hello.kt -include-runtime -d hello.jar
Nous pouvons le lancer maintenant :
java -jar hello.jar
kotlin-native-windows-1.8.21.zip./bin dans le path pour que cela soit plus pratique.hello.kt :notepad hello.kt
fun main() {
println("Hello, World!")
}
Compilons notre code :
kotlinc hello.kt
Nous pouvons le lancer un executable maintenant :
program.exe
Le premier lancement est long, mais les suivants sont rapides.
kotlinc.
Testons quelques commandes, par exemple :
40+2
"Hello, World!"
println("Hello, World!")
1
1.0
fun main() {
println("Hello, World!")
}
org.example.kotlin, alors les fichiers seront placés
directement dans le répertoire racine contenant les sources.org.example.kotlin.network.socket
seront placés dans le sous répertoire : network/socket.
.kt.main, puis la touche TAB, pour lancer l'autocomplétion.
fun main() {
println("Bonjour le monde")
}
abstract class Foo { ... }
class FooImpl: Foo { ... }
fun Foo(): Foo { return FooImpl(...) }
`. (Note : cela ne fonctionnera
pas
en Android)._ sont aussi autorisés :
class MyTestCase {
@Test fun `ensure everything works`() { ... }
@Test fun ensureEverythingWorks_onAndroid() { ... }
}
if, for ou
catch.
else.->....,.//.const, les propriétés top level ou propriétés
val d'un objet sans fonction get
custom, qui contient une valeur profondément immuable), doit utiliser des majuscules, et _ comme
séparateur :
const val MAX_COUNT = 8
val USER_NAME_FIELD = "UserName"
Les fonctions top level ou les propriétés d'un objet dont les valeurs peuvent évoluer, utiliserons la
notation camel-case :
val mutableCollection: MutableSet<String> = HashSet()
Le nom des propriétés qui contiennent une référence à un objet Singleton, peuvent utiliser la même règle
de nommage :
val PersonComparator: Comparator<Person> = /*...*/
Pour les valeurs des enums, il est possible d'utiliser les majuscules séparées par des _, ou l'écriture
camel-case, en commençant par une majuscule, selon l'usage.
class C {
private val _elementList = mutableListOf<Element>()
val elementList: List<Element>
get() = _elementList
}
List, PersonReader.close,
readPersons. Le nom doit aussi faire
comprendre s'il modifie l'objet ou s'il en retourne un nouveau. Par exemple : sort modifie
la collection, alors que sorted retournera
une copie de la collection triée.Manager,
Wrapper, etc.IOStream), mais ne mettez que la première lettre
en majuscule s'il est plus long (XmlFormatter, HttpInputStream).
public / protected / private / internal
expect / actual
final / open / abstract / sealed / const
external
override
lateinit
tailrec
vararg
suspend
inner
enum / annotation
companion
inline
infix
operator
data
Placer les annotations avant les modifieurs :
private val foo: Foo
val message = "Hello world !"
val message: String = "Hello world !"
var message: String = "Hello world !"
val name: String = "Tristan"
val age: Int = 41
val isDeveloper: Boolean = true
Est équivalent à :
val name = "Tristan"
val age = 41
val isDeveloper = true
import kotlin.random.Random
fun isUserHappy() = Random.nextInt(0, 100) % 2 == 0
fun main() {
val message: String
if (isUserHappy())
message = "That's great"
else
message = "What's going on?"
println(message)
}
var name: String = "Tristan"
name = null
Alors que le code suivant est correct.
var name: String? = "Tristan"
name = null
var name: String? = "Tristan"
name.toUpperCase()
Il faut donc prendre une précaution en utilisant cette variable, en utilisant ? :
var name: String? = "Tristan"
name?.toUpperCase()
Si la valeur de la variable contient null, alors la méthode ne sera pas appelée.
$, par exemple :
val name = "Tristan"
println("Hello $name")
Référence vers les chaînes de caractère.
const.
public static final String SERVER_URL = "http://my.api.com/";
Deviendra en Kotlin :
const val SERVER_URL = "http://my.api.com/"
| Type | Size (bits) | Min value | Max value |
|---|---|---|---|
| Byte | 8 | -128 | 127 |
| Short | 16 | -32768 | 32767 |
| Int | 32 | -2,147,483,648 (-231) | 2,147,483,647 (231 - 1) |
| Long | 64 | -9,223,372,036,854,775,808 (-263) | 9,223,372,036,854,775,807 (263 - 1) |
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
| Type | Size (bits) | Significant bits | Exponent bits | Decimal digits |
|---|---|---|---|---|
| Float | 32 | 24 | 8 | 6-7 |
| Double | 64 | 53 | 11 | 15-16 |
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
printDouble
(sans toucher au code de la
fonction).
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
// printDouble(i) // Error: Type mismatch
// printDouble(f) // Error: Type mismatch
}
fun main() {
fun printDouble(d: Double) { println(d) }
val i = 1
val d = 1.1
val f = 1.1f
printDouble(d)
printDouble(i.toDouble())
printDouble(f.toDouble())
}
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
fun check(c: Char) {
if (c == 1) { // ERROR: incompatible types
// ...
}
}
Pour écrire un caractère, on utilise la simple "quote" '.
Exemple : 't'.
Les caractères spéciaux sont préfixés par \.
Exemple : \t, \b, \n, \r, \', \", \\ and \$.
Pour les autres caractères, on peut utiliser la syntaxe Unicode : '\uFF00'.
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicit conversions to numbers
}
get et set[] grâce à la convention de surcharge des opérateurs),
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
arrayOf(1, 2, 3) permet de créer des tableaux, arrayOfNulls() va
créer un tableau de valeurs
nulles.Array prend en paramètre le nombre d'éléments et la fonction pour remplir
le tableau.
var array = arrayOf(1, 2, 3)
array.forEach { println(it) }
var arrayOfNull: Array<Int?> = arrayOfNulls(5)
arrayOfNull.forEach { println(it) }
val asc = Array(5) { i -> (i * i).toString() }
asc.forEach { println(it) }
[] est équivalent à l'appel des méthodes get() et
set().
var array = arrayOf(1, 2, 3)
println(array[2])
array[0] = 4
array.forEach { println(it) }
val oddArray = TODO()
fun main() {
val oddArray = Array(11) { i -> (i * 2) }
for(i in oddArray) {
print("$i ")
}
}
val alphabetArray = TODO()
fun main() {
val alphabetArray = Array(26) { i -> (i+'a'.toInt()).toChar() }
for (i in alphabetArray) {
print("$i ")
}
}
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// Tableau d'int de taille 5 contenant les valeurs [0, 0, 0, 0, 0]
val arr = IntArray(5)
// Exemple : initialisation des valeurs contenues dans le tableau avec une constante
// Tableau d'int de taille 5 contenant les valeurs [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }
// Exemple : initialisation des valeurs contenues dans le tableau en utilisant une lambda (fonction)
// Tableau d'int de taille 5 contenant les valeurs [0, 1, 2, 3, 4] (valeurs égales à leur position dans le tableau)
var arr = IntArray(5, { it * 1 })
[].+, même si l'on préférera les templates ($ dans les chaînes de caractères).
val s = "abc" + 1
println(s + "def")
val s = "abc" + 1
println("${s}def")
")""")
val stringWithEscape = "Hello, world!\n"
println(stringWithEscape)
val stringRaw = """
for (c in "foo")
print(c)
"""
println(stringRaw)
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
println(text)
$.
val i = 10
println("i = $i") // affiche "i = 10"
Il est possible d'inclure une expression entre ${}, et d'afficher le symbole $ lui-même. Cela fonctionne
aussi dans une chaîne brute (raw string).
println("$name.length is ${name.length}")
println("price: ${'$'}9.99")
val price = """
${'$'}9.99
"""
hello qui doit retourner : Hello, <NAME>
fun hello(name: String): String = TODO()
fun main() {
fun hello(name: String): String = "Hello, ${name.toUpperCase()}"
print(hello("Tristan"))
}
/*
Les commentaires sur plusieurs
lignes, pour pouvoir bien s'exprimer.
Vous pouvez écrire autant que vous voulez.
*/
// ceci est un commentaire uniligne.
if est une expression : il retourne une valeur. De ce fait l'opérateur
ternaire ? n'existe
plus.
// Usage traditionnel
var max = a
if (a < b) max = b
// Avec else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// Comme une expression
val max = if (a > b) a else b
if, peuvent être des blocs, dont la dernière expression est la valeur du
bloc :
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
Quand le if est utilisé comme expression (pour retourner une valeur ou pour assigner une
valeur),
l'expression doit obligatoirement comporter la branche else.
when est l'équivalent du switch en Java.
var apiReponse = 404
when (apiReponse) {
200 -> "OK"
404 -> "NOT FOUND"
401 -> "UNAUTHORIZED"
403 -> "FORBIDDEN"
else -> "UNKNOWN"
}
Quand le when est utilisé comme expression (pour retourner une valeur ou pour assigner une valeur),
l'expression doit obligatoirement comporter la branche else, sauf si tous les cas sont couverts (d'un
enum par exemple).
var apiReponse = 404
fun printResponse(apiReponse: Int) = when (apiReponse) {
200 -> print("OK")
404 -> print("NOT FOUND")
401 -> print("UNAUTHORIZED")
403 -> print("FORBIDDEN")
else -> print("UNKNOWN")
}
printResponse(apiReponse)
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}
Les conditions peuvent être des expressions :
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
when (x) {
in 1..10 -> print("x is in the range")
in validNumbers -> print("x is valid")
!in 10..20 -> print("x is outside the range")
else -> print("none of the above")
}
On peut aussi tester le type du paramètre :
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
On peut l'utiliser sans paramètre, les conditions sont alors obligatoirement des booléens,
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")
}
fun Request.getBody() =
when (val response = executeRequest()) {
is Success -> response.body
is HttpError -> throw HttpException(response.status)
}
while est exactement la même qu'en Java :
var isRaining = true
while (isRaining){
println("I don't like rain.")
}
do {
println("I don't like rain.")
} while (isRaining)
for, peut itérer sur tout ce qui fournit un itérateur, sa syntaxe est la suivante :
for (item in collection) print(item)
Par exemple sur une liste de chaînes de caractères, cela donne :
val names = listOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
for(name in names) {
println("This developer rocks: $name")
}
rangeTo(),
correspondant à l'opérateur ...in ou !in. Exemple :
if (i in 1..4) { // equivalent à 1 <= i && i <= 4
println(i)
}
Pour définir un intervalle, souvent utilisés dans les boucles for, la syntaxe est la suivante :
for (i in 1..4) println(i)
Pour utiliser l'ordre décroissant, la syntaxe sera :
for (i in 4 downTo 1) println(i)
for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)
Pour ne pas inclure le dernier élément de la liste, utiliser la fonction until :
for (i in 1 until 10) { // i in [1, 10), 10 is excluded
print(i)
}
for (i in array.indices) {
println(array[i])
}
Ou alors en utilisant le format suivant qui met en œuvre la déstructuration (destructuring) :
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")
}
une chaîne de caractères.
var stringValue = "Une chaîne de caractères"
Le résultat attendu est :U n e c h a î n e d e c a r a c t è r e s
var stringValue = "Une chaîne de caractères"
for (c in stringValue) {
print("$c ")
}
Solution bis :
var stringValue = "Une chaîne de caractères"
stringValue.forEach { print("$it ")}
Solution ter :
var stringValue = "Une chaîne de caractères"
stringValue.toCharArray().joinToString(" ")
Référence
joinToString.
fun main(args: Array<String>) {
for (x in 0 until 10) {
when {
(x % 3 == 0 && x % 5 == 0) -> println("FizzBuzz")
(x % 3 == 0) -> println("Fizz")
(x % 5 == 0) -> println("Buzz")
else -> println(x)
}
}
}
fun main(args: Array<String>) {
for (x in 0 until 10) {
println( when {
(x % 3 == 0 && x % 5 == 0) -> "FizzBuzz"
(x % 3 == 0) -> "Fizz"
(x % 5 == 0) -> "Buzz"
else -> x
})
}
}
val numbers = mutableListOf("one", "two", "three", "four")
numbers.add("five") // this is OK
//numbers = mutableListOf("six", "seven") // compilation error
List et Set :
val listOfNames = listOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
listOfNames[0]
//listOfNames[0] = "Mathieu NEBRA" // Error: List is immutable
val mutableListOfNames = mutableListOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
mutableListOfNames[0]
mutableListOfNames[0] = "Mathieu NEBRA" // OK
val setOfNames = setOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
setOfNames.first()
//setOfNames.add("Mathieu NEBRA") // Error: Set is immutable
val mutableSetOfNames = mutableSetOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
mutableSetOfNames.first()
mutableSetOfNames.add("Mathieu NEBRA") // OK
Exemple de tableau et de Map :
var arrayOfNames = arrayOf("Jake WHARTON", "Joe BIRCH", "Robert MARTIN")
var mapOfNames = mapOf(0 to "Jake WHARTON", 1 to "Joe BIRCH", 2 to "Robert MARTIN")
package org.example
fun printMessage() { /*...*/ }
class Message { /*...*/ }
// ...
Tout le contenu (tels que les classes et les fonctions) du fichier de code source appartiendront au
package déclaré. Dans l'exemple ci-dessus, le nom complet de printMessage() est org.example.printMessage(),
de la même manière, le nom complet de
Message est org.example.Message.import :
import org.example.Message // Message is now accessible without qualification
Il est aussi possible d'importer tout un groupe de classes avec l'utilisation de
* :
import org.example.* // everything in 'org.example' becomes accessible
as :
import org.example.Message // Message is accessible
import org.test.Message as TestMessage // TestMessage stands for 'org.test.Message'
import est aussi utilisé pour importer d'autres types que des classes, telles
que :ENUMUtils pour nommer un fichier source, qui apporte peu
d'informations sur son
contenu.
until au lieu d'un intervalle ouvert :
for (i in 0..n - 1) { ... } // bad
for (i in 0 until n) { ... } // good
fun double(x: Int): Int {
return 2 * x
}
val result = double(2)
Stream().read() // créé une instance de la class Stream et appelle la fonction read()
fun double(x: Int): Int = x * 2
Il n'est même pas nécessaire de préciser le type de retour, qui est inféré par le compilateur :
fun double(x: Int) = x * 2
fun powerOf(number: Int, exponent: Int) { /*...*/ }
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*...*/ }
open class A {
open fun foo(i: Int = 10) { /*...*/ }
}
class B: A() {
override fun foo(i: Int) { /*...*/ } // No default value authorized
}
"default" :
fun upper(): String = TODO()
fun upper(str:String? = "default") = str?.toUpperCase()
Dans ce cas, si l'on précise une valeur null, c'est cette valeur qui est utilisée et la méthode
n'affichera rien.
fun upper(str:String = "default") = str.toUpperCase()
add qui additionne 2 Int passés en paramètre :
fun add(a: Int, b: Int): Int = TODO()
fun add(a: Int, b: Int): Int = a + b
fun strEq(s1: String, s2: String): Boolean = TODO()
fun strEq(s1: String, s2: String, ignoreCase: Boolean): Boolean = TODO()
fun strEq(s1: String, s2: String) = s1.equals(s2)
fun strEq(s1: String, s2: String, ignoreCase: Boolean) = if(ignoreCase) s1.toUpperCase().equals(s2.toUpperCase()) else s1.equals(s2)
fun main() {
println(strEq("Tristan", "TRISTAN"))
println(strEq("Tristan", "TRISTAN", true))
}
Solution bis :
fun strEq(s1: String, s2: String, ignoreCase: Boolean): Boolean = s.equals(p, ignoreCase)
Référence : equals.
Unit indique que la méthode ne retourne pas de valeur, c'est l'équivalent
à void en Java.
val languages = arrayOf("Java", "JavaScript", "Go", "Kotlin")
fun printLanguages(): Unit = TODO()
val languages = arrayOf("Java", "JavaScript", "Go", "Kotlin")
fun printLanguages(): Unit { for (language in languages) println(language) }
fun main() {
printLanguages()
}
Solution bis :
fun printLanguages() { languages.forEach { println(it) } }
Solution ter :
fun printLanguages() = languages.forEach { println(it) }
fun tenFirstNumber(): Unit = TODO()
fun countdown(): Unit = TODO()
fun firstEvenNumbers(): Unit = TODO()
fun firstOddNumbers(): Unit = TODO()
fun tenFirstNumber(): Unit { for(i in 0 until 10) print("$i "); println()}
fun countdown(): Unit { for(i in 10 downTo 0) print("$i "); println()}
fun firstEvenNumbers(): Unit { for(i in 0 until 20 step 2) print("$i "); println()}
fun firstOddNumbers(): Unit { for(i in 1 .. 20 step 2) print("$i "); println()}
fun main() {
tenFirstNumber()
countdown()
firstEvenNumbers()
firstOddNumbers()
}
fun tenFirstNumber() = (0..9).forEach { println(it) }
fun countdown() = (10 downTo 0).forEach { println(it) }
fun firstEvenNumbers() = (1..20 step 2).forEach { println(it) }
fun firstOddNumbers() = (0..9).forEach { println(it * 2) }
fun main() {
tenFirstNumber()
countdown()
firstEvenNumbers()
firstOddNumbers()
}
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
/*...*/
}
reformat(str)
Nous pouvons préciser tous les paramètres :
reformat(str, true, true, false, '_')
L'appel avec tous les paramètres nommés est bien plus clair :
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
fun foo(bar: Int = 0, baz: Int) { /*...*/ }
foo(baz = 1) // La valeur par défaut bar = 0 est utilisée
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*...*/ }
foo(1) { println("hello") } // Utilise la valeur par défaut baz = 1
foo(qux = { println("hello") }) // Utilise les valeurs par défaut bar = 0 et baz = 1
foo { println("hello") } // Utilise les valeurs par défaut bar = 0 et baz = 1
reformat(str, wordSeparator = '_')
Il est possible de mixer les paramètres de position et les paramètres nommés :
f(1, y = 2)
Mais l'ordre est important : il est impossible d'utiliser un paramètre nommé puis un paramètre de
position :
f(x = 1, 2)
... pour définir un nombre variable de
paramètres.
En Kotlin, c'est le mot clé vararg qui est utilisé (pour le
dernier paramètre généralement, autrement, il faudra nommer les paramètres).
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
Un seul paramètre peut être marqué variable.
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
infix fun Int.shl(x: Int): Int { ... }
// appel de la méthode en utilisant la notation infix
1 shl 2
// est la même chose que
1.shl(2)
fun(x: Int, y: Int): Int = x + y
Une fonction anonyme ressemble à une fonction classique, sauf le que le nom est omis. Le corps de la
fonction peut aussi être un bloc :
fun(x: Int, y: Int): Int {
return x + y
}
Si le type des paramètres peut être inféré par le compilateur, il n'est pas nécessaire de le préciser,
comme dans l'exemple :
ints.filter(fun(item) = item > 0)
tailrec et répond au format requis, le
compilateur optimise la récursion, en générant une boucle rapide et optimisée à la place :
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
Référence
: "Tail recursive functions".
Math.cos plusieurs fois, en partant de 1.0 jusqu'à ce que le
résultat ne
change plus, en arrivant au résultat de : 0.7390851331706995 pour la précision
eps.
val eps = 1E-10 // "good enough", could be 10^-15
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (Math.abs(x - y) < eps) return x
x = Math.cos(x)
}
}
findFixPoint pour visualiser le nombre d'appels récursifs
effectués (par exemple en affichant un . à
chaque itération.
val eps = 1E-10 // "good enough", could be 10^-15
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (Math.abs(x - Math.cos(x)) < eps) x else {
print('.')
findFixPoint(Math.cos(x))
}
Unit.
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// "return Unit" or "return" is optional
}
Il n'est même pas obligatoire de préciser ce type retour :
fun printHello(name: String?) {
...
}
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
visited peut être définie comme variable locale :
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
fun isMultiple(operand: Int): Boolean = TODO()
La fonction isMultiple retourne true si la valeur de n est
multiple de operand.
fun isEven(n: Int): Boolean = TODO()
fun isEven(n: Int): Boolean {
fun isMultiple(operand: Int): Boolean = n % operand == 0
return isMultiple(2)
}
fun main() {
println(isEven(2))
println(isEven(3))
}
class Invoice { /*...*/ }
class Empty
class Person constructor(firstName: String) { /*...*/ }
Si le constructeur n'a pas d'annotations, ou de modificateurs de visibilité, le mot clé constructor
n'est pas obligatoire :
class Person(firstName: String) { /*...*/ }
init. Les blocs sont exécutés dans
l'ordre d'apparition dans le code :
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
Les paramètres du constructeur peuvent être utilisés dans les blocs d'initialisation ou pour
initialiser les propriétés déclarées dans le corps de la classe :
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }
valvar
public class User {
// PROPERTIES
private String email;
private String password;
private int age;
// CONSTRUCTOR
public User(String email, String password, int age) {
this.email = email;
this.password = password;
this.age = age;
}
// GETTERS
public String getEmail() { return email; }
public String getPassword() { return password; }
public int getAge() { return age; }
// SETTERS
public void setEmail(String email) { this.email = email; }
public void setPassword(String password) { this.password = password; }
public void setAge(int age) { this.age = age; }
}
En Kotlin son équivalent est :
class User(var email: String, var password: String, var age: Int)
constructor :
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
Si la classe comporte un constructeur primaire, chaque constructeur secondaire doit faire appel au
constructeur primaire, directement ou indirectement via un autre constructeur secondaire. La délégation
à un autre constructeur de la même classe est effectué en utilisant le mot clé this :
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(name: String, parent: Person): this(name) {
parent.children.add(this)
}
}
Note : le code des blocs d'init font partis du constructeur primaire. La délégation au constructeur
primaire est la première action effectuée dans un constructeur secondaire, donc le code de tous les
blocs d'initialiseurs est exécuté avant le corps du constructeur secondaire, même si la classe n'a pas
de constructeur primaire, la délégation est implicite :
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
Une classe non abstraite qui ne déclare pas de constructeur en aura un public, par défaut, ne comportant
pas de paramètre.
class DontCreateMe private constructor () { /*...*/ }
= dans le constructeur :
class Customer(val customerName: String = "")
class User(email: String, var password: String, var age: Int) {
var email: String = email
get() { println("User email read access done."); return field}
set(value) { println("User email write access done."); field = value}
}
class User(var email: String, private var password: String, var age: Int)
new, on utilise directement le nom de la classe :
val user = User("hello@gmail.com", "azerty", 41)
Pour accéder aux champs, la notation à . est utilisée :
val user = User("hello@gmail.com", "azerty", 41)
println(user.email) // Getter
user.email = "my_new_email@gmail.com" // Setter
println(user.email) // Getter
Person avec les propriétés firstName et
lastName dont les valeurs par défaut sont la chaîne
vide :"". La propriété lastName est en lecture seule. Vous afficherez le
nom complet (prénom nom) après avoir modifié le prénom
(affectation d'une nouvelle valeur pour le prénom de notre objet créé) et vérifiée que le nom est bien
en lecture seule (en essayant de modifier sa valeur).
class Person
class Person (var firstName: String = "", val lastName: String = "")
fun main() {
var father = Person("Anakin", "Skywalker")
println(father)
println( "${father.firstName} ${father.lastName}")
me.firstName = "Luke"
// me.lastName = "Organa d'Alderaan"
println( "${me.firstName} ${me.lastName}")
}
User, création d'une instance, changement de la valeur de l'email et
affichage de cette valeur.
class User(email: String, var password: String, var age: Int) {
var email: String = email
get() { println("User email read access done."); return field}
set(value) { println("User email write access done."); field = value}
}
fun main() {
var currentUser = User("tristan.salaun.pro@gmail.com", "azerty", 41)
currentUser.email = "test@test.com"
println(currentUser.email)
}
var,
ou en lecture seule (immutable) en utilisant le mot clé val :
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "221b Baker Street"
var city: String = "London"
var state: String? = null
var zip: String = "NW1 6XE"
}
Pour accéder aux propriétés, il suffit d'utiliser son nom :
fun copyAddress(address: Address): Address {
val result = Address() // there's no 'new' keyword in Kotlin
result.name = address.name // accessors are called
result.street = address.street
// ...
return result
}
class Sample() {
fun foo() { print("Foo") }
}
Comme déjà vu, pour appeler une telle méthode, il suffit d'utiliser la notation à point :
Sample().foo() // creates instance of class Sample and calls foo
public, les modifieurs de
visibilité disponibles sont :
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.
Any
(équivalent à
Object en Java), qui est la superclasse par défaut de toutes les
classes qui n'ont pas
déclaré de super type :
class Example // Implicitly inherits from Any
La classe Any à 3 méthodes : equals(), hashCode() et toString().
open class Base(p: Int)
class Derived(p: Int): Base(p)
super, ou déléguer à un constructeur
secondaire qui le fera :
class MyView: View {
constructor(ctx: Context): super(ctx)
constructor(ctx: Context, attrs: AttributeSet): super(ctx, attrs)
}
open :
open class Shape {
open fun draw() { /*...*/ }
fun fill() { /*...*/ }
}
class Circle(): Shape() {
override fun draw() { /*...*/ }
}
Une méthode qui redéfinit une autre de la classe parente est elle-même redéfinissable, à moins de la
marquer comme final :
open class Rectangle(): Shape() {
final override fun draw() { /*...*/ }
}
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle: Shape() {
override val vertexCount = 4
}
Il est possible de redéfinir une propriété de type val avec une autre de type
var, mais pas l'inverse.val déclare une méthode get, en la redéfinissant par
une var, on déclare une méthode set
de plus dans la
classe dérivée.
interface Shape {
val vertexCount: Int
}
class Polygon: Shape {
override var vertexCount: Int = 0 // Can be set to any number later
}
override :
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4): Shape // Always has 4 vertices
class Polygon: Shape {
override var vertexCount: Int = 0 // Can be set to any number later
}
open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int =
name.length.also { println("Initializing size in Base: $it") }
}
class Derived(
name: String,
val lastName: String
): Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =
(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
Cela signifie que lorsque la classe primaire est créée, les propriétés déclarées ou redéfinies dans la
classe dérivée, ne sont pas encore initialisées.
super :
open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle: Rectangle() {
override fun draw() {
super.draw()
println("Filling the rectangle")
}
val fillColor: String get() = super.borderColor
}
super qualifié par le nom de cette classe : super:@Outer :
class FilledRectangle: Rectangle() {
fun draw() { /* ... */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* ... */ }
fun drawAndFill() {
super@FilledRectangle.draw() // Calls Rectangle's implementation of draw()
fill()
println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get()
}
}
}
super qualifié du nom de la classe parente avec des chevrons super<Base>
:
open class Rectangle {
open fun draw() { /* ... */ }
}
interface Polygon {
fun draw() { /* ... */ } // interface members are 'open' by default
}
class Square(): Rectangle(), Polygon {
// The compiler requires draw() to be overridden:
override fun draw() {
super<Rectangle>.draw() // call to Rectangle.draw()
super<Polygon>.draw() // call to Polygon.draw()
}
}
abstract. Un membre abstrait n'a pas
d'implémentation dans la classe. Le mot clé open n'est pas nécessaire, dans le cas d'une
classe
ou méthode abstraite, car cela est évident.
open class Polygon {
open fun draw() {}
}
abstract class Rectangle: Polygon() {
override abstract fun draw()
}
class MyClass1 {
companion object {
private const val TAG = "MyClass1"
fun myMethod() { ... }
}
}
interface :
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
Implémenter une interface :
class Child: MyInterface {
override fun bar() {
// body
}
}
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child: MyInterface {
override val prop: Int = 29
}
interface Named {
val name: String
}
interface Person: Named {
val firstName: String
val lastName: String
override val name: String get() = "$firstName $lastName"
}
data class Employee(
// implementing 'name' is not required
override val firstName: String,
override val lastName: String,
val position: Position
): Person
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C: A {
override fun bar() { print("bar") }
}
class D: A, B {
override fun foo() {
supe<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
Les interfaces A et B déclarent toutes les deux les fonctions
foo() et bar(). Note : par défaut les méthodes dans une interface, sans
corps,
sont marquées comme abstraites, c'est pour cela que la méthode bar(')) de la classe A,
n'est pas explicitement marquée.Cdoit surcharger la méthode bar() et l'implémenter.D doit les implémenter. Cette règle s'applique aux méthodes
héritées avec une implémentation simple (tel que bar()) ou multiples (foo()).
open class User (var name: String, var firstName: String, var age: Int) {
open fun displayInformations() = println("$firstName $name is $age old")
}
class Admin(name: String, firstName: String, age: Int, var phoneNumber: String): User(name, firstName, age) {
override fun displayInformations() = println("$firstName $name is $age old, phone number: $phoneNumber")
}
fun main() {
var currentUser = User(firstName = "Tristan", name = "SALAUN", age = 41)
currentUser.displayInformations()
currentUser = Admin(firstName = "Tristan", name = "SALAUN", age = 41, phoneNumber = "0123456789")
currentUser.displayInformations()
}
Dog, Bird, Duck et Snake avec
les cris respectifs : "Waf", "Cui cui", "Coin coin" et
"Ssssssss". Validez, en appellant la méthode speak sur les différentes instances d'animaux
référencées par un même objet, de type Animal.
interface Animal {
fun speak()
}
class Dog: Animal
class Bird: Animal
class Duck: Animal
class Snake: Animal
interface Animal {
fun speak()
}
class Dog: Animal { override fun speak() = println("Waf") }
class Bird: Animal { override fun speak() = println("Cui cui") }
class Duck: Animal { override fun speak() = println("Coin coin") }
class Snake: Animal { override fun speak() = println("Ssssssss") }
fun main() {
var myPet: Animal = Dog()
myPet.speak()
myPet = Bird()
myPet.speak()
myPet = Duck()
myPet.speak()
myPet = Snake()
myPet.speak()
}
fun printNumber(n: Number){
println("Using printNumber(n: Number)")
println(n.toString() + "\n")
}
fun printNumber(n: Int){
println("Using printNumber(n: Int)")
println(n.toString() + "\n")
}
fun printNumber(n: Double){
println("Using printNumber(n: Double)")
println(n.toString() + "\n")
}
fun main() {
val a: Number = 99
val b = 1
val c = 3.1
printNumber(a) //Which version of printNumber is getting used?
printNumber(b) //Which version of printNumber is getting used?
printNumber(c) //Which version of printNumber is getting used?
}
data :
data class User(val name: String, val age: Int)
Le compilateur va générer les membres suivant, en utilisant toutes les propriétés déclarées dans le
constructeur principal :equals()/hashCode().toString() sous la forme "User(name=Tristan, age=40)".componentN() correspondant aux propriétés dans leur ordre de
déclaration.
copy().var ou
val.
equals(),
hashCode() ou toString() dans le corps de la classe, ou une
implémentation marquée final dans la classe parente, alors ces fonctions ne sont
pas générés : c'est le code existant qui est utilisé.
componentN() qui sont open et retournent
des types compatibles, la fonction correspondante est générée pour la classe data et surcharge
celles du type parent. Si les fonctions du parent ne peuvent pas êtres surchargées, à cause
d'une signature incompatible, ou si elles sont finales, alors une erreur est signalée.
copy(...) avec une signature
correspondante est déprécié en Kotlin 1.2 et interdit en Kotlin 1.3.
componentN() et copy()
n'est pas autorisé.
data class User(val name: String = "", val age: Int = 0)
data class Person(val name: String) {
var age: Int = 0
}
Seule la propriété name sera utilisée dans les fonctions toString(), equals(),
hashCode() et copy()n et il n'y aura qu'une seule fonction
component1(). De ce fait, deux objets de type Person qui auraient des valeurs
pour la propriété age différentes, seront considérés comme égales.
data class Person(val name: String) {
var age: Int = 0
}
fun main() {
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
println("person1 == person2: ${person1 == person2}")
println("person1 with age ${person1.age}: ${person1}")
println("person2 with age ${person2.age}: ${person2}")
}
copy(). Pour la classe
User ci-dessous, l'implémentation serait la suivante :
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Ce qui nous permet d'écrire :
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"
Pair et Triple. Dans la
majorité des cas, les classes data sont plus appropriées, car elles permettent de nommer les propriétés,
ce qui rend le code beaucoup plus compréhensible.
data et utiliser la copie pour changer
uniquement le prénom (pour déclarer un parent par
exemple) :
data class Person (var firstName: String = "", var lastName: String = "")
data class Person (var firstName: String = "", var lastName: String = "")
fun main() {
var personTristan = Person("Tristan", "SALAUN")
var personMelody = personTristan.copy(firstName = "Mélody")
println(personTristan)
println(personMelody)
}
List, et regardons comment l'affichage va
se comporter.
data class Person (var firstName: String = "", var lastName: String = "", var list: List<Int>)
fun main() {
val listValue = listOf(1,2,3,4,5,6)
var personTristan = Person("Tristan", "SALAUN", listValue)
var personMelody = personTristan.copy(firstName = "Mélody")
println(personTristan)
println(personMelody)
}
Person(firstName=Tristan, lastName=SALAUN, list=[1, 2, 3, 4, 5, 6])
Person(firstName=Mélody, lastName=SALAUN, list=[1, 2, 3, 4, 5, 6])
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
Chaque constante de l'énumération est un objet. Les constantes sont séparées par une virgule.
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
Si une classe enum définie plusieurs membres, il faut séparer la définition des constantes de la
définition des membres, par un point virgule.
import java.util.function.BinaryOperator
import java.util.function.IntBinaryOperator
enum class IntArithmetics: BinaryOperator<Int>, IntBinaryOperator {
PLUS {
override fun apply(t: Int, u: Int): Int = t + u
},
TIMES {
override fun apply(t: Int, u: Int): Int = t * u
};
override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}
fun main() {
val a = 13
val b = 31
for (f in IntArithmetics.values()) {
println("$f($a, $b) = ${f.apply(a, b)}")
}
}
EnumClass) :
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
La méthode valueOf() lève une IllegalArgumentException si le nom passé en
paramètre ne correspond pas à une valeur définie dans la classe.enumValues<T>() et
enumValueOf<T>() :
enum class RGB { RED, GREEN, BLUE }
inline fun <reified T: Enum<T>> printAllValues() {
print(enumValues<T>().joinToString { it.name })
}
printAllValues<RGB>() // prints RED, GREEN, BLUE
Chaque constante de la classe enum possède des propriétés pour obtenir son nom et sa position dans la
déclaration de la classe :
val name: String
val ordinal: Int
Les constantes de la classe enum implémentent aussi l'interface Comparable, avec un ordre
naturel qui est l'ordre de déclaration dans la classe.
Direction précédente, et utilisez un "switch" (when) pour
afficher en clair la direction prise.
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH
when{
true -> TODO()
}
}
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH;
when(direction){
Direction.NORTH -> println("On va au Nord.")
Direction.SOUTH -> println("On va au Sud.")
Direction.EAST -> println("On va à l'Est.")
Direction.WEST -> println("On va à l'Ouest.")
}
}
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
fun main() {
val direction = Direction.NORTH;
println(
when (direction) {
Direction.NORTH -> "On va au Nord."
Direction.SOUTH -> "On va au Sud."
Direction.EAST -> "On va à l'Est."
Direction.WEST -> "On va à l'Ouest."
}
)
}
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
inner pour avoir la possibilité d'accéder aux membres de
la classe englobante (outer class). Une classe imbriquée peut référencer un objet de la classe
englobante :
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
window.addMouseListener(object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
})
Note : dans la JVM, si l'objet est une instance d'une interface fonctionnelle Java (c'est-à-dire,
une
interface avec une seule méthode abstraite), vous pouvez la créer en utilisant une expression lambda,
préfixée du type de l'interface :
val listener = ActionListener { println("clicked") }
sealed avant le nom de
la
classe. Une classe scellée, peut avoir des sous classes, mais toutes doivent être déclarées dans le même
fichier où celle-ci est déclarée. (Avant Kotlin 1.1, la règle était encore plus stricte : les
classes
devaient être des classes imbriquées dans la déclaration de la classe scellée).
sealed class Expr
data class Const(val number: Double): Expr()
data class Sum(val e1: Expr, val e2: Expr): Expr()
object NotANumber: Expr()
(L'exemple ci-dessus utilise la possibilité d'une fonctionnalité de Kotlin 1.1 : la possibilité
pour les
classes data d'étendre une autre classe, incluant les classes scellées.)
when. S'il est possible de vérifier que les cas, couvrent toutes les possibilités, alors
il n'est pas nécessaire d'ajouter la clause else. Toutefois, cela ne fonctionne que si vous
utilisez when en tant qu'expression et non comme une déclaration :
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
data class Substract(val e1: Expr, val e2: Expr): Expr
data class Multiply(val e1: Expr, val e2: Expr): Expr
data class Divide(val e1: Expr, val e2: Expr): Expr
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
is Substract -> eval(expr.e1) - eval(expr.e2)
is Multiply -> eval(expr.e1) * eval(expr.e2)
is Divide -> eval(expr.e1) / eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
+ ou *) et des règles de
priorité
figées. Pour définir un opérateur, il suffit de coder la méthode membre ou une fonction d'extension,
correspondant au nom de l'opérateur à définir. Les fonctions doivent être marquées avec le modifieur
operator.
| Expression | Traduites en |
|---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
+a, il
suit les étapes suivante :
a, par exemple le type T.unaryPlus()marquée avec le modifieur operator
sans paramètre, pour le type T.
R, alors
l'expression +a sera de type R.
Point :
data class Point(val x: Int, val y: Int)
operator fun Point.unaryMinus() = Point(-x, -y)
val point = Point(10, 20)
fun main() {
println(-point) // prints "Point(x=-10, y=-20)"
}
| Expression | Traduites en |
|---|---|
a++ |
a.inc() |
a-- |
a.dec() |
a.inc() et a.dec() doivent retourner une valeur, qui sera
assignée à la variable sur laquelle l'opérateur ++ ou -- à été utilisé. Il ne
faut pas changer la valeur de l'objet original.
a++ :
a, par exemple le type T.inc()marquée avec le modifieur operator
sans paramètre, pour le type T.
T.a dans une variable temporaire a0.a.inc() à aa0 comme résultat de l'expression.a++ les étapes sont identiques.++a et --a, la résolution fonctionne de la même
manière et l'effet est le suivant :
a.inc() à aa comme résultat de l'expression.Point.
data class Point(var x: Int, var y: Int)
Testez la différence entre point++ et ++point.
data class Point(var x: Int, var y: Int) {
operator fun inc(): Point {
return Point(this.x + 1, this.y + 1)
}
}
fun main() {
var point1 = Point(1,1)
println(point1)
point1++
println(point1)
println(++point1)
println(point1)
println(point1++)
println(point1)
// Compare with Int behaviour.
var value = 2
println(value)
println(++value)
println(value)
println(value++)
println(value)
}
| Expression | Traduites en |
|---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b), a.mod(b) (deprecated) |
a..b |
a.rangeTo(b) |
+ pour la classe Counter.
data class Counter(val dayIndex: Int) {
operator fun plus(increment: Int): Counter {
return Counter(dayIndex + increment)
}
}
Exercice : proposer un cas d'usage. Par exemple initialisons notre objet Counter avec la valeur 10 et
incrémentons-la de 3.
fun main() {
var counter: Counter = Counter(10)
println(counter)
println(counter + 3)
}
Ou de manière équivalente :
data class Point(var x: Int, var y: Int)
operator fun Point.plus(other: Point): Point { return Point(this.x + other.x, this.y + other.y) }
Point qui permet d'additionner 2 points (on additionne
le x1 avec le x2 et le y1 avec le y2).
data class Point(var x: Int, var y: Int)
data class Point(var x: Int, var y: Int) {
operator fun plus(other: Point): Point {
return Point(this.x + other.x, this.y + other.y)
}
}
Ou de manière équivalente :
data class Point(var x: Int, var y: Int)
operator fun Point.plus(other: Point): Point { return Point(this.x + other.x, this.y + other.y) }
| Expression | Traduites en |
|---|---|
a in b |
b.contains(a) |
a !in b |
!b.contains(a) |
in et !in par rapport aux
précédents opérateurs.
| Expression | Traduites en |
|---|---|
a[i] |
a.get(i) |
a[i, j] |
a.get(i, j) |
a[i_1, ..., i_n] |
a.get(i_1, ..., i_n) |
a[i] = b |
a.set(i, b) |
a[i, j] = b |
a.set(i, j, b) |
a[i_1, ..., i_n] = b |
a.set(i_1, ..., i_n, b) |
get et set avec le nombre
d'arguments correspondant.
| Expression | Traduites en |
|---|---|
a() |
a.invoke() |
a(i) |
a.invoke(i) |
a(i, j) |
a.invoke(i, j) |
a(i_1, ..., i_n) |
a.invoke(i_1, ..., i_n) |
invoke avec
le nombre de paramètres approprié.
| Expression | Traduites en |
|---|---|
a += b |
a.plusAssign(b) |
a -= b |
a.minusAssign(b) |
a *= b |
a.timesAssign(b) |
a /= b |
a.divAssign(b) |
a %= b |
a.remAssign(b), a.modAssign(b) (deprecated) |
a += b, le compilateur effectue les
étapes suivantes :
plus() pour plusAssign(), alors remonter une erreur (car il y
a une ambiguïté).
Unit.a.plusAssign(b).a = a + b (avec une vérification du type de
a + b qui doit retourner un sous type de a.
| Expression | Traduites en |
|---|---|
a == b |
a?.equals(b) ?: (b === null) |
a != b |
!(a?.equals(b) ?: (b === null)) |
equals(other: Any?): Boolean qui peut
être surchargée pour fournir une fonction personnalisée de comparaison d'égalité. Toute autre fonction
avec le même nom (par exemple equals(other: Foo)) ne sera pas appelée.=== et !==
(tests
d'identité), donc aucune convention n'existe pour ces opérateurs.== est spéciale : elle est traduite en une expression complexe qui
recherche les
valeurs nulles. null == null est toujours vrai, et x == null est toujours
faux, et n'invoque pas x.equals().
fun main() {
val first = Integer(10)
val second = Integer(10)
println(first == second) // 1
println(first.equals(second)) // 2
println(first === second) // 3
}
A votre avis, quel sera l'affichage du code ci-dessus ?
| Expression | Traduites en |
|---|---|
a > b |
a.compareTo(b) > 0 |
a < b |
a.compareTo(b) < 0 |
a >= b |
a.compareTo(b) >= 0 |
a <= b |
a.compareTo(b) <= 0 |
compareTo, qui doit
nécessairement retourner un Int.
fold pour les collections, qui prend une valeur
initiale pour l'accumulateur et une fonction pour combiner
les items. Le
résultat est obtenu en appliquant la fonction sur l'item courant et l'accumulateur, pour en faire
changer la valeur. Exemple :
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
Dans le code ci-dessus, le paramètre combine à un type de fonction (R, T) -> R,
donc il accepte une fonction qui prend 2 arguments, de
types R
et T et retourne une valeur de type R. Cette fonction est appelée dans une
boucle for, et la valeur de retour est ensuite
assignée à l'accumulateur.
fold, nous devons passer une instance de type fonction en argument
et les expressions lambdas sont couramment utilisées à cet
usage :
val items = listOf(1, 2, 3, 4, 5)
// Lambdas are code blocks enclosed in curly braces.
items.fold(0, {
// When a lambda has parameters, they go first, followed by '->'
acc: Int, i: Int ->
println("acc = $acc, i = $i, ")
val result = acc + i
println("result = $result")
// The last expression in a lambda is considered the return value:
result
})
// Parameter types in a lambda are optional if they can be inferred:
val joinedToString = items.fold("Elements:", { acc, i -> "$acc $i" })
// Function references can also be used for higher-order function calls:
val product = items.fold(1, Int::times)
invoke(...)
: par exemple f.invoke(x) ou
simplement
f(x).1.foo(2).
fun main() {
val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus
println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!"))
println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // extension-like call
}
max(strings, { a, b -> a.length < b.length })
La fonction max est une fonction d'ordre supérieur, qui prend une fonction en second
paramètre. Cet argument est une expression, qui est elle-même
une fonction : une
fonction littérale qui correspond à la fonction nommée suivante :
fun compare(a: String, b: String): Boolean = a.length < b.length
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
Une expression lambda est toujours entourée d'accolades. Les paramètres dans la syntaxe complète, sont
déclarés dans ces accolades et leur typage est optionnel. Le
corps est situé
après la ->. Si le type de retour inféré de la lambda n'est pas Unit, la
dernière (et possiblement seule) expression dans le corps de la
lambda est
considéré comme la valeur de retour.
val sum = { x: Int, y: Int -> x + y }
val product = items.fold(1) { acc, e -> acc * e }
Cette notation est appelée lambda de fin (trailing lambda).
run { println("...") }
->. Le paramètre sera implicitement déclaré avec le
nom it :
var ints = listOf(0, 1,-2,3,-1)
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
_ à la place du
paramètre :
map.forEach { _, value -> println("$value!") }
Int et stockez-la dans une variable.
Appelez cette lambda stockée à partir de la variable avec la
fonction invoke().
val sum = TODO()
fun main() {
val sum = { x: Int, y: Int -> x + y }
println(sum.invoke(3, 5))
}
Ou de manière plus concise :
fun main() {
val sum = { x: Int, y: Int -> x + y }
println(sum(3, 5))
}
executeOperation écrite ci-dessous.
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
inline permet d'optimiser l'appel de la méthode (il est optionnel).
val addition: (Int, Int) -> Int = TODO()
val subtraction: (Int, Int) -> Int = TODO()
val multiplication: (Int, Int) -> Int = TODO()
val division: (Int, Int) -> Int = TODO()
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
fun main() {
println(executeOperation(10, 5, addition))
println(executeOperation(10, 5, subtraction))
println(executeOperation(10, 5, multiplication))
println(executeOperation(10, 5, division))
}
val addition = { x: Int, y: Int -> x + y }
val subtraction = { x: Int, y: Int -> x - y }
val multiplication = { x: Int, y: Int -> x * y }
val division = { x: Int, y: Int -> x / y }
inline fun executeOperation(x: Int, y: Int, operation: (Int, Int) -> Int) = operation(x, y)
fun main() {
println(executeOperation(10, 5, addition))
println(executeOperation(10, 5, subtraction))
println(executeOperation(10, 5, multiplication))
println(executeOperation(10, 5, division))
}
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
button.setOnClickListener ( v -> {
Log.d(TAG, "User clicked button");
});
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button"); })
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button"); } )
button.setOnClickListener( { v: View -> Log.d(TAG, "User clicked button") } )
button.setOnClickListener() { v: View -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { v: View -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { v -> Log.d(TAG, "User clicked button") }
button.setOnClickListener { Log.d(TAG, "User clicked button") }
button.setOnClickListener{ Log.d( TAG, "User clicked button" ) }
Qui correspond au code Java :
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "User clicked button");
}
});
Ou encore :
button.setOnClickListener ( v -> {
Log.d(TAG, "User clicked button");
});
swap à MutableList<Int>
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Le mot clé this, dans la fonction d'extension, fait référence à l'objet receveur (qui est
juste avant le point). Maintenant, nous pouvons appeler cette fonction sur tous les objets de
type MutableList<Int>.
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'list'
Testez ce code pour en comprendre la puissance.
String qui permet de mettre la première lettre d'une
chaîne de caractère en majuscule et le reste en minuscule
(pour afficher par exemple un prénom, non composé).
fun String.firstUpper(): Any = TODO()
// Exemple d'appel :
fun main() {
println("tristan".firstUpper()) // => Tristan
println("TRISTAN".firstUpper()) // => Tristan
}
fun String.firstUpper() = this.first().toUpperCase() + this.substring(1).toLowerCase()
fun main() {
println("tristan".firstUpper())
println("TRISTAN".firstUpper())
}
Autre solution :
fun String.firstUpper() = this.toLowerCase().capitalize()
Encore plus concis (sans utiliser le this) :
fun String.firstUpper() = toLowerCase().capitalize()
MutableListMutableList <T>, et
peut donc être générique :
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Le type du receveur est déclaré avant le nom de la fonction, pour qu'il soit disponible à ce moment là.
fun main() {
open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
}
s, qui est de type Shape.
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
Le code affiche "Class method".
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
Le code affiche "Extension function".
this ==
null dans le corps de la fonction. C'est ce qui nous permet de toujours pouvoir faire appel à la
méthode toString() en Kotlin, sans avoir besoin de tester la nullité. La vérification
s'effectue dans la fonction d'extension.
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
Le code affiche "Extension function".
val <T> List<T>.lastIndex: Int
get() = size - 1
firstLetter pour la classe StringBuilder qui
permet d'accéder à la première lettre contenue dans
l'objet.
var StringBuilder.firstLetter: Char
get() = TODO()
set(value) = TODO()
import java.lang.StringBuilder
var StringBuilder.firstLetter: Char
get() = get(0)
set(value) = this.setCharAt(0, value)
fun main() {
val message = StringBuilder("hello world !")
println("${message.firstLetter} is the first letter of $message")
message.firstLetter = 'H'
println("${message.firstLetter} is the first letter of $message")
}
A noter, que le this dans this.setCharAt(0, value) est optionnel.
import java.lang.StringBuilder
var StringBuilder.firstLetter: Char
get() = this.first()
set(value) { this[0] = value }
fun main() {
val message = StringBuilder("hello world !")
println("${message.firstLetter} is the first letter of $message")
message.firstLetter = 'H'
println("${message.firstLetter} is the first letter of $message")
}
A noter, que le this dans this.setCharAt(0, value) est optionnel.
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
sum située en
dehors des accolades.Ints contenus dans la variable ints.
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
fun main() {
var ints = listOf(1, 2, -2, 3, 4, -15, 5, 6, -1)
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
println(sum)
}
B ait les mêmes fonctionnalités que
la classe A et que la classe B
soit une classe A. Dans ce cas-là, vous pouvez utiliser
l'héritage. Cela donne une relation permanente entre les
classes. En utilisant
la délégation, vous pouvez passer en paramètre un autre objet d'un autre type, un sous type de la classe
A, par exemple, à l'instance de B. Ce qui rend le mécanisme
de délégation extrêmement puissant.
interface Showable {
void show();
}
class View implements Showable {
@Override
public void show() {
System.out.println("View.show()");
}
}
class CustomView implements View {
@Override
public void show() {
System.out.println("CustomView.show()");
}
}
class Screen implements Showable {
private Showable showable;
Screen(Showable showable) {
this.showable = showable;
}
@Override
public void show() {
showable.show();
}
}
Showable view = new View();
Showable customView = new CustomView();
new Screen(view).show(); //View.show()
new Screen(customView).show(); //CustomView.show()
interface Nameable {
var name: String
}
class JackName: Nameable {
override var name: String = "Jack"
}
class LongDistanceRunner: Runnable {
override fun run() {
println("long")
}
}
class Person(name: Nameable, runner: Runnable): Nameable by name, Runnable by runner
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
println(person.name) //Jack
person.run() //long
}
by.
class TristanName: Nameable
et class ShortDistanceRunner: Runnable qui
seront utilisées par la classe Person :
fun displayPerson(person: Person){
println(person.name)
person.run()
}
interface Nameable {
var name: String
}
class JackName: Nameable {
override var name: String = "Jack"
}
class TristanName: Nameable {
override var name: String = "Tristan"
}
class LongDistanceRunner: Runnable {
override fun run() {
println("long")
}
}
class ShortDistanceRunner: Runnable {
override fun run() {
println("short")
}
}
class Person(name: Nameable, runner: Runnable): Nameable by name, Runnable by runner
fun displayPerson(person: Person){
println(person.name)
person.run()
}
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
displayPerson(person)
val person2 = Person(TristanName(), ShortDistanceRunner())
displayPerson(person2)
}
Nameable, par exemple
firstName, companyName et
cityName, il faut implémenter ces nouveaux attributs dans les classes dérivées et tester
que notre objet Person à bien hérité de
ces nouvelles propriétés .
interface Nameable {
var name: String
var firstName: String
var companyName: String
var cityName: String
}
class JackName: Nameable {
override var name: String = "Jack"
override var firstName: String = "Terence"
override var companyName: String = "IBM"
override var cityName: String = "London"
}
class TristanName: Nameable {
override var name: String = "SALAUN"
override var firstName: String = "Tristan"
override var companyName: String = "STDev"
override var cityName: String = "Marseille"
}
class LongDistanceRunner: Runnable {
override fun run() {
println("long")
}
}
class ShortDistanceRunner: Runnable {
override fun run() {
println("short")
}
}
class Person(name: Nameable, runner: Runnable): Nameable by name, Runnable by runner
fun displayPerson(person: Person){
println("${person.firstName} ${person.name} of ${person.companyName} from ${person.cityName}")
person.run()
}
fun main(args: Array<String>) {
val person = Person(JackName(), LongDistanceRunner())
displayPerson(person)
val person2 = Person(TristanName(), ShortDistanceRunner())
displayPerson(person2)
}
. :
class Foo {
var prop: String? = null
}
val foo = Foo()
foo.prop = "something"
val another = foo.prop
Vous pouvez aussi écrire des getters et des setters sur mesure :
var str: String
get() = this.toString()
set(value) {
println(value)
field = value
}
import kotlin.reflect.KProperty
class Example {
val someName by NameDelegate()
val otherName by NameDelegate()
}
class NameDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return property.name
}
}
fun main() {
val example = Example()
println(example.someName)
println(example.otherName)
}
Que va afficher le code ci-dessus ?someName, et pourquoi ?
import kotlin.reflect.KProperty
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
Reprenez l'exemple précédent, sans changer le code de la fonction main et remplacer la délégation
NameDelegate par la délégation
Delegate ci-dessus. Que constatez vous ?
lazylazy :
val myVar: String by lazy {
println("Lazy init")
"Hello"
}
println("myVar is not initialized yet")
println("$myVar My dear friend")
De manière plus classique/concise, nous utiliserons :
val myString by lazy { "Some Value" }
Que va afficher le code ci-dessus ?
observableobservable se trouve dans
la classe Delegates. Elle prend en paramètre une
valeur initiale
et une lambda qui sera exécutée à chaque fois que la valeur du champ sera modifiée, sa signature est la
suivante :
inline fun <T> observable(
initialValue: T,
crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit
): ReadWriteProperty<Any?, T>
var updated = false
var max: Int by Delegates.observable(0) { property, oldValue, newValue ->
updated = true
}
println(max) // 0
println("updated is ${updated}")
max = 10
println(max) // 10
println("updated is ${updated}")
Que va afficher le code ci-dessus ?
by observable pour la variable maxCount qui affichera
le nom de la propriété modifiée, son ancienne valeur
et la nouvelle.
fun main() {
var maxCount: Int = 0
maxCount = 5
maxCount = 10
maxCount = 20
}
Qu'affiche le code ci-dessus ?
import kotlin.properties.Delegates
fun main() {
var maxCount: Int by Delegates.observable(initialValue = 0) { property, oldValue, newValue ->
println("${property.name} is being changed from $oldValue to $newValue")
}
maxCount = 5
maxCount = 10
maxCount = 20
}
vetoableobservable, sauf que la lambda, doit retourner un
Boolean, qui indique si la valeur doit être
modifiée, ou non.
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
age déclarée comme vu précédemment :
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
import kotlin.properties.Delegates.vetoable
fun main() {
var age: Int by vetoable(initialValue = 0) { property, oldValue, newValue ->
newValue > 0
}
age = 10
println(age)
age = -1
println(age)
age = 30
println(age)
age = 0
println(age)
}
import kotlin.properties.Delegates
fun main() {
var age: Int by Delegates.vetoable(initialValue = 0) { property, oldValue, newValue ->
if (newValue > 0) true else {println("${property.name} rejected value $newValue staying at value $oldValue"); false}
}
age = 10
println(age)
age = -1
println(age)
age = 30
println(age)
age = 0
println(age)
}
notNulllateinit, dans le sens
où il lève une IllegalStateException si la
variable est accédé avant d'être initialisée.
var age by notNull<Int>()
fun main() = println(age)
var person1: Person // Erreur sur cette ligne. à corriger.
fun main(args: Array<String>) {
// initializing variable lately
person1 = Person("Ted",28)
println("${person1.name} is ${person1.age.toString()}")
}
data class Person(var name:String, var age:Int)
var person1 by notNull<Person>()
fun main(args: Array<String>) {
// initializing variable lately
person1 = Person("Ted",28)
println("${person1.name} is ${person1.age.toString()}")
}
data class Person(var name:String, var age:Int)
Une remarque sur les bonnes pratiques utilisées (ou pas) dans cet exemple ?
ReadWriteProperty.
Pour tester son usage, nous écrirons une classe Demo contenant un attribut var
name qui sera déléguée à notre
class LogDelegate<T> :
class LogDelegate<T>: ReadWriteProperty<Any, T?> {}
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class LogDelegate<T>: ReadWriteProperty<Any, T?> {
private var value: T? = null
override fun getValue(thisRef: Any, property: KProperty<*>): T? {
println("LOG get $value")
return value
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T?) {
println("LOG set: $value")
this.value = value
}
}
class Demo {
var name by LogDelegate<String>()
}
val d = Demo()
fun main() {
d.name = "Tristan"
println(d.name)
}
lateinit var age: Int)
class Box<T>(t: T) {
var value = t
}
En général pour créer une instance de ce genre de classe, nous devons fournir le type de l'argument :
val box: Box<Int> = Box<Int>(1)
Mais si le type du paramètre peut être inféré, par exemple depuis le type des paramètres du
constructeur, ou
par un autre moyen, alors il est possible d'omettre le type des arguments :
val box = Box(1) // 1 est de type Int, donc le compilateur peut en déduire que nous utilisons un type: Box<Int>
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString(): String { // extension function
// ...
}
Pour appeler les méthodes génériques, il faut préciser le type après le nom de la méthode :
val l = singletonList<Int>(1)
Mais si le type du paramètre peut être inféré, alors il est possible d'omettre le type :
val l = singletonList(1)
open class A
open class B: A()
Considérons les types suivants :
MutableList<A>
MutableList<B>
MutableList ?
open class A
open class B: A()
fun main() {
var objectA = A()
var objectB = B()
var listofA: MutableList<A> = mutableListOf<A>()
var listofB: MutableList<B> = mutableListOf<B>()
println("objectA is A ${(objectA is A)}")
println("objectB is B ${(objectB is B)}")
println("objectA is B ${(objectA is B)}")
println("objectB is A ${(objectB is A)}")
println("listofA is MutableList<A> ${(listofA is MutableList<A>)}")
println("listofB is MutableList<B> ${(listofB is MutableList<B>)}")
println("listofA is MutableList<B> ${(listofA is MutableList<B>)}")
println("listofB is MutableList<A> ${(listofB is MutableList<A>)}")
}
MutableList est invariante.
open class A
open class B: A()
Considérons les types en lecture seule suivants :
List<A>
List<B>
List ?
open class A
open class B: A()
fun main() {
var objectA = A()
var objectB = B()
var listofA: List<A> = listOf<A>()
var listofB: List<B> = listOf<B>()
println("objectA is A ${(objectA is A)}")
println("objectB is B ${bjectB is B)}} ${intln("objectA${s B $}"{bjectA is B)}} ${intln("objectB${s A ${bjectB is A)}} ${intln("listofA${s List<A> ${(listofA is List<A>)}")
println("listofB is List<B> ${(listofB is List<B>)${println("li}"stofA is List<B> ${(listofA is List<B>)}")
println("listofB is List<A> ${(listofB is List<A>)}")
}
List est covariant : List<B> est
un sous-type de List<A>.List<T> étant immutable, lors de l'affectation de List<B>
dans une variable de type List<A>,
une copie de la liste est effectuée.
abstract class Animal(val size: Int)
class Dog(val cuteness: Int): Animal(100)
class Spider(val terrorFactor: Int): Animal(1)
Dog et Spider sont bien des sous types de Animal :
val dog: Dog = Dog(10)
val spider: Spider = Spider(9000)
var animal: Animal = dog
animal = spider
Tout est OK.
ListList :
val dogList: List<Dog> = listOf(Dog(10), Dog(20))
val animalList: List<Animal> = dogList
La liste étant immuable (impossible de changer sa valeur après initialisation), cela fonctionne bien,
car une copie de la liste est réalisée.interface Compare<T> avec une méthode
compare(T item1, T item2), qui permet de comparer deux items.
val dogCompare: Compare<Dog> = object: Compare<Dog> {
override fun compare(first: Dog, second: Dog): Int {
return first.cuteness - second.cuteness
}
}
Essayons de l'affecter à une variable qui permet de comparer les Animal :
val animalCompare: Compare<Animal> = dogCompare // Compiler error
val animalCompare: Compare<Animal> = object: Compare<Animal> {
override fun compare(first: Animal, second: Animal): Int {
return first.size - second.size
}
}
val spiderCompare: Compare<Spider> = animalCompare // Works nicely!
open class Fruit
open class Apple: Fruit() // Apple extends Fruit
class Gala: Apple() // Gala extends Apple
class Variance{
val fruitProducer: () -> Fruit = ::Fruit
val appleProducer: () -> Apple = ::Apple
val galaProducer: () -> Gala = ::Gala
val fruitConsumer: (Fruit) -> Unit = ::eatFruit
val appleConsumer: (Apple) -> Unit = ::eatApple
val galaConsumer: (Gala) -> Unit = ::eatGala
val newFruitProducer1: () -> Fruit = appleProducer
val newFruitProducer2: () -> Fruit = galaProducer
val newAppleProducer: () -> Apple = galaProducer
val newGalaConsumer1: (Gala) -> Unit = appleConsumer
val newGalaConsumer2: (Gala) -> Unit = fruitConsumer
val newAppleConsumer: (Fruit) -> Unit = fruitConsumer
}
fun eatFruit(fruit: Fruit) {}
fun eatApple(apple: Apple) {}
fun eatGala(fruit: Gala) {}
fun foo(x: Any) {
if (x is Person) {
println("${x.name}") // This wouldn't compile outside the if
}
}
Notez que nous n'avons pas besoin de caster l'objet x, pour quelle raison, à votre avis
?
val p = x as Person
Si l'objet n'est pas vraiment de type Person (ou d'une sous classe), alors l'exception
ClassCastException sera levée.Person, vous pouvez utiliser
as?. Notez que le
type de retour sera Person? :
val p = x as? Person
x as Person? pour caster un type qui peut être null. La différence
entre cette solution et la précédente as? est que
celle-ci échouera si
x est une instance non nulle d'un autre type que Person :
val p = x as Person?
x avec les 2 méthodes (as et
as?).x de type Any contenant une instance de
Person, et ensuite contenant une instance de
Other.
class Person(var name: String)
class Other()
Vous testerez ensuite la différence entre les 2 autres types de cast (String en
Int) :
val value = "Message"
println(value as? Int)
println(value as Int?)
class Person(var name: String)
class Other()
fun main() {
fun foo(x: Any) {
if (x is Person) {
println("${x.name}") // This wouldn't compile outside the if
}
}
var x: Any = Person("SALAUN")
var p = x as Person
var pNull = x as? Person
x = Other()
p = x as Person
pNull = x as? Person
val value = "Message"
println(value as? Int)
println(value as Int?)
}
import java.util.ArrayList;
import java.util.List;
public class Test {
private static int getDefaultSize(Object object){
if(object instanceof String) {
return ((String) object).length();
} else if(object instanceof List) {
return ((List) object).size();
}
return 0;
}
public static void main(String[] args) {
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
System.out.println(getDefaultSize(list));
System.out.println(getDefaultSize("list"));
}
}
private fun getDefaultSize(anyObject: Any): Int {
if (anyObject is String) {
return anyObject.length
} else if (anyObject is List<*>) {
return anyObject.size
}
return 0
}
Ou encore plus concis :
private fun getDefaultSize(anyObject: Any) = when (anyObject) {
is String -> anyObject.length
is List<*> -> anyObject.size
else -> 0
}
Pair : qui contient 2 éléments.Triple : qui contient 3 éléments.Pairfirst contient la première valeur.second contient la seconde valeur.Triplefirst contient la première valeur.second contient la seconde valeur.third contient la troisième valeur.Pair de valeur, et les afficher séparément.Triple.
data class Pair<out A, out B>: Serializable
data class Triple<out A, out B, out C>: Serializable
fun main() {
val pairValue= Pair(1, "x")
val tripleValue = Triple("Tristan","SALAUN", 40)
println(pairValue.first)
println(pairValue.second)
println(tripleValue.first)
println(tripleValue.second)
println(tripleValue.third)
}
data class Person(var name:String, var age: Int)
val tristan = Person("Tristan", 44)
val (name, age) = tristan
Cette syntaxe est appelée "destructuring declaration". Cette déclaration crée plusieurs variables en une
seule fois. Dans notre exemple, les 2 variables déclarées,
peuvent être utilisées de manière indépendantes :
println(name)
println(age)
val name = person.component1()
val age = person.component2()
Les fonctions component1() et component2() est un nouvel exemple des
conventions utilisées dans Kotlin (tout comme +,
*,
...).
for ((a, b) in collection) { ... }
component3(), component4(), etc.
result et un
status. Une façon pratique de le faire, en
Kotlin, est de déclarer une classe data, et retourner une instance de cet objet.
data class Result(val result: Int, val status: Status)
fun function(...): Result {
// computations
return Result(result, status)
}
// Now, to use this function:
val (result, status) = function(...)
Les classes data déclarent automatiquement les fonctions componentN(), donc la
déstructuration fonctionne directement dans notre cas.
Pair dans des variables
a et b.Triple avec c, d et e.
fun main() {
val (a, b) = Pair(1, "x")
val (c, d, e) = Triple("Tristan","SALAUN", 40)
println(a)
println(b)
println(c)
println(d)
println(e)
println("a = $a, b = $b, c = $c, d = $d and e = $e")
}
Map_Throwable. Toutes les
exceptions ont un message, une pile d'exécution (stack
trace), et une cause optionnelle.throw :
throw Exception("Hi There!")
Pour attraper une exception, il faut utiliser l'expression try :
try {
// some code
}
catch (e: SomeException) {
// handler
}
finally {
// optional finally block
}
Il peut y avoir 0 ou plus blocs de catch, le block finally est optionnel.
Toutefois, il doit y avoir au moins un block catch ou un block
finally.
val numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
La valeur retournée par l'expression est, soit la dernière expression du block try, ou la dernière
expression du bloc catch. Le contenu du block finally
ne modifie pas le résultat de l'expression.
input ( "2005" et "azerty") pour
affecter la valeur à numValue :
val numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
import java.lang.Integer.parseInt
fun main() {
var input = "2005"
var numValue: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
println(numValue)
input = "azerty"
numValue = try { parseInt(input) } catch (e: NumberFormatException) { null }
println(numValue)
}
const.
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }
companion object {
private const val TAG = "ClassName"
}
setup dans un test unitaire. Nous ne voulons
toutefois pas que la variable puisse avoir une valeur nulle, pour ce faire, nous pouvons utiliser le
modifieur lateinit :
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // dereference directly
}
}
var, déclarées dans le corps de la
classe (pas dans le constructeur primaire, et seulement si la propriété n'a pas d'accesseurs sur mesures
(custom getter or setter).
lateinit var allByDefault: String // error: explicit initializer required, default getter and setter implied
print(allByDefault) // KO, Exception : kotlin.UninitializedPropertyAccessException: lateinit property allByDefault has not been initialized
lateinit var à bien été initialisée, on
peut
utiliser .isInitialized :
if (foo::bar.isInitialized) {
println(foo.bar)
}
class MyTest() {
lateinit var subject: String
fun displaySubject() {
println(subject)
}
}
fun main() {
var myTest = MyTest()
myTest.displaySubject()
}
fun main() {
var myTest = MyTest()
myTest.subject = "The best subject"
myTest.displaySubject()
}
| Nom de l'annotation | Usage |
|---|---|
| @Target | Liste de tous les types d'éléments possibles qui peuvent être annotés avec cette annotation. |
| @Retention | Définie si l'annotation est stockée dans le fichier de la classe compilée, et s'il est visible via la réflexion lors de l'éxécution du programme. |
| @Repeatable | Définie si l'annotation est applicable plusieurs fois sur un même bloc de code. |
| @MustBeDocumented | Spécifie que l'annotation fait partie d'une API publique et doit donc être inclus dans la classe ou méthode. |
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class MyClass
annotation avant la class.
annotation class MyClass
constructor
lors de la déclaration et placer
l'annotation avant ce mot clé.
class MyClass @Inject constructor( dependency: MyDependency){
//. . .
}
import java.lang.annotation.ElementType
import java.lang.annotation.RetentionPolicy
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Ann(val value: Int)
Utilisation de l'annotation :
@Ann(value = 10)
class MyClass
fun main(args: Array<String>) {
val c = MyClass()
val x = c.javaClass.getAnnotation(Ann::class.java)
println("Value:${x?.value}")
}
Que va afficher le code ci-dessus ?
Person :
firstName de type chaîne de caractère en lecture/écriture.lastName de type chaîne de caractère en lecture.Friend qui hérite de Person qui contient en plus :
Short: age en lecture/écriture.Colleague qui hérite de Person qui contient en plus :
role qui ne peut prendre que les valeurs suivantes : MANAGER, CEO, WORKER.
Contact qui hérite de Person qui contient en plus :
phoneNumber de type chaîne de caractère en lecture seule.email de type chaîne de caractère en lecture seule.
open class Person(var firstName: String, val lastName: String)
class Friend(firstName: String, lastName: String, var age: Short): Person(firstName, lastName)
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: String, var email: String): Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
type du numéro, qui ne peut prendre que les valeurs suivantes : MOBILE, FIX,
FAX.
value de type chaîne de caractère, avec une valeur initiale de
"0000000000", qui ne peut prendre comme valeur qu'une
chaîne de caractère, composée de 10 chiffres (utilisation de vetoable).
import kotlin.properties.Delegates
open class Person(var firstName: String, val lastName: String)
class Friend(firstName: String, lastName: String, var age: Short): Person(firstName, lastName)
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String): Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String = "0000000000") {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
Friend afin de ne pouvoir définir un age qu'avec une
valeur valide :
MAX_AGE définie avec la valeur : 150.
import kotlin.properties.Delegates
open class Person(var firstName: String, val lastName: String)
class Friend(firstName: String, lastName: String, age: Short): Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
}
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName)
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, varemail: String): Person(firstName, lastName)
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String) {
var value: String by Delegates.vetoable("0000000000") { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
toString abstraite à notre classe Person.
import kotlin.properties.Delegates
abstract class Person(var firstName: String, val lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short): Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(type: PhoneType, value: String) {
var value: String by Delegates.vetoable("0000000000") { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
Person.fun getType(person:Person) qui
retournera le type en String de l'objet passé en paramètre.
import kotlin.properties.Delegates
abstract class Person(var firstName: String, val lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short): Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
else -> "Inconnu"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5)
// Old version
//var counter = 0
//personList.forEach { println("${counter++} ${getType(it)} $it") }
personList.forEachIndexed { index, element -> println("${index} ${getType(element)} $element") }
}
getType, qui nous permet d'afficher le type d'objet en
français, nous avons dû utiliser le else.
Si nous avons besoin d'implémenter une nouvelle classe fille, il est fort probable, que nous oublions
d'ajouter ce cas dans cette méthode.
Pour fiabiliser le code, nous allons mettre en place une classe scellée
pour la classe Person.
import kotlin.properties.Delegates
sealed class Person(var firstName: String, val lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short): Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}"
}
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5)
personList.forEachIndexed { index, element -> println("${index} ${getType(element)} $element") }
}
Address qui sera une propriété optionnelle de la classe
Friend, avec les propriétés suivantes :
line1 de type chaîne de caractère, obligatoire.line2 de type chaîne de caractère, qui peut prendre une valeur
nulle.
cp de type chaîne de caractère, au format similaire au numéro de
téléphone : longueur de 5, ne contenant que des chiffres.
city de type chaîne de caractère, obligatoire.country de type chaîne de caractère, prenant la valeur "France", par
défaut.
import kotlin.properties.Delegates
sealed class Person(var firstName: String, val lastName: String) {
abstract override fun toString(): String
}
class Friend(firstName: String, lastName: String, age: Short, var address: Address? = null): Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
}
class Colleague(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
}
class Contact(firstName: String, lastName: String, var phoneNumber: PhoneNumber, var email: String):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
class Address(val line1: String, val line2: String? = null, cp: String, val city: String, val country: String = "France") {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person:Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val person: Person = Friend("Tristan", "SALAUN", 42.toShort())
val person2: Person = Friend("Melody", "SALAUN", 25.toShort())
val person2b: Person = Friend("Jean", "DUPONT", 55.toShort(), Address("36, quai des Orfèvres", cp = "75001", city = "Paris"))
val person3: Person = Colleague("Nicolas", "DUPOND", Role.MANAGER)
val person4: Person = Colleague("Jean-Christophe", "ANDRE", Role.WORKER)
val person5: Person = Contact("John", "DOE", PhoneNumber(PhoneType.MOBILE, "0612345678"), "john.doe@test.com")
val personList = listOf(person, person2, person3, person4, person5, person2b)
var counter = 0
personList.forEach { println("${counter++} ${getType(it)} $it") }
}
Person, qui retourne une
instance d'une des classes fille.
import kotlin.random.Random
class RandomValues {
companion object {
val firstNameList = listOf("Jean", "Pierre", "Clément")
val lastNameList = listOf("DUPOND", "DUPONT", "MARTIN")
fun getFirstName(): String {
return firstNameList[Random.nextInt(0, firstNameList.size)]
}
fun getLastName(): String {
return lastNameList[Random.nextInt(0, lastNameList.size)]
}
fun getRole(): Role {
return Role.values()[Random.nextInt(0, Role.values().size)]
}
fun getPhoneNumber(): PhoneNumber {
return PhoneNumber(PhoneType.MOBILE, "0000000000")
}
fun getEmail(): String {
return "test@test.com"
}
}
}
import kotlin.properties.Delegates
import kotlin.random.Random
sealed class Person(var firstName: String, val lastName: String) {
abstract override fun toString(): String
companion object {
fun getPerson(): Person {
val typeId = Random.nextInt(0, 3)
return when (typeId) {
0 -> Friend.getFriend()
1 -> Colleague.geColleague()
2 -> Contact.getContact()
else -> Friend.getFriend()
}
}
}
}
class Friend private constructor(firstName: String, lastName: String, age: Short, var address: Address? = null):
Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
companion object {
fun getFriend() =
Friend(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
age = Random.nextInt(0, MAX_AGE).toShort()
)
}
}
class Colleague private constructor(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
companion object {
fun geColleague() =
Colleague(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
role = RandomValues.getRole()
)
}
}
class Contact private constructor(
firstName: String,
lastName: String,
var phoneNumber: PhoneNumber,
var email: String
):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
companion object {
fun getContact() =
Contact(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
phoneNumber = RandomValues.getPhoneNumber(),
email = RandomValues.getEmail()
)
}
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String) {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
}
class Address(
val line1: String,
val line2: String? = null,
cp: String,
val city: String,
val country: String = "France"
) {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person: Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val randomPersonList = mutableListOf<Person>()
for (i in 0..10) {
randomPersonList.add(Person.getPerson())
}
var counter = 0
randomPersonList.forEach { println("${counter++} ${getType(it)} $it") }
}
JsonExport qui comporte une méthode toJson qui va nous
permettre d'exporter nos contacts en JSON.
interface JsonExport {
fun toJson(): String
}
sealed class Person(var firstName: String, val lastName: String): JsonExport {
....
}
import kotlin.properties.Delegates
import kotlin.random.Random
interface JsonExport {
fun toJson(): String
}
sealed class Person(var firstName: String, val lastName: String): JsonExport {
abstract override fun toString(): String
companion object {
fun getPerson(): Person {
val typeId = Random.nextInt(0, 3)
return when (typeId) {
0 -> Friend.getFriend()
1 -> Colleague.geColleague()
2 -> Contact.getContact()
else -> Friend.getFriend()
}
}
}
}
class Friend private constructor(firstName: String, lastName: String, age: Short, var address: Address? = null):
Person(firstName, lastName) {
val age: Short by Delegates.vetoable(age) { property, oldValue, newValue ->
newValue in 0..MAX_AGE
}
override fun toString() = "$firstName $lastName ${age.lifeStep()}${if (address != null) "\n$address" else ""}"
companion object {
fun getFriend() =
Friend(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
age = Random.nextInt(0, MAX_AGE).toShort()
)
}
override fun toJson() = """
{
"type": "Friend",
"firstName": "$firstName",
"lastName": "$lastName",
"age": "$age"
${if (address != null) """, "line2": "${address?.toJson()}\n" """ else "" }
}
""".trimIndent()
}
class Colleague private constructor(firstName: String, lastName: String, var role: Role): Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $role"
companion object {
fun geColleague() =
Colleague(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
role = RandomValues.getRole()
)
}
override fun toJson() = """
{
"type": "Colleague",
"firstName": "$firstName",
"lastName": "$lastName",
"role": "$role"
}
""".trimIndent()
}
class Contact private constructor(
firstName: String,
lastName: String,
var phoneNumber: PhoneNumber,
var email: String
):
Person(firstName, lastName) {
override fun toString() = "$firstName $lastName $phoneNumber $email"
companion object {
fun getContact() =
Contact(
firstName = RandomValues.getFirstName(),
lastName = RandomValues.getLastName(),
phoneNumber = RandomValues.getPhoneNumber(),
email = RandomValues.getEmail()
)
}
override fun toJson() = """
{
"type": "Contact",
"firstName": "$firstName",
"lastName": "$lastName",
"phoneNumber": "$phoneNumber",
"email": "$email"
}
""".trimIndent()
}
enum class Role {
MANAGER, CEO, WORKER
}
class PhoneNumber(val type: PhoneType, value: String): JsonExport {
var value: String by Delegates.vetoable(value) { property, oldValue, newValue ->
newValue.length == 10 && isNumber(newValue)
}
override fun toString() = "$type $value"
override fun toJson() = """
"phone": {
"type": "$type",
"value": "$value"
}
""".trimIndent()
}
class Address(
val line1: String,
val line2: String? = null,
cp: String,
val city: String,
val country: String = "France"
): JsonExport {
var cp: String by Delegates.vetoable(cp) { property, oldValue, newValue ->
newValue.length == 5 && isNumber(newValue)
}
override fun toString() = "$line1\n${if (!line2.isNullOrEmpty()) "$line2\n" else ""}$cp $city\n$country"
override fun toJson() = """
"address": {
"line1": "$line1",
${if (!line2.isNullOrEmpty()) """ "line2": "$line2\n" """ else "" }
"cp": "$cp",
"city": "$city",
"country": "$country"
}
""".trimIndent()
}
fun isNumber(s: String?): Boolean {
return if (s.isNullOrEmpty()) false else s.all { Character.isDigit(it) }
}
enum class PhoneType {
MOBILE, FIX, FAX
}
const val MAX_AGE = 150
fun Short.lifeStep() =
when (this) {
in 0..2 -> "Bébé"
in 2..10 -> "Enfant"
in 10..18 -> "Adolescent"
in 18..70 -> "Adulte"
else -> "Personne âgée"
}
fun getType(person: Person) = when (person) {
is Friend -> "Amis"
is Colleague -> "Collègue"
is Contact -> "Contact"
}
fun main() {
val randomPersonList = mutableListOf<Person>()
for (i in 0..10) {
randomPersonList.add(Person.getPerson())
}
var counter = 0
randomPersonList.forEach { println("${counter++} ${getType(it)} $it") }
println("""{ "personList": [ """)
randomPersonList.forEach { println("${it.toJson()},") }
println(""" ] }""")
}
fold,
pour obtenir des statistiques sur notre liste d'éléments :
Friend.
val firstNameCumulatedSize = randomPersonList.fold(0) { acc, person ->
acc + person.firstName.length
}
println("$firstNameCumulatedSize / ${randomPersonList.size} = ${firstNameCumulatedSize.toFloat() / randomPersonList.size.toFloat()}")
val ageCumulated = randomPersonList.fold(0) { acc, person ->
if(person is Friend) {
acc + person.age
} else {
acc
}
}
println("$ageCumulated")
Person qui :
Friend.
operator fun Person.plus(other:Person) = Friend(RandomValues.getFirstName(), "${this.lastName}-${other.lastName}", 0 )
import java.util.*
fun demo(source: List<Int>) {
val list = ArrayList<Int>()
// 'for'-loops work for Java collections:
for (item in source) {
list.add(item)
}
print(list)
// Operator conventions work as well:
for (i in 0..source.size - 1) {
list[i] = source[i] // get and set are called
}
print(list)
}
fun main() {
demo(listOf(1,3,5,74,1,-2,5,98))
}
get et un seul
argument avec un nom
commençant par set) sont représentées comme des propriétés en Kotlin. Les accesseurs pour
les valeurs de type Boolean (le nom du getter
commence par
is et le nom du setter commence par set) sont représentés aussi comme des
propriétés. Par exemple :
import java.util.Calendar
fun calendarDemo() {
val calendar = Calendar.getInstance()
if (calendar.firstDayOfWeek == Calendar.SUNDAY) { // call getFirstDayOfWeek()
calendar.firstDayOfWeek = Calendar.MONDAY // call setFirstDayOfWeek()
}
if (!calendar.isLenient) { // call isLenient()
calendar.isLenient = true // call setLenient()
}
}
fun main() {
calendarDemo()
}
public class Customer {
private String firstName;
private String lastName;
private int age;
//standard setters and getters
}
Customer directement dans notre code en Kotlin :
fun main() {
val customer = Customer()
customer.firstName = "Frodo"
customer.lastName = "Baggins"
println("${customer.firstName} ${customer.lastName}")
}
void, alors quand elle est appelée depuis Kotlin elle retournera
Unit.
get.set(pour
les propriétés de type var).
var firstName: String sera compilée en Java de la manière suivante :
private String firstName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
is, alors la règle diffère : le nom du getter sera
le même que la propriété et le nom du setter sera obtenu en
replaçant le
is par set. Par exemple, une propriété isOpen, le getter sera
isOpen() et le setter : setOpen.
Cette règle est
valable pour toutes les propriétés, peu importe leur type, pas seulement pour les propriétés de type
Boolean.
var firstName = ""
var isOpen = false
Obtenez le bytecode Kotlin avec le menu : Tools, Kotlin, Show Kotlin Bytecode.Decompile, pour obtenir le code Java équivalent.app.kt dans un package org.example,
incluant toutes les fonctions
d'extension, sont compilées
dans des méthodes statiques d'une classe nommée org.example.AppKt. Exemple :
// app.kt
package org.example
class Util
fun getTime(): Int { println("10h21"); return 10 }
// Java
import org.example.AppKt;
import org.example.Util;
public class Test {
Util utilVar = new Util();
int timeValue = AppKt.getTime();
}
@JvmName :
@file:JvmName("DemoUtils")
package org.example
class Util
fun getTime() { /*...*/ }
// Java
new org.example.Util();
org.example.DemoUtils.getTime();
@JvmName) est normalement une erreur.
Toutefois, nous
pouvons indiquer au compilateur de générer une classe facade qui aura le nom correspondant et contiendra
toutes les déclarations dans une même fichier. Pour cela,
nous devons
utiliser l'annotation @JvmMultifileClass dans tous les fichiers.
// oldutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getTime() { /*...*/ }
// newutils.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package org.example
fun getDate() { /*...*/ }
// Java
org.example.Utils.getTime();
org.example.Utils.getDate();
@JvmField. Le champ aura alors la même
visibilité que la propriété d'origine.
class User(id: String) {
@JvmField val ID = id
}
// Java
class JavaClient {
public String getID(User user) {
return user.ID;
}
}
Une propriété modifiée avec lateinit sera aussi exposée en tant que champs d'instance. La
visibilité du champ sera la même que la visibilité du setter
de la propriété.
@JvmFieldlateinitconst@JvmField@JvmField, cela donnera un champ "static" avec la même
visibilité que la propriété elle même :
class Key(val value: Int) {
companion object {
@JvmField
val COMPARATOR: Comparator<Key> = compareBy<Key> { it.value }
}
}
// Java
Key.COMPARATOR.compare(key1, key2);
// public static final field in Key class
lateinitlateinit, cela donnera un champ "static" avec la même
visibilité que la propriété elle même :
object Singleton {
lateinit var provider: Provider
}
// Java
Singleton.provider = new Provider();
// public static non-final field in Singleton class
constconst (dans une classe ou au top niveau (top level)), seront
traduites en champs statiques en Java :
// file example.kt
object Obj {
const val CONST = 1
}
class C {
companion object {
const val VERSION = 9
}
}
const val MAX = 239
// Java
int const = Obj.CONST;
int max = ExampleKt.MAX;
int version = C.VERSION;
default dans les interfaces| Visibilité en Kotlin | Visibilité en Java | Commentaire |
|---|---|---|
| private (members) | ||
| private (top level) | ||
| protected | ||
| internal | ||
| public | public | RAS |
@JvmOverloads.
class Circle @JvmOverloads constructor(centerX: Int, centerY: Int, radius: Double = 1.0) {
@JvmOverloads fun draw(label: String, lineWidth: Int = 1, color: String = "red") { /*...*/ }
}
Pour chaque paramètre avec une valeur par défaut, une méthode de surcharge sera générée. Dans notre
exemple, cela donnera :
// Constructors:
Circle(int centerX, int centerY, double radius)
Circle(int centerX, int centerY)
// Methods
void draw(String label, int lineWidth, String color) { }
void draw(String label, int lineWidth) { }
void draw(String label) { }
null, mais comme nous le savons,
ce n’est pas le cas pour Java, ce qui le rend peu pratique
pour les objets qui en proviennent. Un exemple très simple permet de mettre cela en relief. Prenez le
code suivant :
// Java
public class Nullable {
public String get(){
return null;
}
}
fun main() {
Nullable().get().length
}
Que se passe-t-il quand on lance le code ?
fun main() {
Nullable().get()?.length
}
Shape, en Kotlin, avec des propriétés : height,
width et
area, et deux fonctions : shapeMessage et draw :
// Shape.kt
class Shape(var width: Int, var height: Int, val shape: String) {
var area: Int = 0
fun shapeMessage() {
println("Hi i am $shape, how are you doing")
}
fun draw() {
println("$shape is drawn")
}
fun calculateArea(): Int {
area = width * height
return area
}
}
public class FromKotlinClass {
public static void callShapeInstance() {
Shape shape = new Shape(5,5,"Square");
shape.shapeMessage();
shape.setHeight(10);
System.out.println(shape.getShape() + " width " + shape.getWidth());
System.out.println(shape.getShape() + " height " + shape.getHeight());
System.out.println(shape.getShape() + " area " + shape.calculateArea());
shape.draw();
}
public static void main(String[] args) {
callShapeInstance();
}
}
object :
// Kotlin
object Singleton {
fun happy() {
println("I am Happy")
}
}
Pour appeler le singleton en Java, il faudra utiliser le mot clé INSTANCE :
// Java
public static void main(String args[]) {
Singleton.INSTANCE.happy();
}
INSTANCE en utilisant l'annotation @JvmStatic :
object Singleton {
fun happy() {
println("I am Happy")
}
@JvmStatic fun excited() {
println("I am very Excited")
}
}
Ce qui donnera l'appel en Java :
public static void main(String args[]) {
Singleton.INSTANCE.happy();
Singleton.excited();
}
utils.kt :
fun logD(message: String) {
Log.d("", message)
}
fun logE(message: String) {
Log.e("", message)
}
En Java, il sera possible d'appeler simplement ces fonctions comme suit :
UtilsKt.logD("Debug");
UtilsKt.logE("Error");
fun String.firstUpper() = this.first().toUpperCase() + this.substring(1).toLowerCase()
Que l'on pourrait appeler en Kotlin :
println("tristan".firstUpper())
println("TRISTAN".firstUpper())
En Java cela deviendra :
System.out.println(ExtensionKt.firstUpper("Tristan"));
Comme vous pouvez le voir, l'objet sur lequel la fonction est appliquée (le receveur/"reveiver") est
ajouté en paramètre de la fonction. De plus les paramètres
optionnels deviennent obligatoires, car Java ne gère pas cette fonctionnalité.
default
dans les interfaces, ne sont disponibles que pour la JVM 1.8, et l'annotation
@JvmDefault correspondante
est expérimentale en Kotlin 1.3.
Customer :
public class Customer {
private String firstName;
private String lastName;
private int age;
//standard setters and getters
}
La reflexion fonctionne à la fois sur les classes Kotlin et Java.Customer avec les méthodes de réflexion Kotlin :
constructors de Class<T> contient le tableau des
constructeurs.
name du Constructor contient le nom du constructeur.
fun main() {
val type = Customer::class.java
val constructors = type.constructors
println(constructors.size)
println(constructors[0].name)
}
Note, la méthode ci-dessous nécessite une librairie Kotlin
Customer::class.constructors.forEach { println(it) }
data classsealed class
MyClass::class.java.methods
Qui permet de lister les méthodes d'une classe. Décomposons cette construction :
MyClass::class nous donne une représentation de la class MyClass.java nous donne l'équivalent de java.lang.Class.methods appelle la méthode java.lang.Class.getMethods()
data class ExampleDataClass(
val name: String, var enabled: Boolean)
fun main() {
ExampleDataClass::class.java.methods.forEach(::println)
}
| Java type | Kotlin type |
|---|---|
byte |
kotlin.Byte |
short |
kotlin.Short |
int |
kotlin.Int |
long |
kotlin.Long |
char |
kotlin.Char |
float |
kotlin.Float |
double |
kotlin.Double |
boolean |
kotlin.Boolean |
| Java type | Kotlin type |
|---|---|
java.lang.Object |
kotlin.Any! |
java.lang.Cloneable |
kotlin.Cloneable! |
java.lang.Comparable |
kotlin.Comparable! |
java.lang.Enum |
kotlin.Enum! |
java.lang.Annotation |
kotlin.Annotation! |
java.lang.CharSequence |
kotlin.CharSequence! |
java.lang.String |
kotlin.String! |
java.lang.Number |
kotlin.Number! |
java.lang.Throwable |
kotlin.Throwable! |
| Java type | Kotlin type |
|---|---|
java.lang.Byte |
kotlin.Byte? |
java.lang.Short |
kotlin.Short? |
java.lang.Integer |
kotlin.Int? |
java.lang.Long |
kotlin.Long? |
java.lang.Character |
kotlin.Char? |
java.lang.Float |
kotlin.Float? |
java.lang.Double |
kotlin.Double? |
java.lang.Boolean |
kotlin.Boolean? |
Collection<T> est parente de toute la hiérarchie des collections. Elle définit le
comportement commun d'une collection en lecture seule : la récupération de la taille de la liste,
la
vérification d'appartenance d'un objet à la collection, etc.Collection hérite d'Iterable<T> qui
définie les opérations pour itérer sur les éléments. C'est le type à utiliser pour gérer les différents
types de collections. Dans les cas plus précis, préférer List ou Set.
fun printAll(strings: Collection<String>) {
for(s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
add et remove.
fun List<String>.getShortWordsTo(shortWords: MutableList<String>, maxLength: Int) {
this.filterTo(shortWords) { it.length <= maxLength }
// throwing away the articles
val articles = setOf("a", "A", "an", "An", "the", "The")
shortWords -= articles
}
fun main() {
val words = "A long time ago in a galaxy far far away".split(" ")
val shortWords = mutableListOf<String>()
words.getShortWordsTo(shortWords, 3)
println(shortWords)
}
List<T> et Array<T>
// Initializing array and list
val array = arrayOf(1, 2, 3)
val list = listOf("apple", "ball", "cow")
val mixedArray = arrayOf(true, 2.5, 1, 1.3f, 12000L, 'a') // mixed Array
val mixedList = listOf(false, 3.5, 2, 1.4f, 13000L, 'b') // mixed List
List<T>MutableList.
Array<T>
val array1 = arrayOf(1, 2, 3, 4, 5, 6)
val array2 = arrayOf(7, 8, 9, 10, 11, 12)
val newArray = array1 + array2
for(element in newArray) {
println("$element - ")
} // 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12
array.add(1) // cannot be expanded or shrank
list.add("dog") // cannot be expanded or shrank
Notons que les listes mutables peuvent changer de taille.
val array = arrayOf(1, 2, 3)
val list = listOf("apple", "ball", "cow")
Array sont modifiables (mutable), leur contenu peut changer de valeur,
contrairement aux List qui sont en lecture seule (immutable) :
val array = arrayOf(1, 2, 3)
array[2] = 4 // OK
for (element in array){
println("$element, ") // 1, 2, 4
}
val list = listOf("apple", "ball", "cow")
list[2] = "cat" //will not compile, lists are immutable
// println(list) = {apple, ball, cow}
val m = mutableListOf(1, 2, 3)
m[0] = m[1] // OK
IntArray,
DoubleArray, CharArray, etc) : qui utilisent directement les
tableaux
Java primitifs (int[], double[], char[], etc.)
val optimisedIntegerArray = intArrayOf(1, 2, 3, 4, 5, 6) // optimised for Integer Array
val optimisedDoubleArray = doubleArrayOf(1.2, 2.3, 3.4, 4.5, 5.6, 6.7, 7.8) // Optimised for Double Array
val optimisedCharacterArray = charArrayOf('a', 'b', 'c', 'd') // Optimised for Character Array
//List does not have intListOf, charListOf as such it is not optimised for primitive arrays
Array<T> est une classe dont l'implémentation est connue : c'est une région
de
mémoire séquentielle de taille fixe qui stocke les éléments. Dans la JVM, elle est représentée
par
Java arrayList<T> et MutableList<T> sont des interfaces qui peuvent avoir
plusieurs implémentations : ArrayList<T>, LinkedList<T>,
etc.
Array<T> est invariant (Array<Int> n'est pas un Array<Number>,
idem pour MutableList<T>List<T> est covariant (List<Int> est une List<Number>).
val a: Array<Number> = Array<Int>(0) { 0 } // won't compile
val l: List<Number> = listOf(1, 2, 3) // OK
fun main() {
printValue(getValue())
}
private fun printValue(value: Lazy<Int>) = println("Nothing")
private fun getValue() = lazy {
println("Returning 5")
return@lazy 5
}
Que remarquez-vous ?println en :
"Nothing ${value.value}"
Quel changement observez-vous, et pourquoi ?
build.gradle
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
}
launch {} :
launch {
// ...
}
Les coroutines, utilisent un pool de threads pour fonctionner, mais un thread peut faire tourner
plusieurs coroutines, donc il n'est pas nécessaire d'avoir
beaucoup de threads lancés.
println("Start")
// Start a coroutine
GlobalScope.launch {
delay(1000)
println("Hello")
}
Thread.sleep(2000) // wait for 2 seconds
println("Stop")
La fonction delay() fonctionne comme la fonctions Thread.sleep(), mais elle ne
bloque pas le thread, elle suspend simplement la
coroutine. Le thread retourne dans le pool de thread. Et la coroutine, reprendra son fonctionnement sur
un thread disponible.Thread.sleep(2000) ?Thread.sleep(2000) par delay(2000) ?
@OptIn(DelicateCoroutinesApi::class) :
@OptIn(DelicateCoroutinesApi::class)
fun main() {
...
delay(...), nous allons l'appeler dans une fonction
runBlocking {} :
runBlocking {
delay(2000)
}
Ce qui nous donnera au final :
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
println("Start")
// Start a coroutine
GlobalScope.launch {
delay(1000)
println("Hello")
}
// Step 1
//Thread.sleep(2000) // wait for 2 seconds
// Step 2
// delay(2000)
// Step 3
runBlocking {
delay(2000)
}
println("Stop")
}
val c = AtomicLong()
for (i in 1..1_000_000L) {
c.addAndGet(i)
}
println(c.get())
Que se passerait-il si l'opération prenait 1 seconde ?Thread.sleep(1000)
threads :
val c = AtomicLong()
for (i in 1..1_000_000L)
thread(start = true) {
c.addAndGet(i)
}
println(c.get())
Que remarquez-vous, concernant la charge de la machine ?
val c = AtomicLong()
for (i in 1..1_000_000L)
GlobalScope.launch {
c.addAndGet(i)
}
println(c.get())
Que constate-t-on ?delay(1000)), que ce passe-t-il ? Et pourquoi ?main se termine.
async {}. C'est comme launch
{} mais cela retourne une instance de
Deferred<T> qui comporte une fonction await() qui retourne le résultat de
la coroutine. Deferred<T> est une sorte de
future très basique.AtomicLong n'est plus nécessaire
val deferred = (1L..1_000_000L).map { n ->
GlobalScope.async {
n
}
}
val sum = deferred.sumBy { it.await() }
println("Sum: $sum")
Quel est le problème ici ?
await() dans un contexte de coroutine, nous utilisons de
nouveau runBlocking {}
val deferred = (1L..1_000_000L).map { n ->
GlobalScope.async {
n
}
}
runBlocking {
val sum: Long = deferred.sumOf { it.await() }
println("Sum: $sum")
}
Nous devrions obtenir le résultat suivant :
Sum: 1784293664
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
delay(1000)
n
}
}
runBlocking {
val sum = deferred.sumOf { it.await() }
println("Sum: $sum")
}
Allons nous devoir attendre 1 million de secondes (11,5 jours) pour obtenir notre résultat ?
fun workload(n: Long): Long {
delay(1000)
return n
}
Le compilateur, n'est pas content, car delay ne peut être utilisé que dans une cadre de
coroutine. Marquons la fonction avec le mot clé
suspend :
suspend fun workload(n: Long): Long {
delay(1000)
return n
}
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
fun main() {
suspend fun workload(n: Long): Long {
//delay(3000)
return n
}
val deferred = (1..1_000_000).map { n ->
GlobalScope.async {
workload(n)
}
}
runBlocking {
val sum = deferred.sumOf { it.await() }
println("Sum: $sum")
}
}
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
fun main() {
val deferred: List<Deferred<Long>> = (1..1_000_000L).map { n ->
GlobalScope.async { n }
}
runBlocking {
val sum = deferred.sumOf { it.await() }
println("Sum: $sum")
}
}
sequence qui utilise
yield :
fun main(args: Array<String>) {
val fibonacciSeq = sequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
// Print the first five Fibonacci numbers
println(fibonacciSeq.take(8).toList())
}
Ce qui devrait nous afficher :
[1, 1, 2, 3, 5, 8, 13, 21]
fun main(args: Array<String>) {
val lazySeq = sequence {
print("START ")
for (i in 1..5) {
yield(i)
print("STEP ")
}
print("END")
}
// Print the first three elements of the sequence
lazySeq.take(3).forEach { print("$it ") }
}
END ?yieldAll, par exemple :
fun main(args: Array<String>) {
val lazySeq = sequence {
yield(0)
yieldAll(1..10)
}
lazySeq.forEach { print("$it ") }
}
suspend fun SequenceScope<Int>.yieldIfOdd(x: Int) {
if (x % 2 != 0) yield(x)
}
val lazySeq = sequence<Int> {
for (i in 1..10) yieldIfOdd(i)
}
fun main(args: Array<String>) {
lazySeq.forEach { print("$it ") }
}
filter. Par exemple prenons les 8
premières valeurs de la suite de fibonacci qui sont
supérieures à 20 :
fun main(args: Array<String>) {
val fibonacciSeq = sequence {
var a = 0
var b = 1
yield(1)
while (true) {
yield(a + b)
val tmp = a + b
a = b
b = tmp
}
}
// Print the first five Fibonacci numbers
println(fibonacciSeq.filter { it > 20 }.take(8).toList())
}
val personSequence = sequence {
while (true)
yield(Person.getPerson())
}
fun main() {
println(personSequence.filter { it.firstName.length == 4 }.take(10).toList())
}
override fun onDestroy() {
super.onDestroy()
async.cancelAll()
}
// list_folders.kts
import java.io.File
val folders = File(args[0]).listFiles { file -> file.isDirectory() }
folders?.forEach { folder -> println(folder) }
Pour lancer le script, il faut ajouter l'option -script et passer le chemin du script à lancer :
$ kotlinc -script list_folders.kts "path_to_folder_to_inspect"
fun <T> singletonList(item: T): List<T> { /*...*/ }
Pour plus d'informations, voir Fonctions Génériques
lock()
lock(l) { foo() }
Au lieu de créer un objet de type fonction en paramètre, et de générer un appel à celle-ci, le
compilateur pourrait produire le code suivant :
l.lock()
try {
foo()
}
finally {
l.unlock()
}
lock()
avec le modifieur inline:
inline fun <T> lock(lock: Lock, body: () -> T): T { ... }
Ce modifieur va impacter la fonction, en elle-même, mais aussi la lambda passée en paramètre : tout sera
mis en ligne à l'endroit de l'appel.adb version.adb devices| Assert | C00000 |
| Debug | 41BB2E |
| Error | FF6B68 |
| Info | F5F550 |
| Verbose | BBBBBB |
| Warning | F4AC40 |
C:\Users<your_user>\AppData\Roaming\Google\AndroidStudio2022.3\templates
(par ex, selon la version installée)
~/.AndroidStudio"version"/config/templates~/Library/Preferences/AndroidStudio"version"/templatesMacintosh HD> Utilisateurs> "USER_NAME"> Bibliothèque> Application Support> Google>AndroidStudio"version"> templatesand_compose_.and_uia_.
and_ et sont filtrés par le type de
fichier dans lequel on se trouve (XML, Java, Kotlin).
scrcpy qui nous permettra de partager l'affichage de notre téléphone.
C:\tools par exemple) ce qui
donnera dans notre cas :
C:\tools\scrcpy-win64-v2.3.1.
scrcpy.winget est d'utiliser la commande suivante :
winget install --silent --id=Genymobile.scrcpy -e
android {
...
buildFeatures {
viewBinding true
}
}
Si l'on est en kotlin (build.gradle.kts) :
android {
...
buildFeatures {
viewBinding = true
}
}
onCreate de notre Activity
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
onCreate de notre Activity (en Java)
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
View view = binding.getRoot();
setContentView(view);
}
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BTN1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button2"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BTN2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toEndOf="@+id/button" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BTN3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button2" />
</androidx.constraintlayout.widget.ConstraintLayout>
setOnClickListener.with.
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tag="Salut"
android:text="btn2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toEndOf="@+id/button1" />
buttonCallback :
val buttonCallback: (View) -> Unit = { localView ->
if (BuildConfig.DEBUG) {
Log.d(TAG, "onCreate ${localView.tag}")
}
}
with :
with(binding) {
button1.setOnClickListener(buttonCallback)
button2.setOnClickListener(buttonCallback)
button3.setOnClickListener(buttonCallback)
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/activity_main_editText_weight_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:hint="Poids">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/activity_main_editText_weight"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/activity_main_editText_height_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/activity_main_editText_weight_layout"
android:hint="Taille">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/activity_main_editText_height"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="20"/>
addTextChangedListener :
binding.editTextHeight.addTextChangedListener { v ->
calcIMC()
}
binding.editTextWeight.addTextChangedListener { v ->
calcIMC()
}
calcIMC() :
fun calcIMC() {
val weight = try {
binding.editTextWeight.text.toString().toInt()
} catch (e: NumberFormatException) {
binding.textViewResult.text = "ERROR"
0
}
val height: Float = try {
binding.editTextHeight.text.toString().toFloat()
} catch (e: NumberFormatException) {
binding.textViewResult.text = "ERROR"
0f
}
if (weight != 0 && height != 0f) {
binding.textViewResult.text = (weight / (height * height)).toString()
}
}
and_webview.
and_SharedPreferences_loadand_SharedPreferences_save
Tools/AVD Manager

Tools/SDK Manager
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Premier button" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Second button" />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Premier button" />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Premier button" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Second button" />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="horizontal"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Premier button" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Second button" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="horizontal"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="button 1" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:gravity="end|bottom"
android:text="button 2" />
<Button
android:id="@+id/button3"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:text="button 3" />
</LinearLayout>

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="horizontal"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:text="btn 1" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="button 2 big label" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BTN" />
</LinearLayout>
</ScrollView>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Centré horizontalement" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:text="Centré verticalement" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Centré dans le parent" />
</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="En haut" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="En bas" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="A gauche" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="A droite" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Ces soirées là !" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:text="Ces soirées là !" />
</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:context=".MainActivity">
<TextView
android:id="@+id/ex_rel_3_textView_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="[I] En haut à gauche par défaut" />
<TextView android:id="@+id/ex_rel_3_textView_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ex_rel_3_textView_1"
android:text="[II] En dessous de [I]" />
<TextView
android:id="@+id/ex_rel_3_textView_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/ex_rel_3_textView_1"
android:layout_toRightOf="@id/ex_rel_3_textView_1"
android:text="[III] En dessous et à droite de [I]" />
<TextView
android:id="@+id/ex_rel_3_textView_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/ex_rel_3_textView_5"
android:layout_alignLeft="@id/ex_rel_3_textView_2"
android:gravity="start"
android:text="[IV] au dessus de [V], bord aligné sur le bord gauche de [II]" />
<TextView
android:id="@+id/ex_rel_3_textView_5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentBottom="true"
android:text="[V] En bas à droite" />
</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<TableLayout 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"
android:padding="8dp"
android:stretchColumns="1"
tools:context=".MainActivity">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_span="3"
android:text="Les items précédés d'un V ouvrent un sous-menu" />
<TableRow>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_span="3"
android:background="@color/colorPrimaryDark" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_weight="1"
android:text="N'ouvre pas un sous-menu" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Non !" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="V" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ouvre pas un sous-menu" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Là oui !" />
</TableRow>
<TableRow>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_span="3"
android:background="@color/colorPrimaryDark" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="V" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ouvre pas un sous-menu" />
</TableRow>
<TableRow>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_span="3"
android:background="@color/colorPrimaryDark" />
</TableRow>
<TableRow>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="1"
android:layout_span="2"
android:text="Cet item s'étend sur 2 colonnes, il faut un très long texte." />
</TableRow>
</TableLayout>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/activity_edit_guideline_picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.2" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/activity_edit_guideline_col1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<ImageButton
android:id="@+id/activity_edit_imageButton_favorit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/vertical_margin"
android:layout_marginRight="@dimen/horizontal_margin"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@android:drawable/btn_star_big_on" />
<ImageView
android:id="@+id/activity_edit_imageView_picture"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_marginTop="@dimen/vertical_margin"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/activity_edit_guideline_picture"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/activity_edit_guideline_picture"
app:srcCompat="@mipmap/ic_launcher" />
<ImageView
android:id="@+id/activity_edit_imageView_edit_picture"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="@id/activity_edit_imageView_picture"
app:layout_constraintEnd_toEndOf="@id/activity_edit_imageView_picture"
app:srcCompat="@android:drawable/ic_menu_camera" />
<EditText
android:id="@+id/activity_edit_editText_firstname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="@dimen/horizontal_margin"
android:hint="FirstName"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="@id/activity_edit_guideline_col1"
app:layout_constraintTop_toBottomOf="@id/activity_edit_imageView_picture" />
<EditText
android:id="@+id/activity_edit_editText_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/horizontal_margin"
android:hint="Name"
app:layout_constraintLeft_toLeftOf="@id/activity_edit_editText_firstname"
app:layout_constraintRight_toRightOf="@id/activity_edit_guideline_col1"
app:layout_constraintTop_toBottomOf="@id/activity_edit_editText_firstname" />
<RadioGroup
android:id="@+id/activity_edit_RadioGroup_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="@id/activity_edit_editText_firstname"
app:layout_constraintRight_toRightOf="@id/activity_edit_guideline_col1"
app:layout_constraintTop_toBottomOf="@id/activity_edit_editText_name">
<RadioButton
android:id="@+id/activity_edit_radioButton_male"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="H" />
<RadioButton
android:id="@+id/activity_edit_radioButton_female"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="F" />
</RadioGroup>
<EditText
android:id="@+id/activity_edit_editText_birthday"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/horizontal_margin"
android:editable="false"
android:hint="Date naissance"
android:inputType="none"
app:layout_constraintLeft_toLeftOf="@id/activity_edit_guideline_col1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/activity_edit_editText_firstname" />
<TextView
android:id="@+id/activity_edit_textView_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintRight_toRightOf="@id/activity_edit_editText_birthday"
app:layout_constraintTop_toBottomOf="@id/activity_edit_editText_birthday"
tools:text="41 ans" />
<Spinner
android:id="@+id/activity_edit_spinner_phone_type"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/horizontal_margin"
app:layout_constraintBottom_toBottomOf="@id/activity_edit_editText_phone"
app:layout_constraintLeft_toLeftOf="@id/activity_edit_editText_firstname"
app:layout_constraintRight_toRightOf="@id/activity_edit_guideline_col1"
app:layout_constraintTop_toTopOf="@id/activity_edit_editText_phone" />
<EditText
android:id="@+id/activity_edit_editText_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/horizontal_margin"
android:hint="Phone number"
app:layout_constraintLeft_toRightOf="@id/activity_edit_guideline_col1"
app:layout_constraintTop_toBottomOf="@id/activity_edit_RadioGroup_gender" />
<Button
android:id="@+id/activity_edit_button_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SAVE"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
[package.]R.type.nom
// Par exemple
R.string.app_name
@[package:]type/nom
// Par exemple :
@string/app_name
strings.xml et son utilisation pour internationaliser notre
application.
<string name="good_example">"This'll work"</string>
<string name="good_example_2">This\'ll also work</string>
<string name="bad_example">This doesn't work</string>
<string name="bad_example_2">XML encodings don't work</string>
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);
<?xml version="1.0" encoding="utf-8"?>
<resources>
<plurals name="numberOfSongsAvailable">
<!– zero, one, two, few, many, other
As a developer, you should always supply "one" and "other"
strings. Your translators will know which strings are actually
needed for their language. Always include %d in "one" because
translators will need to use %d for languages where "one"
doesn't mean 1 (as explained above).
<item quantity="one">%d song found.</item>
<item quantity="other">%d songs found.</item>
</plurals>
</resources>
int count = getNumberOfsongsAvailable();
Resources res = getResources();
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);
<b> for bold text.
<i> for italic text.
<u> for underline text.
Par exemple :
<string name="welcome">Welcome to <b>Android</b>!</string>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="planets_array">
<item>Mercury</item>
<item>Venus</item>
<item>Earth</item>
<item>Mars</item>
</string-array>
</resources>
Que l'on utilisera :
val planets = resources.getStringArray(R.array.planets_array)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
</resources>
<style name="Counter" parent="AppTheme">
<item name="android:textColor">@color/l4e_counter_text</item>
<item name="android:background">@drawable/rounded_corner</item>
<item name="android:padding">@dimen/l4e_counter_padding</item>
</style>
<style name="DialogTitle" parent="AppTheme">
<item name="android:padding">@dimen/l4e_dialog_title_text_padding</item>
<item name="android:textSize">@dimen/l4e_dialog_title_text_size</item>
<item name="android:textStyle">bold</item>
</style>
when(v?.id){
R.id.activity_main_button_test -> Log.d(TAG, "onCreate click")
}
Association du listener avec notre classe.
findViewById<Button>(R.id.activity_main_button_test).setOnClickListener(this@MainActivity)
private val btnListener: View.OnClickListener = View.OnClickListener {
when (it?.id) {
R.id.activity_main_button_test -> Log.d(TAG, "onCreate click")
}
}
Attribuons notre listener à notre bouton :
findViewById<Button>(R.id.activity_main_button_test).setOnClickListener(btnListener)
findViewById<Button>(R.id.activity_main_button_test).setOnClickListener { Log.d(TAG, "onCreate click") }
android:onClick="handleClick"
Créons la méthode en utilisant l'aide de l'éditeur, il nous reste plus qu'à implémenter la méthode.
<Button
android:id="@+id/activity_main_button_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="handleClick"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/activity_main_textView_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello world."
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/activity_main_editText_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Enter your first name"
android:inputType="textPersonName"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
and_material_design_edittext.
<CheckBox
android:id="@+id/activity_main_checkBox_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="Is activated ?"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RadioGroup
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Thé" />
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Café" />
</RadioGroup>
Dans la méthode onCreate de notre Activity utilisons le LiveTemplate : android_spinner_string_array.
string-array contenant la liste des planètes, retirons tout le code pour gérer
le Spinner et ajoutons l'attribut
android:entries="@array/planets_array".
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/planets_array" />
Régulièrement des facilités de développement sont disponibles dans le SDK ou l'IDE.
<ProgressBar
android:id="@+id/activity_main_progressBar_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<RatingBar
android:id="@+id/activity_main_ratingBar_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numStars="5"
android:rating="4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<DatePicker
android:id="@+id/activity_main_datePicker_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TimePicker
android:id="@+id/activity_main_timePicker_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<WebView
android:id="@+id/activity_main_webView_test"
android:layout_width="match_parent"
android:layout_height="match_parent" />
var webView = findViewById<WebView>(R.id.activity_main_webView_test)
webView.loadUrl("http://light4events.fr")
Quel est le problème qui empèche la page de s'afficher ? Comment résoudre le problème ?
<uses-permission android:name="android.permission.INTERNET" />
Pour empêcher l'ouverture d'un navigateur externe, nous allons ajouter la ligne suivante (avant de
charger l'URL) :
webview.setWebViewClient(new WebViewClient());
Pour aller plus loin, nous pouvons utiliser le LiveTemplate : and_webview dans notre méthode onCreate.
EditText en Material Design, nous avons d'autres
LiveTemplate à disposition, en voici la liste exaustive :
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>


<EditText
android:id="@+id/activity_main_editText_value"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/activity_main_button_send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Envoyer"
app:layout_constraintTop_toBottomOf="@id/activity_main_editText_value" />
lateinit var etValue: EditText
Puis ajoutons le code suivant à notre méthode onCreate :
etValue = findViewById<EditText>(R.id.activity_main_editText_value)
findViewById<Button>(R.id.activity_main_button_send).setOnClickListener() {
var intent = Intent(this@MainActivity, Main2Activity::class.java)
intent.putExtra(INTENT_PARAMS_EXTRA_VALUE, etValue.text.toString())
startActivity(intent)
}
Je m'aide du LiveTemplate and_intent_activity pour écrire le code.
var value = savedInstanceState?.getString(INTENT_PARAMS_EXTRA_VALUE) ?: intent.getStringExtra(INTENT_PARAMS_EXTRA_VALUE)
Je m'aide du LiveTemplate and_intent_onCreate pour écrire le
code.
findViewById<EditText>(R.id.activity_main2_editText_value).setText(value)
const val REQUEST_CODE_GET_TEXT = 4567 // Arbitrary value
Dans l'Activity, toujours, lançons l'appel à l'Activity2, en attendant une réponse :
var intent = Intent(this@MainActivity, Main2Activity::class.java)
startActivityForResult(intent, REQUEST_CODE_GET_TEXT)
<EditText
android:id="@+id/phone"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/phone_hint"
android:inputType="phone" />
android:hint="@string/phone_hint"
android:inputType="phone"
android:imeOptions="actionSend"
Dans notre Activity, nous gérons le click sur le bouton "Envoyer" du clavier avec :
etValue.setOnEditorActionListener(){v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_SEND){
Log.d(TAG, "Action send to be handled.")
true
} else {
false
}
}
and_fullscreen_immersive, à l'extérieur
d'une méthode).
and_pip, à l'extérieur d'une méthode).
Layout.OnClickListener.
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_TEXT, "Un texte à partager");
// Start the activity
startActivity(sendIntent);
// Create the text message with a string
val sendIntent = Intent()
with(sendIntent){
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Un texte à partager")
}
// Start the activity
startActivity(sendIntent)
<activity android:name=".ReceiveTextActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
Bundle extras = getIntent().getExtras();
String receivedText = (extras != null) ? extras.getString(Intent.EXTRA_TEXT) : "";
Log.d(TAG, "onCreate received text = " + receivedText);
val receivedText = intent.extras?.getString(Intent.EXTRA_TEXT) ?: ""
Log.d(TAG, "onCreate received text = $receivedText")
sendIntent.setAction("aaa");
Comment y remédier ?
Il est préférable de vérifier que l'Intent pourra être récupéré avec le code suivant :
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
Comment faire si l'on veut proposer le choix à chaque fois ?
On va forcer le lancement du chooser, avec le code suivant :
// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);
// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(chooser);
}
Comment faire si l'on veut proposer le choix à chaque fois ?
On va forcer le lancement du chooser, avec le code suivant :
// Always use string resources for UI text.
// This says something like "Share this photo with"
val title = resources.getString(R.string.chooser_title)
// Create intent to show the chooser dialog
val chooser = Intent.createChooser(sendIntent, title)
// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(packageManager) != null) {
startActivity(chooser)
}
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:host="test.link.com"/>
</intent-filter>
Essayons de cliquer sur le lien suivant : Ouvrir application avec
"http://test.link.com".
and_service_implementation
Pour appeler notre service (en Kotlin), nous utiliserons dans notre activity, un LiveTemplate, et nous
suivrons à chaque fois les indications
en TODO.
and_service_calling








CALL_PHONE.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.phonecall">
<uses-permission android:name="android.permission.CALL_PHONE"/>
<application ...>
...
</application>
</manifest>
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.CALL_PHONE)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed; request the permission
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.CALL_PHONE},
MY_PERMISSIONS_REQUEST_CALL_PHONE);
// MY_PERMISSIONS_REQUEST_CALL_PHONE is an
// app-defined int constant. The callback method gets the
// result of the request.
}
} else {
// Permission has already been granted
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_CALL_PHONE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the job you need to do.
} else {
// permission denied, boo! Disable the functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request.
}
}
and_permission_single.actionToBeCalled().@SuppressLint("MissingPermission") sur cette méthode pour supprimer le
warning.
callAction().RequestPermission.and_request_permission_single ou and_request_permission_multiple
en fonction des besoins.
String url = "tel:"+"0612345678";
Intent callIntent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
startActivity(callIntent);
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.services"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
debug qui est définis. Un second type release
est aussi définis.
flavors.
buildTypes {
// ...
}
flavorDimensions "version"
productFlavors {
demo {
dimension "version"
}
full {
dimension "version"
}
}






ext {
appcompatVersion = '1.1.0'
constraintLayoutVersion = '1.1.3'
junitVersion = '4.12'
runnerVersion = '1.2.0'
espressoVersion = '3.2.0'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.constraintlayout:constraintlayout:$constraintLayoutVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$runnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
}
Méthode plus moderne : le catalogue de version.


lint.xml à la racine du projet.@SuppressLint.
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<!-- Disable the given check in this project -->
<issue id="IconMissingDensityFolder" severity="ignore" />
<!-- Ignore the ObsoleteLayoutParam issue in the specified files -->
<issue id="ObsoleteLayoutParam">
<ignore path="res/layout/activation.xml" />
<ignore path="res/layout-xlarge/activation.xml" />
</issue>
<!-- Ignore the UselessLeaf issue in the specified file -->
<issue id="UselessLeaf">
<ignore path="res/layout/main.xml" />
</issue>
<!-- Change the severity of hardcoded strings to "error" -->
<issue id="HardcodedText" severity="error" />
</lint>
maven { url "https://jitpack.io" }
(Page du projet).
Can’t find referenced class org.sl4j ne semble plus être d'actualité.TextView,
All Attributes,
fontFamilly
et sélectionnons par exemple smokum.
SeekBar.
var outputText = findViewById<TextView>(R.id.outputText)
findViewById<EditText>(R.id.inputText).addTextChangedListener {
outputText.text = it
}
Autosizing
TextViews
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:autoSizeTextType="uniform" />
colors.xmldimens.xmlstyles.xmlcolors.xml afin de regrouper ce type de
ressources.
dimens.xml nous permet de déclarer les différentes tailles (taille du texte,
marge, ...) afin de regrouper ce type de ressources.
<style name="DialogTitle" parent="AppTheme">
<item name="android:padding">@dimen/dialog_title_text_padding</item>
<item name="android:textSize">@dimen/dialog_title_text_size</item>
<item name="android:textStyle">bold</item>
</style>
Widget.
CharSequence widgetText = context.getString(R.string.appwidget_text);
Par :
Date current = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMM yyy");
String widgetText = dateFormat.format(current);

TextView tvText;
Définissons le code du bouton :
tvText = findViewById(R.id.text);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tvText.setVisibility(tvText.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE );
}
});
Testons.
android:animateLayoutChanges="true"
Testons de nouveau.
background.xml :
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="90"
android:endColor="#FFF"
android:startColor="#CCC" />
</shape>
Ajoutons le en background de notre layout principal.
toolsandroid par le préfix
tools ce qui impactera seulement l'affichage dans l'IDE, mais
sera ignoré dans
l'application. Testons par exemple en changeant la visibilité d'un élément :
tools:visibility="gone"
region// region nomDeNotreRegion puis
// endregion
pour regrouper des blocs de code, afin de mieux les organiser.Structure présent à gauche de notre interface.
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
// Add a marker in Startup Marseille and move the camera
LatLng startupMarseille = new LatLng(43.291590, 5.378210);
mMap.addMarker(new MarkerOptions().position(startupMarseille).title("Marker at Startup Marseille."));
mMap.moveCamera(CameraUpdateFactory.zoomTo(19));
mMap.moveCamera(CameraUpdateFactory.newLatLng(startupMarseille));
}
implementation 'com.google.android.gms:play-services-location:17.0.0'
Définir un attribut de type Marker pour garder une référence sur le marker de la position
courante :
private Marker currentPosition;
FusedLocationProviderClient client = LocationServices.getFusedLocationProviderClient(this);
// Get the last known location
client.getLastLocation().addOnCompleteListener(this, new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
// TODO
}
});
LatLng currentLocation = new LatLng(task.getResult().getLatitude(), task.getResult().getLongitude());
currentPosition = mMap.addMarker(new MarkerOptions().position(currentLocation).title("Current position").icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));
com.google.android.gms.tasks.RuntimeExecutionException: java.lang.SecurityException: Client must have ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to perform any location operations.
Comment la corriger ?
int color = Color.RED;
Drawable background = rootView.getBackground();
if (background instanceof ColorDrawable)
color = ((ColorDrawable) background).getColor();
ImageView à notre projet.
ImageView imageView = findViewById(R.id.image);
Picasso.get().setLoggingEnabled(true);
Picasso.get().setIndicatorsEnabled(true);
Picasso.get().load("https://upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg").into(imageView);
Glide.with(this).load("https://upload.wikimedia.org/wikipedia/commons/d/da/Internet2.jpg").into(imageView);
Canvas. Pour cela, nous
allons suivre les étapes suivantes :
File / New / Ui Component / Custom View

Paint.translate.rotate.setColor.Path.drawArc.
private int mAngle = 0;
Et le setter associé :
public void setmAngle(int mAngle) {
this.mAngle = mAngle;
invalidate();
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
Paint paint = new Paint();
// Définir la flèche
Path northPath = new Path();
northPath.moveTo(0, -cy);
northPath.lineTo(-10, 0);
northPath.lineTo(10, 0);
northPath.close();
// Dessiner le cercle de fond
// Définir le rectangle contenant le cercle que l'on va dessiner
RectF pitchOval = new RectF(0, 0, getWidth(), getHeight());
paint.setColor(Color.BLACK);
canvas.drawArc(pitchOval, 0, 360, false, paint);
canvas.translate(cx, cy);
canvas.rotate(mAngle);
paint.setColor(Color.RED);
canvas.drawPath(northPath, paint);
}
public class MyView extends View {
private int mAngle = 0;
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int cx = getWidth() / 2;
int cy = getHeight() / 2;
Paint paint = new Paint();
// Définir la flèche
Path northPath = new Path();
northPath.moveTo(0, -cy);
northPath.lineTo(-10, 0);
northPath.lineTo(10, 0);
northPath.close();
// Dessiner le cercle de fond
// Définir le rectangle contenant le cercle que l'on va dessiner
RectF pitchOval = new RectF(0, 0, getWidth(), getHeight());
paint.setColor(Color.BLACK);
canvas.drawArc(pitchOval, 0, 360, false, paint);
canvas.translate(cx, cy);
canvas.rotate(mAngle);
paint.setColor(Color.RED);
canvas.drawPath(northPath, paint);
}
public void setmAngle(int mAngle) {
this.mAngle = mAngle;
invalidate();
}
}
val sharedPref = PreferenceManager.getDefaultSharedPreferences(applicationContext)
val editor = sharedPref.edit()
editor.putString(SHARED_PREFS_VALUE, "Value")
editor.commit()
Ou plus rapidement le LiveTemplate : and_SharedPreferences_save.implementation 'androidx.preference:preference-ktx:1.2.1'.
val sharedPref = PreferenceManager.getDefaultSharedPreferences(applicationContext)
val defaultValue = resources.getInteger(R.string.value_default)
sharedPref.getInt(SHARED_PREFS_VALUE, defaultValue)
Ou plus rapidement le LiveTemplate : and_SharedPreferences_load.sharedPref.
@file:JvmName("FileTools")
package com.example.myapplication
import android.content.Context
import java.io.File
fun readFile(context: Context, isExternal: Boolean, path: String, fileName: String): String {
val currentFile = getFile(context, isExternal, path, fileName)
// READ
// val inputAsString = FileInputStream(currentFile).bufferedReader().use { it.readText() }
var sb = StringBuilder()
currentFile?.forEachLine { sb.append( "$it\n") }
return sb.toString()
}
fun writeFile(
context: Context,
isExternal: Boolean,
path: String,
fileName: String,
content: String,
append: Boolean
): Boolean {
val currentFile = getFile(context, isExternal, path, fileName)
if (currentFile == null) {
return false
}
// WRITE
// FileOutputStream(currentFile).use {
// it.write(content.toByteArray())
// }
// TODO check if write is OK
if(append){
currentFile?.appendText(content)
} else {
currentFile?.writeText(content)
}
return true
}
fun getFile(context: Context, isExternal: Boolean, path: String, fileName: String): File? {
val rootPath: File?
if (isExternal) {
//<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
rootPath = context.getExternalFilesDir(null)
} else {
rootPath = context.getFilesDir()
}
// In case of error while opening, then return.
if (rootPath == null) {
return null
}
val currentPath = File(rootPath, path)
if (!currentPath.exists()) {
var success = currentPath.mkdirs()
if (!success) {
return null
}
}
val file = File(currentPath, fileName)
return file
}
var isExternal = false
var path = "trs"
var fileName = "test.txt"
var append = true
writeFile(this@MainActivity, isExternal, path, fileName, "AAAAA\n", append)
val stringContent = readFile(this@MainActivity, isExternal, path, fileName)
if (BuildConfig.DEBUG) {
Log.d(TAG, "onCreate $stringContent")
}
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Audio.Media._ID, // 0
MediaStore.Audio.Media.ARTIST, // 1
MediaStore.Audio.Media.TITLE, // 2
MediaStore.Audio.Media.ALBUM_ID, // 3
MediaStore.Audio.Media.ALBUM, // 4
MediaStore.Audio.Media.DATA, // 5
MediaStore.Audio.Media.DISPLAY_NAME, // 6
MediaStore.Audio.Media.DURATION }; // 7
String selection = MediaStore.Audio.Media.IS_MUSIC + " != 0";
Cursor cursor = getContentResolver().query(media,projection,selection,null,null);
while(cursor.moveToNext()){
if(BuildConfig.DEBUG){
Log.d(TAG, "onCreate " +
cursor.getString(0) + " " +
cursor.getString(1) + " " +
cursor.getString(2) + " " +
cursor.getString(3) + " " +
cursor.getString(4) + " " +
cursor.getString(5) + " " +
cursor.getString(6) + " " +
cursor.getString(7) + " ");
}
}
User :
data class User(var firstName: String, var lastName: String)
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val cursor = MatrixCursor(arrayOf("name", "firstname"))
cursor.newRow()
.add("name", "SALAUN")
.add("firstname", "Tristan")
cursor.newRow()
.add("name", "SNOW")
.add("firstname", "John")
return cursor
}
lateinit var userList: ArrayList<User>
Nous allons initialiser la liste dans la méthode onCreate :
override fun onCreate(): Boolean {
// Init some values at the create
userList = arrayListOf(User("Tristan", "SALAUN"))
return true
}
Notre méthode query va donc prendre la forme :
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
val cursor = MatrixCursor(arrayOf("name", "firstname"))
for (curentUser in this.userList) {
cursor.newRow()
.add("name", curentUser.lastName)
.add("firstname", curentUser.firstName)
}
// cursor.newRow()
// .add("name", "SALAUN")
// .add("firstname", "Tristan")
//
// cursor.newRow()
// .add("name", "SNOW")
// .add("firstname", "John")
return cursor
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val currentUser = User(
firstName = values?.getAsString(UserContract.MyDatas.KEY_COL_FIRSTNAME),
lastName = values?.getAsString(UserContract.MyDatas.KEY_COL_NAME)
)
userList.add(currentUser)
val rowID: Long = userList.size.toLong()
val _uri =
ContentUris.withAppendedId(UserContract.MyDatas.CONTENT_URI, rowID)
context!!.contentResolver.notifyChange(_uri, null)
return _uri
}
import android.net.Uri
import android.provider.BaseColumns
class UserContract {
companion object {
// The Authority
private const val AUTHORITY = "com.exemple.contentprovider.provider"
// The path to the data… and explain
private const val PATH_TO_DATA = "users" //Vous pouvez déclarer plusieurs paths (les paths utilisent les /)
}
interface MyDatas : BaseColumns {
companion object {
// The URI and explain, with example if you want
val CONTENT_URI: Uri = Uri.parse("content://${UserContract.AUTHORITY}/${UserContract.PATH_TO_DATA}")
// My Column ID and the associated explanation for end-users
const val KEY_COL_ID = BaseColumns._ID // Mandatory
// My Column Name and the associated explanation for end-users
const val KEY_COL_NAME = "name"
// My Column First Name and the associated explanation for end-users
const val KEY_COL_FIRSTNAME = "firstName"
// The index of the column ID
const val ID_COLUMN = 1
// The index of the column NAME
const val NAME_COLUMN = 2
// The index of the column FIRST NAME
const val FIRSTNAME_COLUMN = 3
}
}
}
ContentProviderClient.
ContentProviderClient, et ajoutons le code pour interroger notre nouveau
ContentProvider :
Cursor cursor = getContentResolver().query(Uri.parse("content://com.exemple.contentprovider.provider"), null, null, null, null);
if(cursor.moveToFirst()) {
StringBuilder strBuild=new StringBuilder();
while (!cursor.isAfterLast()) {
strBuild.append("\n"+cursor.getString(0)+ "-"+ cursor.getString(1));
cursor.moveToNext();
}
if(BuildConfig.DEBUG){
Log.d(TAG, "onCreate " + strBuild);
}
}
ContentProvider :
// Defines an object to contain the new values to insert
val newValues = ContentValues().apply {
/*
* Sets the values of each column and inserts the data. The arguments to the "put"
* method are "column name" and "value".
*/
put(UserContract.MyDatas.KEY_COL_FIRSTNAME, "John")
put(UserContract.MyDatas.KEY_COL_NAME, "Doe")
}
val newUri = contentResolver.insert(
UserContract.MyDatas.CONTENT_URI, // The UserDictionary content URI
newValues // The values to insert
)
NetworkTool :
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.NetworkInfo
import android.os.Build
class NetworkTool {
companion object {
// NEED : <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
private fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val nw = connectivityManager.activeNetwork ?: return false
val actNw = connectivityManager.getNetworkCapabilities(nw) ?: return false
return when {
actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
//for other device who are able to connect with Ethernet
actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
//to check internet over Bluetooth
actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH) -> true
else -> false
}
} else {
return connectivityManager.activeNetworkInfo?.isConnectedOrConnecting ?: false
}
}
fun isConnected(ni: NetworkInfo?): Boolean {
return ni != null && ni.isConnectedOrConnecting
//ni!=null && ni.getState()==NetworkInfo.State.CONNECTED
}
fun isBroadband(ni: NetworkInfo?): Boolean {
if (ni == null) return false
var isBroadband = false
when (ni.type) {
ConnectivityManager.TYPE_BLUETOOTH -> {}
ConnectivityManager.TYPE_ETHERNET -> isBroadband = true
ConnectivityManager.TYPE_MOBILE -> {}
ConnectivityManager.TYPE_WIFI -> isBroadband = true
ConnectivityManager.TYPE_WIMAX -> isBroadband = true
else -> {}
}
return isBroadband
}
}
}
NetworkStateReceiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.NetworkInfo
import android.util.Log
import java.lang.Boolean
class NetworkStateReceiver: BroadcastReceiver() {
companion object {
private const val TAG = "NetworkStateReceiver"
}
// post event if there is no Internet connection
override fun onReceive(context: Context?, intent: Intent) {
Log.d(TAG, "onReceive $intent")
if (intent.extras != null) {
val ni = intent.extras!![ConnectivityManager.EXTRA_NETWORK_INFO] as NetworkInfo?
Log.d(TAG, "onReceive DetailedState = " + ni!!.detailedState.name)
Log.d(TAG, "onReceive State = " + ni.state.name)
Log.d(TAG, "onReceive ExtraInfo = " + ni.extraInfo)
Log.d(TAG, "onReceive Reason = " + ni.reason)
Log.d(TAG, "onReceive isAvailable = " + Boolean.toString(ni.isAvailable))
Log.d(TAG, "onReceive isConnected = " + Boolean.toString(ni.isConnected))
Log.d(TAG, "onReceive isConnectedOrConnecting = " + Boolean.toString(ni.isConnectedOrConnecting))
Log.d(TAG, "onReceive isFailover = " + Boolean.toString(ni.isFailover))
Log.d(TAG, "onReceive isRoaming = " + Boolean.toString(ni.isRoaming))
Log.d(TAG, "onReceive describeContents = " + ni.describeContents())
Log.d(TAG, "onReceive ExtraInfo = " + ni.extraInfo)
Log.d(TAG, "onReceive Subtype = " + ni.subtype)
Log.d(TAG, "onReceive SubtypeName = " + ni.subtypeName)
Log.d(TAG, "onReceive Type = " + ni.type)
Log.d(TAG, "onReceive TypeName = " + ni.typeName)
Log.d(TAG, "onReceive EXTRA_EXTRA_INFO = " + intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO))
Log.d(
TAG,
"onReceive EXTRA_IS_FAILOVER = " + Boolean.toString(intent.getBooleanExtra(ConnectivityManager.EXTRA_IS_FAILOVER, Boolean.FALSE))
)
Log.d(TAG, "onReceive EXTRA_NETWORK_TYPE = " + intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, -1))
Log.d(
TAG,
"onReceive EXTRA_NO_CONNECTIVITY = " + Boolean.toString(
intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY,
Boolean.FALSE
)
)
)
val ni2 = intent.extras!![ConnectivityManager.EXTRA_OTHER_NETWORK_INFO] as NetworkInfo?
if (ni2 != null) {
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO DetailedState = " + ni2.detailedState.name)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO State = " + ni2.state.name)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO ExtraInfo = " + ni2.extraInfo)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO Reason = " + ni2.reason)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO isAvailable = " + Boolean.toString(ni2.isAvailable))
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO isConnected = " + Boolean.toString(ni2.isConnected))
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO isConnectedOrConnecting = " + Boolean.toString(ni2.isConnectedOrConnecting))
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO isFailover = " + Boolean.toString(ni2.isFailover))
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO isRoaming = " + Boolean.toString(ni2.isRoaming))
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO describeContents = " + ni2.describeContents())
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO ExtraInfo = " + ni2.extraInfo)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO Subtype = " + ni2.subtype)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO SubtypeName = " + ni2.subtypeName)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO Type = " + ni2.type)
Log.d(TAG, "onReceive EXTRA_OTHER_NETWORK_INFO TypeName = " + ni2.typeName)
}
Log.d(TAG, "onReceive EXTRA_REASON = " + intent.getStringExtra(ConnectivityManager.EXTRA_REASON))
if (NetworkTool.isConnected(ni)) {
// there is Internet connection
if (BuildConfig.DEBUG) {
Log.d(TAG, "onReceive CONNECTED")
Log.d(TAG, "onReceive BROADBAND " + if (NetworkTool.isBroadband(ni)) "TRUE" else "FALSE")
}
} else {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onReceive NOT CONNECTED")
}
// no Internet connection, send network state changed
}
}
}
}
private val networkStateReceiver = NetworkStateReceiver()
Puis dans la méthode onCreate on enregistre le listenner :
registerReceiver(networkStateReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
Et on le désenregistre dans le onStop :
unregisterReceiver(networkStateReceiver)
Pour tester, on peut couper la connexion WiFi par exemple, et la réactiver.
dependencies {
...
// JSon parser
implementation 'com.google.code.gson:gson:2.10.1'
}
data class Book(var id: String, var name: String, var author: String, var genre: String, var numpages: Int, var releaseDate: String, var cover: String)
Et remplissons une liste de Book :
var list = arrayListOf(
Book(
id = "01fsEF", name = "1984",
author = "George Orwell",
genre = "Fiction dystopique",
numpages = 376,
releaseDate = "1949",
cover = "1984.png"
),
Book(
id = "3H1J0n",
name = "Le Meilleur des mondes",
author = "Aldous Huxley",
genre = "Science-fiction",
numpages = 285,
releaseDate = "1932",
cover = "meilleur-des-mondes.jpg"
),
Book(
id = "MbtsI7",
name = "Malevil",
author = "Robert Merle",
genre = "Littératures de l'imaginaire",
numpages = 541,
releaseDate = "1972",
cover = "malevil.jpg"
)
)
val gson = Gson()
val listType = object : TypeToken<ArrayList<Book?>?>() {}.type
val jsonResult: String = gson.toJson(list, listType)
Log.d(TAG, "onCreate jsonResult = $jsonResult")
private fun jsonToPrettyFormat(jsonString: String?): String? {
val json = Gson().fromJson(jsonString, JsonElement::class.java)
val gson = GsonBuilder()
.serializeNulls()
.disableHtmlEscaping()
.setPrettyPrinting()
.create()
return gson.toJson(json)
}
Que l'on appellera par exemple :
Log.d(TAG, "onCreate jsonResult = ${jsonToPrettyFormat(jsonResult)}")
// Deserialization
val bookList2: ArrayList<Book> = Gson().fromJson(jsonResult, listType)
Log.d(TAG, "onCreate bookList2 = $bookList2")
// JSon parsing
implementation 'com.google.code.gson:gson:2.10.1'
// Network calls
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
implementation 'com.squareup.retrofit2:converter-gson:2.8.1'
// handle recyclerview
implementation "androidx.recyclerview:recyclerview:1.3.2"
// handle livedata life cycle in fragments
implementation "androidx.fragment:fragment-ktx:1.8.0"
// Handle images loading (choose one)
// implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.github.bumptech.glide:glide:4.15.1'



String.
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Query
interface NewsApi {
@GET("top-headlines")
fun getNewsList(
@Query("country") newsSource: String?,
@Query("apiKey") apiKey: String?
): Call<NewsResponse?>?
}
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class RetrofitService {
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
fun <S> createService(serviceClass: Class<S>): S {
return retrofit.create(serviceClass)
}
}
import android.util.Log
import androidx.lifecycle.MutableLiveData
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
object NewsRepository {
private const val TAG = "NewsRepository"
private var newsApi: NewsApi? = null
init {
newsApi = RetrofitService().createService(NewsApi::class.java)
}
fun getNews(country: String?, key: String?): MutableLiveData<NewsResponse?> {
val newsData = MutableLiveData<NewsResponse?>()
newsApi?.getNewsList(country, key)?.enqueue(object : Callback<NewsResponse?> {
override fun onResponse(call: Call<NewsResponse?>, response: Response<NewsResponse?>) {
if (response.isSuccessful) {
newsData.setValue(response.body())
} else {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onResponse $response")
}
}
}
override fun onFailure(call: Call<NewsResponse?>, t: Throwable) {
newsData.value = null
if (BuildConfig.DEBUG) {
Log.d(TAG, "onFailure $t")
}
}
})
return newsData
}
}
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class NewsViewModel : ViewModel() {
private var mutableLiveData: MutableLiveData<NewsResponse?>? = null
fun init() {
if (mutableLiveData != null) {
return
}
mutableLiveData = NewsRepository.getNews("fr", "96c6792fdad6434fbc3a893daba40e0f")
}
fun getNewsRepository(): LiveData<NewsResponse?>? {
return mutableLiveData
}
}
NewsFragment à notre projet, et dans le layout du fragment,
ajouter une RecyclerView :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:context=".NewsFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNews"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
activity_main.xml :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:context=".MainActivity">
<fragment android:id="@+id/news_fragment"
android:name="com.example.todeletenetwork.NewsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp">
<TextView
android:id="@+id/item_news_textView_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:transitionName="title"
app:layout_constraintEnd_toStartOf="@id/item_news_imageView_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title title title title title title title title title title title title title title title title title title title title title title title title title title title title title" />
<TextView
android:id="@+id/item_news_textView_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:transitionName="description"
app:layout_constraintEnd_toStartOf="@id/item_news_imageView_image"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/item_news_textView_title"
tools:text="description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description description" />
<ImageView
android:id="@+id/item_news_imageView_image"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:src="@mipmap/ic_launcher"
android:transitionName="image"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="item_news_textView_description,item_news_imageView_image" />
<TextView
android:id="@+id/item_news_textView_publishedAt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier"
tools:text="14 minutes ago" />
<TextView
android:id="@+id/item_news_textView_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier"
tools:text="by author" />
</androidx.constraintlayout.widget.ConstraintLayout>
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.bumptech.glide.Glide
class NewsAdapter(val context: Context, val myDataset: List<Article>):
RecyclerView.Adapter<NewsAdapter.MyViewHolder>() {
private var mDataset: List<Article>? = null
private var mInflater: LayoutInflater? = null
init {
mInflater = LayoutInflater.from(context)
mDataset = myDataset
}
class MyViewHolder(v: View): ViewHolder(v) {
// each data item is just a string in this case
var tvTitle: TextView
var tvDescription: TextView
var tvPublishedAt: TextView
var tvAuthor: TextView
var imageView: ImageView
init {
tvTitle = v.findViewById(R.id.item_news_textView_title)
tvDescription = v.findViewById(R.id.item_news_textView_description)
tvPublishedAt = v.findViewById(R.id.item_news_textView_publishedAt)
tvAuthor = v.findViewById(R.id.item_news_textView_author)
imageView = v.findViewById(R.id.item_news_imageView_image)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// create a new view
val v = mInflater!!.inflate(R.layout.item_news, parent, false)
return MyViewHolder(v)
}
override fun getItemCount(): Int = if(mDataset == null) 0 else mDataset!!.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
mDataset?.let {safeDataset ->
holder.tvTitle.setText(safeDataset[position].title)
holder.tvDescription.setText(safeDataset[position].description)
holder.tvPublishedAt.setText(safeDataset[position].publishedAt)
holder.tvAuthor.setText(safeDataset[position].author)
Glide.with(holder.imageView).load(safeDataset[position].urlToImage).into(holder.imageView)
}
}
}
private var newsAdapter: NewsAdapter? = null
private val newsViewModel: NewsViewModel by viewModels()
private var rvHeadline: RecyclerView? = null
private var articleArrayList = arrayListOf<Article>()
private fun setupRecyclerView(context: Context) {
if (newsAdapter == null) {
newsAdapter = NewsAdapter(context, articleArrayList)
rvHeadline?.layoutManager = LinearLayoutManager(context)
rvHeadline?.adapter = newsAdapter
//rvHeadline?.setItemAnimator(DefaultItemAnimator())
//rvHeadline?.setNestedScrollingEnabled(true)
} else {
newsAdapter?.notifyDataSetChanged()
}
}
onCreateView :
// Inflate the layout for this fragment
val rootLayout = inflater.inflate(R.layout.fragment_news, container, false)
context?.let { safeContext ->
rvHeadline = rootLayout.findViewById(R.id.rvNews)
newsViewModel.init()
newsViewModel.getNewsRepository()?.observe(viewLifecycleOwner) {
val newsArticles = it?.articles
if (newsArticles != null) {
articleArrayList.addAll(newsArticles)
newsAdapter?.notifyDataSetChanged()
}
}
setupRecyclerView(safeContext)
}
return rootLayout
java.lang.NoSuchMethodError: No static method metafactory(Ljava/lang/invoke/MethodHandles
Il faut activer la compatibilité JVM 1.8 en ajoutant dans le build.gradle, le code suivant :
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
java.net.SocketException: socket failed: EPERM (Operation not permitted)
Il suffit que nous désinstallions l'application, pour la réinstaller.
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/")
.addConverterFactory(GsonConverterFactory.create())
.build()
Par :
var client = OkHttpClient.Builder()
.connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS).build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/v2/").client(client)
.addConverterFactory(GsonConverterFactory.create(Gson()))
.build()
Cela nous permet d'attendre un peu plus de temps avant d'avoir une erreur. Il faut dans ce cas-là,
afficher une indication à l'utilisateur pour qu'il comprenne
qu'il doit attendre.
Caused by: java.lang.IllegalArgumentException: Binary XML file line #11: Must specify unique android:id, android:tag, or have a parent with an id for com.example.todeletenetwork.NewsFragment
Il suffit de préciser un id au tag fragment dans le layout de notre activity.
SensorManager sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
List<Sensor> liste = sensorManager.getSensorList(Sensor.TYPE_ALL);
if (BuildConfig.DEBUG) {
for (Sensor currentSensor : liste) {
Log.d(TAG, "onCreate sensor " + currentSensor);
}
}
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
var liste = sensorManager.getSensorList(Sensor.TYPE_ALL)
if (BuildConfig.DEBUG) {
for (currentSensor in liste) {
Log.d(TAG, "onCreate sensor $currentSensor")
}
}
private Sensor mProximitySensor = null; // En attribut de classe.
mProximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
var mProximitySensor:Sensor? = null // En attribut de classe.
mProximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
private SensorManager mSensorManager = null; // En attribut de classe.
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Puis nous nous enregistrons sur les changements uniquement quand l'application est active :
@Override
protected void onResume() {
super.onResume();
mSensorManager.registerListener(mSensorEventListener, mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onPause() {
super.onPause();
mSensorManager.unregisterListener(mSensorEventListener, mProximitySensor);
}
final SensorEventListener mSensorEventListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Que faire en cas de changement de précision ?
}
public void onSensorChanged(SensorEvent sensorEvent) {
if(BuildConfig.DEBUG){
Log.d(TAG, "onSensorChanged " + sensorEvent.values.length);
Log.d(TAG, "onSensorChanged " + sensorEvent.values[0]);
}
}
};
Il ne nous reste plus qu'à implémenter notre code fonctionnel.
var mSensorManager: SensorManager? = null // En attribut de classe.
mSensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
Puis nous nous enregistrons sur les changements uniquement quand l'application est active :
override fun onResume() {
super.onResume()
mSensorManager?.registerListener(
mSensorEventListener,
mProximitySensor,
SensorManager.SENSOR_DELAY_NORMAL
)
}
override fun onPause() {
super.onPause()
mSensorManager?.unregisterListener(mSensorEventListener, mProximitySensor)
}
val mSensorEventListener: SensorEventListener = object : SensorEventListener {
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
// Que faire en cas de changement de précision ?
}
override fun onSensorChanged(sensorEvent: SensorEvent) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onSensorChanged ${sensorEvent.values.size}")
Log.d(TAG, "onSensorChanged ${sensorEvent.values[0]}")
}
}
}
Il ne nous reste plus qu'à implémenter notre code fonctionnel.
adb shell monkey -p your.package.name -v 500
monkeyrunnermonkeyrunner qui va installer
l'application, réaliser des actions (tout en effectuant des captures d'écran), pour enfin effacer
l'application testée.screenshot_app.py :
# -*- coding: utf-8 -*-
# https://developer.android.com/studio/test/monkeyrunner
# https://medium.com/@soonsantos/guide-to-run-monkeyrunner-e9363f36bca4
# Imports the monkeyrunner modules used by this program
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice
# Connects to the current device, returning a MonkeyDevice object
device = MonkeyRunner.waitForConnection()
MonkeyRunner.alert(u"\n\nMonkeyRunnerScript\nMerci de vérifier que :\n\
\t - Le téléphone est bien installé.\n\
\n\n\n\n\n\
Appuyez sur 'Continuer' POUR REPENDRE L'EXECUTION...","MonkeyRunner","Continuer");
print("Start")
# Presses the Home button
device.press('KEYCODE_HOME',MonkeyDevice.DOWN_AND_UP)
# sets a variable with the package's internal name
package = 'fr.salaun.tristan.todelete'
# sets a variable with the name of an Activity in the package
activity = 'fr.salaun.tristan.todelete.MainActivity'
apk_path = device.shell('pm path ' + package)
if apk_path.startswith('package:'):
print "XXXYY already installed."
else:
print "XXXYY app is not installed, installing APKs..."
device.installPackage('D:/path to apk/yourapp.apk')
# sets the name of the component to start
runComponent = package + '/' + activity
# Runs the component
device.startActivity(component=runComponent)
# Wait a bit
MonkeyRunner.sleep(1)
# Takes a screenshot
print("Take screenshot of screen")
result = device.takeSnapshot()
# Writes the screenshot to a file
result.writeToFile('screenshot.png','png')
# ==================================================
# Go out of the application
device.press("KEYCODE_BACK", MonkeyDevice.DOWN_AND_UP)
setup appelée avant chaque testtearDown appelée après chaque testAndroidJUnit4 déprécié
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
Et modifions l'import correspondant.
ContentProvider et mettons en place des tests
relatifs à notre base de donnée.

uiautomatorviewer.bat
(dans C:\Users\USER_NAME\AppData\Local\Android\Sdk\tools\bin pour identifier les
id
des éléments composants l'interface graphique des applications tierces.

and_intent_select_contact
pour implémenter la méthode.EditText.EditText
private EditText editTextNumber, editTextText;
Récupérons leur valeur :
editTextNumber = findViewById(R.id.activity_main_editText_number);
editTextText = findViewById(R.id.activity_main_editText_message);
and_send_sms_01_method.
sendSMS(getApplicationContext(), editTextNumber.getText().toString(), editTextText.getText().toString());
and_permission_single.actionToBeCalled(), et
remplaçons-le par l'appel à la méthode actionToBeCalled().
java.lang.SecurityException: Sending SMS message: uid XXXXX does not have android.permission.SEND_SMS.
Nous avons :
Manifest.xml.EditText et les Buttons.BroadcastReceivers vers la
MainActivity.
| Touches | Action |
|---|---|
| Shift deux fois rapidement. | Ouvrir la recherche. |
| F2 | Aller jusqu'à la prochaine erreur. |
| Ctrl + Y | Effacer une ligne. |
| Shift + F6 | Renommer. |
| Shift + Ctrl + Flèche haut/bas | Déplacer une ligne de code. |
| Ctrl + Alt + L | Indenter le code. |
| Alt + F7 | Find usage. |
| Ctrl + Alt + O | Organiser les imports. |
| Ctrl (maintenu) + click souris | Ouvrir la source cliquée. |
| Ctrl + D | Dupliquer la ligne. |

and_tts : pour activer une synthèse vocale.and_intent_voice_recognition : pour activer une reconnaissance vocale.
and_intent_select_contact,
et suivre les instructions.
clear.bat, pour effacer les données d'une application :
echo Waiting for device on USB
adb wait-for-usb-device
echo Device found
echo Clear APP Tristan
adb shell pm clear fr.salaun.tristan.android.myapplication
delete.bat, pour effacer une application :
echo Waiting for device on USB
adb wait-for-usb-device
echo Device found
echo "Delete APP Tristan"
adb uninstall fr.salaun.tristan.android.myapplication
install.sh, pour installer plusieurs applications d'un répertoire :
#!/bin/bash
# example : ./install_all_apk.sh /c/apk/
echo Waiting for device on USB
adb wait-for-usb-device
echo Device found
echo "The path to use : $1"
for filename in $1/*.apk; do
echo "installing $filename"
adb install -r -d -g $filename
done
install_apk_all_devices.bat, pour installer une application sur plusieurs devices :
@echo off
SETLOCAL ENABLEDELAYEDEXPANSION
:: INSTALL ON ALL ATTACHED DEVICES ::
FOR /F "tokens=1,2 skip=1" %%A IN ('adb devices') DO (
start "Sub" install_apk_all_devices_installer.bat %%A
)
ENDLOCAL
:EOF
install_apk_all_devices_installer.bat, qui est appelé par le script précédent :
@echo off
SET ARGUMENTS=%~1
if "%ARGUMENTS%" == "" (
GOTO EOF
)
adb -s %ARGUMENTS% uninstall fr.salaun.tristan.android.myapplication
echo Waking up the device %ARGUMENTS%.
adb -s %ARGUMENTS% shell input keyevent KEYCODE_WAKEUP
REM Sleep for 2 seconds
echo Wait a bit
ping -n 5 127.0.0.1>nul
echo Unlock the screen
adb -s %ARGUMENTS% shell input keyevent 82
echo Install the application.
adb -s %ARGUMENTS% install -r -t D:\apk\debug\fr.salaun.tristan.android.myapplication-debug.apk
if errorlevel 1 goto performdeletebefore
echo Install Success!
goto launch
:performdeletebefore
echo Something bad happened during install.
echo Try to uninstall first.
adb -s %ARGUMENTS% uninstall fr.salaun.tristan.android.myapplication
adb -s %ARGUMENTS% install -r -t D:\apk\debug\fr.salaun.tristan.android.myapplication-debug.apk
if errorlevel 1 goto ERROR
:launch
echo Launch the application.
adb -s %ARGUMENTS% shell am start -n fr.salaun.tristan.android.myapplication/fr.salaun.tristan.android.myapplication.MainActivity
:EOF
exit
:ERROR
echo "Exit with error"
com.example.jsonmyapplication.build.graddle (app) :
implementation 'com.google.code.gson:gson:2.8.6'
Puis créons les classes suivantes :
data class Book(var id: String, var name: String, var author: String, var genre: String, var numpages: Int, var releaseDate: String, var cover: String)
data class BookList (val bookList: List<Book>)
Déclarons une liste de livres :
var list = arrayListOf(
Book(
id = "01fsEF", name = "1984",
author = "George Orwell",
genre = "Fiction dystopique",
numpages = 376,
releaseDate = "1949",
cover = "1984.png"
),
Book(
id = "3H1J0n",
name = "Le Meilleur des mondes",
author = "Aldous Huxley",
genre = "Science-fiction",
numpages = 285,
releaseDate = "1932",
cover = "meilleur-des-mondes.jpg"
),
Book(
id = "MbtsI7",
name = "Malevil",
author = "Robert Merle",
genre = "Littératures de l'imaginaire",
numpages = 541,
releaseDate = "1972",
cover = "malevil.jpg"
)
)
var bookList = BookList (list)
fun jsonToPrettyFormat(jsonString: String?): String? {
val json = JsonParser.parseString(jsonString).asJsonObject
val gson = GsonBuilder()
.serializeNulls()
.disableHtmlEscaping()
.setPrettyPrinting()
.create()
return gson.toJson(json)
}
// Serialization
val gson = Gson()
val listType: Type = object : TypeToken<BookList?>() {}.type
val jsonResult: String = gson.toJson(bookList, listType)
Log.d(TAG, "onCreate jsonResult = $jsonResult")
Log.d(TAG, "onCreate jsonResult = ${jsonToPrettyFormat(jsonResult)}")
Et enfin testons la "Déserialization" :
// Deserialization
val bookList2: BookList = Gson().fromJson(jsonResult, listType)
Log.d(TAG, "onCreate bookList2 = ${bookList2}")
TextView tvDemo = (TextView) findViewById(R.id.textview);
tvDemo.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
copyContentToClipboard("content", ((TextView)v).getText().toString());
return true;
}
});
En Kotlin :
findViewById<TextView>(R.id.textview).setOnLongClickListener(View.OnLongClickListener {
copyContentToClipboard("label", (it as TextView).text.toString())
return@OnLongClickListener true
})
Utilisons le Live Template and_copy_to_clipboard pour écrire la
méthode : copyContentToClipboard.
View / Tool Windows / Device File Explorer.
adb exec-out run-as debuggable.app.package.name cat databases/file > file
Plusieurs fichiers
adb exec-out run-as debuggable.app.package.name tar c databases/ > databases.tar
adb exec-out run-as debuggable.app.package.name tar c shared_prefs/ > shared_prefs.tar
A tester :
> adb shell
shell $ run-as com.example.package
shell $ chmod 666 databases/file
shell $ exit ## exit out of 'run-as'
shell $ cp /data/data/package.name/databases/file /sdcard/
shell $ run-as com.example.package
shell $ chmod 600 databases/file
> adb pull /sdcard/file .
adb backup -apk -shared -all -f <filepath>/backup.ab
Restauration :
adb restore <filepath>/backup.ab
Backup d'une seule application (avec APK -apk) :
adb backup -f "“"D:\myfolder\myapp.ab" -apk <package name>
Multiples
exemple.
java -jar "C:\ADB\android-backup-tookit\android-backup-extractor\android-backup-extractor-20180203-bin\abe.jar" unpack c:\adb\backup2.ab backup-extracted.tar (You’ll be asked to enter the password)
Toute la
documentation.Unsupported major.minor version 52.0 nous indique, que nous avons compilé
le code avec une version de JDK 1.8 et que nous
essayons de le lancer avec une version antérieure :