En cours de rédaction

Cours Google sur Kotlin avec Android: lien

Classes

Constructeur

Il y a 2 types de constructeurs : les primaires et secondaires :

class Heure_composant {
    // mettre en pause le retour de l'heure
    var Paused:Boolean = false
    // Constructeur primaire
    init {
        println ("Constructeur primaire : Je me lance ...")
    }
    // Constructeur secondaire
    constructor() {
        println ("Constructeur Secondaire : je me lance ...")
    }
}

Instenciation

    val maj_heure_composant:Heure_composant = Heure_composant();

Héritage

Par défaut une classe est de type « final ». Pour qu’elle soit héritable il faut le spécifier avec le mot clef « open » devant « class »

open class Mother{
    var name:String = "Mother"
}
class Daughter:Mother(){
    var address:String = ""
}

Singleton

Très simple sous kotlin, il suffit d’utiliser le mot clef « objet » en lieu et place de « class » :

object mon_singleton {

    var valeur = ""

}

fun main() {
    
    val objet1:mon_singleton = mon_singleton
    val objet2:mon_singleton = mon_singleton
    println("objet1 : $objet1")
    println("objet2 : $objet2")
    objet1.valeur = "test singleton"
    println("objet1 : ${objet1.valeur}")
    println("objet2 : ${objet2.valeur}")

}

Résultat :

objet1 : mon_singleton@378fd1ac
objet2 : mon_singleton@378fd1ac
objet1 : test singleton
objet2 : test singleton

Pas de constructeur mais utilisation de init possible en utilisant ‘object’

Si constructeur, code plus verbeux, exemple :

class MySingleton private constructor(private val param: String) {

    companion object {
        @Volatile
        private var INSTANCE: MySingleton? = null

        @Synchronized
        fun getInstance(param: String): MySingleton = INSTANCE ?: MySingleton(param).also { INSTANCE = it }
    }

    fun printParam() {
        print("Param: $param")    
    }
}

utilisation :

MySingleton.getInstance("something").printParam()

Fonctions

Récapitulatif sur la déclaration des fonctions, les paramètres, etc.

Déclaration

Simple fonction sans retour de paramètres et prenant une valeur en paramètre

fun ma_fonction(valeur1:String) {
    println ("valeur1 : $valeur1")
}

ma_fonction("test")

Simple fonction avec valeur par defaut pour un paramètre :

fun ma_fonction(valeur1:String="") {
    println ("valeur1 : $valeur1")
}
ma_fonction()

Principe des paramètres nommés quand la fonction est appelées :

fun ma_fonction(valeur1:String="", valeur2:String="") {
    println ("valeur1 : $valeur1")
    println ("valeur2 : $valeur2")
}

ma_fonction("val1","val2")     // utilisation initiale comme en JAVA
ma_fonction()                  // comme les paramètres ont une valeur par défaut --> pas besoin de spécifier une valeur
ma_fonction(valeur2 = "val2")  // utilisation via "paramètre nommé"

Fonction avec retour :

fun ma_fonction(valeur1:String="", valeur2:String=""): String {
    // ...
    return "$valeur1 --> $valeur2"
}

val val_finale:String = ma_fonction()
val val_finale2:String = ma_fonction(valeur1 = "val1", valeur2 = "val2")

Fonction en paramètre

fun ma_fonction(callback:()->Unit) {
        println ("calculs ...")
        callback()
        println("fin")
}

    // Utilisation
    ma_fonction(callback = {
        println (" intérieur callback")
    })

    // raccourci (possible car un seul paramètre attendant une fonction)
    ma_fonction { println("intérieur callback") }

Fonction prenant une fonction en paramètre de manière optionnelle + surchage de fonction :

// fonction avec callback fonctionnel
fun ma_fonction( callbacks: (()->Unit)? = null  ): String {
    // ...
    return ""
}
// fonction surcharge
fun ma_fonction (valeur1:String):String {
    // ...
    return ""
}

// Utilisation de la fonction de différentes manières
ma_fonction {
    println ("callback ...")
}
ma_fonction()
ma_fonction(valeur1 = "test")

Lancer une activité et passage de paramètres

Sur première activité :

// Fetching the Local Context
val mContext = LocalContext.current

// déclaration intent
val intent = Intent(mContext,MainActivity2::class.java)
// ajout paramètre
intent.putExtra("valeur1","Valeur de passage")
// Lance l'activité
mContext.startActivity(intent)

Sur deuxième activité :

// récupération du contexte
val context = LocalContext.current
// récupération de l'intent de l'appli en cours
val intent = (context as MainActivity2).intent
// récupération de la donnée
val valeur:String? = intent.getStringExtra("valeur1")

Log

Utilisation de la classe utilitaire LOG pour éviter les println() …

fun code_click() {

    Log.i("code_click","début code")

    Log.d("code_click","debug 1")
    Log.d("code_click","debug 2")

    Log.w("code_click","proche manque ressource")

    Log.e("code_click","Erreur ...")

    Log.i("code_click","fin code")

}

@Composable
fun App_main13() {

    Surface() {
        Column() {
            Row() {
                Text(text = "Titre")
            }
            Row() {
                Button(onClick = {
                    Log.i(TAG,"Click bouton")
                    Log.i("MonTAG","Click bouton")
                    code_click()
                }) {
                    Text(text = "Cliquer moi pour log")
                }
            }
        }
    }
}

Résultat :

2022-06-27 16:25:05.915 17056-17069/com.example.test_compose7_navigation W/System: A resource failed to call close. 
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation I/ContentValues: Click bouton
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation I/MonTAG: Click bouton
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation I/code_click: début code
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation D/code_click: debug 1
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation D/code_click: debug 2
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation W/code_click: proche manque ressource
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation E/code_click: Erreur ...
2022-06-27 16:25:25.894 17056-17056/com.example.test_compose7_navigation I/code_click: fin code

Liens :

Thread, coroutine, channel et autre

Ressources :

Le principe des Threads est toujours présent comme dans Java

    val mon_thread = Thread{
        println("Hello")
        Thread.sleep(5000)
        println("World")
    }
    mon_thread.start()

Une autre implémentation sous Kotlin est possible :

    val mon_thread2 = thread(start = true) {
        println("Hello2")
        Thread.sleep(5000)
        println("World2")
    }

Le thread va s’executer directement. Bien que cette écriture soit concise, dans l’EDI Android, la variable « mon_thread2 » est marquée comme non utilisée alors que si en fait …

Mais il existe en plus de principe de coroutines, préconisé par KOTLIN, plus sûr et demandant moins de ressouces mémoire :

    runBlocking {
        val job = launch{
            println("${Thread.currentThread()} has run.")
            delay(5000)
            println("Hello world")
        }
    }

Dans cet exemple, le code appelant sera en attente que les lignes ci-dessus soit entièrement executées … ce qui ne correspondant pas à l’effet du code avec les Threads.

Il faut donc passer via Async :

    val tache = GlobalScope.async {
        println("Hello3")
        delay(5000)
        println("World3")
    }

    runBlocking {
        val job = launch {
            tache
        }
    }

et pour faire en sorte que le processus parent attende :

    val tache = GlobalScope.async () {
        println("Hello3")
        delay(5000)
        println("World3")
        return@async "traitement fini"
    }

    runBlocking {
        val res = tache.await()
        println("resultat : ${res}")
    }

Exemple plus complet alliant coroutine, channel et mise à jour de composant graphique :

Un channel permet de discuter entre coroutine. C’est par ce procédé que la mise à jour de l’heure est « mise en pause » au sein de la coroutine « MAJ_heure » (pas le job qui tourne tout le temps, mais juste la transmission de l’information de l’heure au sein avant appel du callback)


// Permet de communiquer entre job
val channel = Channel<String>()

// Fonction qui va envoyer l'heure via callable
suspend fun MAJ_heure(callbacks_heure: ((heure:String) -> Unit)?)  {

    var paused:Boolean = false

    // boucle sans fin (pas bien :) )
    while (true) {

        // channel pas vide ?
        if (!channel.isEmpty) {
            val va_tmp = channel.receive()
            // logique de pause
            if (va_tmp=="pause_paspause") { paused = !paused}
            if (va_tmp=="1") { paused = true}
            if (va_tmp=="0") { paused = false}
        }
        // un petit délai de pause
        delay(50)

        // heure en string
        val cal = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Calendar.getInstance()
        } else {
            TODO("VERSION.SDK_INT < N")
        }
        val heure = cal.get(Calendar.HOUR_OF_DAY)
        val minute = cal.get(Calendar.MINUTE)
        val seconde = cal.get(Calendar.SECOND)
        val milliseconde = cal.get(Calendar.MILLISECOND)

        // si callback défini
        if (callbacks_heure != null) {
            // pas en pause
            if (paused==false) {
                // envoi heure au callback
                callbacks_heure("$heure : $minute : $seconde : $milliseconde")
            }
        }
    }
}



@SuppressLint("CoroutineCreationDuringComposition")
@Composable
fun App_main10() {

    var heure:String by remember { mutableStateOf("12:00:00")}

    // lance actualisation variable heure
    val job = GlobalScope.launch {
        var maj_heure2 = MAJ_heure {
            try{
                heure=it
            } catch (e:Exception) {
            }
        }
    }

    // Affichage global
    Surface() {
        Column(modifier = Modifier.fillMaxWidth().padding(10.dp)) {
            Row{
                Text ("Bienvenue sur l'appli qui tue \nElle va vous donner l'heure ...")
            }

            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Text (" --> Heure :  $heure ")
            }

            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Button(
                    onClick = {
                        GlobalScope.launch {
                            channel.send("pause_paspause")
                        }
                    }) {
                    Text("Pause / pas pause Affichage")
                }
            }
        }
    }
}

Ce qui donne :

Autre solution plus simple et sans passer par un channel pour mettre en pause l’affichage de l’heure :

Création d’une classe dédiée :

package com.example.test_compose7_navigation.Classes
import android.icu.util.Calendar
import android.os.Build
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class Heure_composant ( var callbacks_heure:(heure:String)->Unit={}) {

    var Paused:Boolean = false   // pour mettre en pause le retour de l'heure

    init {
        Demmarer()
    }

    // lance job qui donner l'heure via le callback
    fun Demmarer() {

        val job = GlobalScope.launch {
            // boucle sans fin (pas bien :) )
            while (true) {
                // un petit délai de pause
                delay(50)
                // heure en string
                val cal = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    Calendar.getInstance()
                } else {
                    TODO("VERSION.SDK_INT < N")
                }
                val heure = cal.get(Calendar.HOUR_OF_DAY)
                val minute = cal.get(Calendar.MINUTE)
                val seconde = cal.get(Calendar.SECOND)
                val milliseconde = cal.get(Calendar.MILLISECOND)

                // Affichage en oause ?
                if (!Paused) {
                    // envoi heure au callback
                    callbacks_heure("$heure : $minute : $seconde : $milliseconde")
                }
            }
        }
    }

    fun Pause_pas_pause() {
        Paused=!Paused
    }

}

Utilisation de la classe dans le « composable »

@Composable
fun App_main10() {

    var heure:String by remember { mutableStateOf("12:00:00")}

    // lance actualisation variable heure
    val maj_heure_composant:Heure_composant = Heure_composant() {
        try {
            heure=it
        } catch (e:Exception) {
        }
    }

    // Affichage global
    Surface() {
        Column(modifier = Modifier.fillMaxWidth().padding(10.dp)) {
            Row{
                Text ("Bienvenue sur l'appli qui tue \nElle va vous donner l'heure ...")
            }

            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Text (" --> Heure :  $heure ")
            }

            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Button(
                    onClick = {
                        maj_heure_composant.Pause_pas_pause()
                    }) {
                    Text("Pause / pas pause Affichage")
                }
            }
        }
    }
}

Compose : récupérer le contexte de l’application

val context = LocalContext.current

Compose : mise en forme générale

Utiliser les composables

  • Column
  • Row
  • Spacer


    Column(
        modifier = Modifier
            .border(width = 2.dp, color = Color(0xFF0000FF))
            .padding(5.dp)
    ) {


        Row( modifier = Modifier
            .border(width = 1.dp, color = Color(0xFF00FF00))
            .padding(2.dp)) {
            Text(text = "AAA", modifier = Modifier.border(width = 1.dp, color = Color(0xFFD1AA4F)))
            Text(text = "BBB")
        }

        Row(modifier = Modifier
            .border(width = 1.dp, color = Color(0xFF00FF00))
            .padding(2.dp)) {
            Text(text = "CCC")
            Text(text = "DDD")
            Column(modifier = Modifier
                .border(width = 1.dp, color = Color(0xFF0000FF))
                .padding(2.dp)) {
                Text(text = "EEEE")
                Spacer(modifier = Modifier.height(5.dp).background(Color(0xFF676767)))
                Text(text = "FFFF")
            }
        }
        Row (modifier = Modifier
            .border(width = 1.dp, color = Color(0xFF00FF00))
            .padding(2.dp)){
            Text(text = "GGG")
        }

    }

Ce qui donne

Compose : LazyColumn ou LazyRow (et Item, Divider)

Équivalent du recylcerView

Ressources :


    var liste_texte = mutableListOf<String>("")
    for (i in 1..1000) {
        liste_texte.add("texte n°$i")
    }

    Surface() {
        LazyColumn() {
            for ((index,value) in liste_texte.withIndex()) {
                if ( index % 9 == 1 ) {
                    item {
                        Spacer(modifier = Modifier
                            .background(Color(0x467C9261))
                            .fillMaxWidth()
                            .height(10.dp)
                            )
                        Divider()
                    }
                }
                item {
                    Text(text = value)
                    Divider()
                }
            }
        }
    }

Ce qui donne :

Compose : Card

Référence :

    Surface( modifier = Modifier.fillMaxWidth()) {
        Column( modifier = Modifier.padding(10.dp)) {
             Card(elevation = 10.dp, modifier = Modifier.padding(4.dp)) {
                 Row( verticalAlignment = Alignment.CenterVertically) {
                     Icon(imageVector = Icons.Default.Info, contentDescription = "", modifier = Modifier.padding(4.dp))
                     Text ("Mon Card", modifier = Modifier.padding(8.dp))
                 }
             }
        }
    }

Ce qui donne :

Compose : SnackBarHost (équivalent à Toast)

Cas n°1 (sans personnalisation de la bulle)

    val scope = rememberCoroutineScope()
    var snackbarHostState by remember {mutableStateOf(SnackbarHostState())}

    Surface() {
        Column(modifier = Modifier
            .fillMaxWidth()
            .padding(10.dp)) {

                Button(onClick = { /*TODO*/
                    scope.launch {
                        snackbarHostState.showSnackbar("Message vers utilisateur")
                        }
                    }
            )   {
                Text(text = "Cliquer moi pour un snack ...")
                }
        }
        SnackbarHost(hostState = snackbarHostState)
    }

Ce qui donne :

Cas n°2 :

    val scope = rememberCoroutineScope()
    var snackbarHostState by remember {mutableStateOf(SnackbarHostState())}

    Surface() {
        Column(modifier = Modifier
            .padding(10.dp))
        {
            Button(onClick = {
                // on lance le snackbar
                scope.launch {
                    snackbarHostState.showSnackbar("", duration = SnackbarDuration.Short)
                }
            })   {
                Text(text = "Cliquer moi pour un snack ...")
            }
        }
    }

    SnackbarHost(
        hostState = snackbarHostState) {
        Box(
            contentAlignment = Alignment.Center,
            modifier = Modifier.fillMaxSize()
        ) {
            Card(
                shape = RoundedCornerShape(8.dp),
                border = BorderStroke(2.dp, Color.White),
                modifier = Modifier.padding(10.dp),
                elevation = 10.dp
            ) {
                Row(
                    modifier = Modifier.padding(8.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Text(text = "Message bulle pour utilisateur")
                    Spacer(modifier = Modifier.width(10.dp))
                    Button(onClick =
                    {  // on force l'arret du snackBar ...
                        snackbarHostState.currentSnackbarData?.dismiss()
                    }) {
                        Text ("OK !")
                    }
                }
            }
        }
    }

Ce qui donne :

Compose : EditText

Ressources :

Les champs d’édition sont :

  • TextField
  • OutlinedTextField

Exemple :

    var text by remember { mutableStateOf("")}
    var is_error by remember {mutableStateOf(false)}
    var image_vector_error by remember { mutableStateOf(Icons.Default.Check)}

    OutlinedTextField(
        value = text,
        // permet d'éditer ...
        onValueChange = {
            // MAJ texte
            text = it
            // Détecte une erreur ...
            is_error = if (text.equals("erreur")) true else false
            if (is_error) {
                image_vector_error = Icons.Default.Warning
            } else {
                image_vector_error = Icons.Default.Check
            }
        },
        // Titre du champ
        label = {Text("Nom")},
        isError = is_error,
        modifier = Modifier.padding( top =10.dp, start = 10.dp, bottom = 4.dp),
        // Texte complémenataire si pas de texte saii
        placeholder = {
          Text(text = "Saisir votre nom ...")  
        },
        // Composant avant Texte (Image, texte, autres...)
        leadingIcon = { Icon(imageVector = Icons.Default.Info, contentDescription = "")   },
        // Composant après texte (Image, texte, autres...)
        trailingIcon = { Icon(imageVector = image_vector_error, contentDescription = "") },

    )
    // si erreur --> affiche texte explication en dessous du TextField
    if (is_error) {
        Text(
            text = "Erreur dans la saisie ...",
            color = MaterialTheme.colors.error,
            style = MaterialTheme.typography.caption,
            modifier = Modifier.padding(start = 20.dp)
        )
    }

Ce qui donne :

Affichage initial
Une fois entré dans le composant
Après première saisie
Affichage quand une erreur …

Compose : bouton (Button, OutlinedButton, TextButton)

3 types de bouton d’après material (v2)

        Column(modifier = Modifier.padding(10.dp)) {

            Row() {
                Button(onClick = { /*TODO*/ }) {
                    Text("Boutton")
                }
                Spacer (modifier= Modifier.width(10.dp) )
                Button(onClick = { /*TODO*/ }) {
                    Text("Boutton")
                    Icon(imageVector = Icons.Default.Close, contentDescription = "",
                    modifier = Modifier.padding(start = 4.dp))
                }
            }
            
            OutlinedButton(onClick = { /*TODO*/ }) {
                Text("Outlined Boutton")
            }

            TextButton(onClick = { /*TODO*/ }) {
                Text("Texte Boutton")
            }
        }

ce qui donne :

Compose : TopAppBar

2 implémentation possibles.

Exemple avec une

        Scaffold(
            topBar = {
                TopAppBar(
                    title = {
                        Text("Discute avec ta banque")
                    },
                    navigationIcon = {
                        IconButton(onClick = { /*TODO*/ }) {
                            Icon(Icons.Default.ArrowBack,"")
                        }
                    },
                    actions = {
                        IconButton(onClick = { /*TODO*/ }) {
                            Icon(Icons.Default.Search,"")
                        }
                        IconButton(onClick = { /*TODO*/ }) {
                            Icon(Icons.Default.List,"")
                        }
                    }
                )
            }
        ) {

           Column {
               LazyColumn() {
                   for (i in 1..40) {
                       item {
                           Text ("Contenu appli $i.")
                       }
                   }
               }
           }

        }

Ce qui donne :

Compose : AlertDialog

2 implémentations …

code avec une :

      var afficher_dialogue by remember {mutableStateOf(false)}
  
      Column() {

            Button(onClick = {
                afficher_dialogue = true
            }) {
                Text("AlertDIalog")
            }

            if (afficher_dialogue) {
                
                AlertDialog(
                    onDismissRequest =
                    { //Quand on clique en dehors de la boite de dialogue
                      // Permet de gérer le modal ou pas ...
                        afficher_dialogue = false
                    },
                    title = {
                            Text(text = "Bienvenue")
                    },
                    text = {
                           Text("Bienvenue sur la nouvelle boite dialogue")
                    },
                   confirmButton = {
                       Button(onClick = { afficher_dialogue = false },) {
                           Text("OK")
                       }
                   },
                    dismissButton = {
                        OutlinedButton(onClick = { /*TODO*/ },) {
                            Text("Annuler")
                        }
                    }
                )
            }
        }

Ce qui donne :

Compose : Ressource Texte, Image, Couleur

@Composable
fun App_main12() {

    Surface() {
        Column() {
            Row() {
                val ressources = LocalContext.current.resources

                Image(
                    painter = painterResource(id = R.drawable.img),
                    contentDescription = null,
                    modifier = Modifier.width(30.dp).padding(2.dp)
                    )
                
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    Text(
                        text =  ressources.getString(R.string.titre),
                        color = Color(ressources.getColor(R.color.couleur_titre,null))
                    )
                } else {
                    Text(
                        text =  ressources.getString(R.string.titre),
                        color = Color(ressources.getColor(R.color.couleur_titre))
                    )
                }
            }
        }
    }
}

Liens :

Compose : les états dans les éléments de type Composable

1 Faire au maximum des composants sans états (StateLess) :

  • pour pouvoir tout re-initialiser si besoin

2 Utiliser un modelView le plus possible dans l’activité ce qui simplifiera grandement la gestion des composants et sous composants

liens utiles :

Android/Kotlin : écriture fichier

Premier cas simple (écriture fichier dans le répertoire privé de l’app)

Fonctions permettant la lecture ou l’écriture dans un fichier

fun ecrire_text(context:Context,personne:Personne, callback_texte:(texte_retour:String)->Unit={}) {
    val fileoutputStream:FileOutputStream
    val nom_fichier ="test.txt"
    fileoutputStream =  context.openFileOutput(nom_fichier, Context.MODE_PRIVATE)
    fileoutputStream.write(personne.toString().toByteArray())
    callback_texte("Ecriture fichier effctuée")
}

fun lire_text(context:Context, callback_texte:(texte_retour:String)->Unit={})  {
    var fileInputStream:FileInputStream?=null
    val nom_fichier ="test.txt"

    // fichier existant !
    val file_exist = context.getFileStreamPath(nom_fichier).exists()  // va vérifier le fichier en private
    if (file_exist) {
        fileInputStream = context.openFileInput(nom_fichier)
        var inputStreamReader: InputStreamReader = InputStreamReader(fileInputStream)
        val bufferedReader: BufferedReader = BufferedReader(inputStreamReader)
        val stringBuilder: StringBuilder = StringBuilder()
        var text: String? = null
        while ({ text = bufferedReader.readLine(); text }() != null) {
            stringBuilder.append(text)
        }
        callback_texte(stringBuilder.toString())
    } else {
        callback_texte("Fichier inexistant")
    }
}

Code compose associé :


@Composable
fun App_main11() {

    var texte_resultat by remember{ mutableStateOf("")}
    val context = LocalContext.current
    val personne1 = Personne(
        Id=1,
        Nom = "lulu",
        Premon = "lolo",
        Age = 19
    )
    Surface {
        Column(modifier = Modifier.padding(10.dp)) {
            Row() {
                Text(text = "Test Ecriture/Lecture Fichier")
            }
            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Button(onClick = {
                    //Ecroture fichier
                    ecrire_text(context,personne1) {
                        texte_resultat = it
                    }
                }) {
                    Text(text = "Ecrire")
                }
            }
            Spacer(modifier = Modifier.height(10.dp))
            Row() {
                Button(onClick = {
                    //lecture fichier
                    lire_text(context) {
                        texte_resultat=it
                    }
                }) {
                    Text(text = "lire")
                }
            }
            Spacer(modifier = Modifier.height(10.dp))

            Row() {
                Text(text = texte_resultat)
            }
        }
    }
}

Ce qui donne :

Deuxième cas manipulant des listes d’objets + GSON (écriture fichier dans le répertoire privé de l’app)

fun ecrire_text(context:Context,personne:Personne, callback_texte:(texte_retour:String)->Unit={}) {
    val fileoutputStream:FileOutputStream
    val nom_fichier ="test.txt"

    // Liste d'objet de type personne
    var liste_personne:MutableList<Personne> = mutableListOf()
    liste_personne.add(personne)
    liste_personne.add(personne)
    liste_personne.add(personne)

    // liste d'objets en texte JSON
    val json_string = Gson().toJson(liste_personne)

    // enregistrement fichier (partie privée de l'appli)
    fileoutputStream =  context.openFileOutput(nom_fichier, Context.MODE_PRIVATE)
    fileoutputStream.write(json_string.toByteArray())
    callback_texte("Ecriture fichier effctuée\n $json_string")
    fileoutputStream.close()
}

fun lire_text(context:Context, callback_texte:(texte_retour:String)->Unit={})  {
    var fileInputStream:FileInputStream?=null
    val nom_fichier ="test.txt"
    val file_exist = context.getFileStreamPath(nom_fichier).exists()
    // fichier existant ?
    if (file_exist) {
        // Récupération contenu fichier
        fileInputStream = context.openFileInput(nom_fichier)
        var inputStreamReader: InputStreamReader = InputStreamReader(fileInputStream)
        val bufferedReader: BufferedReader = BufferedReader(inputStreamReader)
        val stringBuilder: StringBuilder = StringBuilder()
        var text: String? = null
        while ({ text = bufferedReader.readLine(); text }() != null) {
            stringBuilder.append(text)
        }
        callback_texte(stringBuilder.toString())

        // passage de texte JSON en liste de personnes
        val json_str:String = stringBuilder.toString()
        val stype = object : TypeToken<List<Personne>> () {}.type
        val liste_personne:List<Personne> = Gson().fromJson<List<Personne>>(json_str,stype)
        println(json_str)
        // Affichage liste
        for (une_personne in liste_personne) {
            println ("--> $une_personne")
        }
    } else {
        callback_texte("Fichier inexistant")
    }
}

Ce qui donne :

Troisième cas : écriture fichier dans répertoire externe

En Java, pour écrire sur un répertoire « public » comme « Mes documents » ou « Download » il était possible de le faire via « context.getExternalStoragePublicDirectory ».

Or en kotlin, cette fonction n’est pas disponible, voire depréciée depuis Android Q.

Nous allons voir comment créer un fichier et y insérer du contenu avant toute chose :

    val nom_fichier = "fichier_externe.txt"

    val path = context.getFilesDir().getAbsolutePath()
    val fichier = File(path, nom_fichier)

    println("path : " + fichier.path)
    println("absoluteFile  : " + fichier.absoluteFile)
    println("absolutePath  : " + fichier.absolutePath)
    println("canonicalFile : " + fichier.canonicalFile)
    println("canonicalPath : " + fichier.canonicalPath)

    println("")

    // création du fichier
    val isNewFileCreated :Boolean = fichier.createNewFile()
    println (" isNewFileCreated : $isNewFileCreated")

    // fichier déja crée ?
    if (!isNewFileCreated) {
        // suppression du fichier
        val fic_efface = fichier.delete()
        println("fichier effacé : $fic_efface" )

    }

    println ("Fichier existant : " + fichier.exists())
    println ("Re création du fichier : " + fichier.createNewFile())
    println ("Possibilité d'écrire dans le fichier : " + fichier.canWrite())

    // Ecriture via "PrintWriter"
    println (" ")
    println ("Ecriture via printWriter")
    println ("taille fichier : " + fichier.length())
    fichier.printWriter().use {
        out-> out.println("test")
    }
    
    println ("taille fichier : " + fichier.length())
    println ("Re création du fichier : " + fichier.createNewFile())

    // Ecriture via "writeText
    println (" ")
    println ("Ecriture via writeText")
    println ("taille fichier : " + fichier.length())
    fichier.writeText("ma ligne de texte")
    println ("taille fichier : " + fichier.length())
    fichier.writeText("ma ligne de texte")
    println ("taille fichier : " + fichier.length())

    // Ecriture via "Files.write"
    println (" ")
    println ("Ecriture via Files.write ")

    val contenu = " Du texte à GOGO de GOGO..."
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Files.write(fichier.toPath(),contenu.toByteArray(), StandardOpenOption.WRITE)
    }
    println ("taille fichier : " + fichier.length())

Autre exemple plus court sauvegardant une liste d’objet en format JSON

    // Liste d'objets de type personne
    var liste_personne:MutableList<Personne> = mutableListOf()
    liste_personne.add(personne)
    liste_personne.add(personne)
    liste_personne.add(personne)
    // liste d'objets en texte JSON
    val json_string = Gson().toJson(liste_personne)

    val path = context.getFilesDir().getAbsolutePath()
    val fichier = File(path, nom_fichier)

    fichier.createNewFile()
    fichier.writeBytes(json_string.toByteArray())
    println ("taille fichier : " + fichier.length())

et le code permettant de lire le fichier créé :

    val nom_fichier = "fichier_externe.txt"
    val path = context.getFilesDir().getAbsolutePath()
    val fichier = File(path, nom_fichier)

    if (fichier.exists()) {
        val texte_fichier = fichier.readText()
        println("tmp : $texte_fichier")

        // passage de texte JSON en liste de personnes
        val stype = object : TypeToken<List<Personne>> () {}.type
        val liste_personne:List<Personne> = Gson().fromJson<List<Personne>>(texte_fichier,stype)

        // Affichage liste
        for (une_personne in liste_personne) {
            println ("--> $une_personne")
        }

    } else {
        println("Fichier inexistant")
    }

Prise en compte vers un répertoire externe :

    /////////////////////////////////
    // Ecriture dans Download

    val chemin_download = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    println(" chemin_download : $chemin_download ")

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
        //// valable API <= 28 (nécessite de définir les permissions de lecture dans AndroidManifest.xml)
        val fichier2 = File(chemin_download, nom_fichier)
        try {
            fichier2.createNewFile()
            fichier2.writeBytes(json_string.toByteArray())
        } catch (e:Exception) {

        }

    } else {
        //// valable API >= 29

        // Création du fichier dans le stockage interne


        // Via le resolver copie du fichier vers le répertoire externe
        val une_url = URL(Uri.fromFile(fichier).toString())
        val mime_type = "text/plain"

        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, nom_fichier)
            put(MediaStore.MediaColumns.MIME_TYPE, mime_type)
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
        }
        val resolver = context.contentResolver
        val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
        if (uri != null) {
            une_url.openStream().use { input ->
                resolver.openOutputStream(uri).use { output ->
                    input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
                }
            }
        }
    }

et pour lire le fichier

fun lire_text_externe(context:Context, callback_texte:(texte_retour:String)->Unit={}) {


    val nom_fichier = "fichier_externe.txt"
    val chemin_download = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
    val fichier = File(chemin_download, nom_fichier)

    var texte_fichier = ""

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {

        //// valable API <= 28 (nécessite de définir les permissions de lecture dans AndroidManifest.xml)


        if (fichier.exists()) {
            texte_fichier = fichier.readText()
        } else {
            println("Fichier inexistant")
        }
    } else {

        //// valable API >= 29

        // Via le contentResolver
        val chemin_download = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        val fichier = File(chemin_download, nom_fichier)
        val resolver = context.contentResolver
        val inputStream:InputStream? = resolver.openInputStream(Uri.fromFile(fichier))
        val reader = BufferedReader(inputStream?.reader())
        texte_fichier = reader.readText()

    }

    if (!texte_fichier.equals("")) {
        println("texte_fichier : $texte_fichier")

        // passage de texte JSON en liste de personnes
        val stype = object : TypeToken<List<Personne>> () {}.type
        val liste_personne:List<Personne> = Gson().fromJson<List<Personne>>(texte_fichier,stype)
        // Affichage liste
        for (une_personne in liste_personne) {
            println ("--> $une_personne")
        }
    } else {
        println ("pas de contenu existant")
    }


}

Ressources:

Compose : Bannière PUB

A ce jour et par défaut il n’y a pas de composants avec Compose pour ajouter une bannière pub. Il faut ajouter manuellement une vue via AndroidView.

/**
 * Composable dédié à la pub de l'application
 * @param size Taille du bloc publicitaire
 * @param id Numéro identification de l'appli pour la pub
 */
@Composable
fun BannerAds(size:AdSize, id:String) {
    AndroidView(
        factory = { context ->
            // Creates custom view
            AdView(context).apply {
                this.setAdSize(size)
                this.adUnitId = id
                this.loadAd(AdRequest.Builder().build())
            }
        }
    )
}

ce qui donne :

Resources :

Voir …

  • voir les Composables ;
    • leçons d’utilisation par google : lien
    • les composants disponibles et manière de les programmer :
      • présentation google : lien
      • autre sur github : lien
    • la récupération des ressources iages, icônes, etc..
    • les variables remember et rememberState
    • les threads
      • thread vs coroutines : lien
    • le positionnement des composables (en dehors des fichiers « main »)
    • le principe d’animation basique
      • présentation google : lien
    • l’intégration de composable tiers
    • l’utilisation en kotlin de bibliothèques écrites en Java (graph et PDF)
  • revoir comment déclarer une classe « singleton » ;la navigation entre les vues
  • les accès aux fichiers
  • les autorisations (Internet, accès disques, etc)
  • les transformations objet vers JSON et inversement

KOTLIN : Prise de notes

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *