10 - Almacenar en un archivo de texto los datos de una lista dinámica.

Hemos visto hasta ahora como mediante la llamada a la función 'mutableStateListOf' podemos crear una lista que permite sincronizar sus datos con Compose. Vimos que si agregamos o eliminamos elementos, los mismos se ven reflejados en pantalla en forma automática al utilizar funciones composables.

Veremos como podemos almacenar los datos de la lista dinámica en una estructura de datos muy sencilla que son los archivos de texto.

Cuando el programa se inicia procederemos a ver si existe el archivo de texto, en dicho caso procedemos a leerlo y cargar el mismo en la lista.

Problema

Se deben disponer dos controles para el ingreso del nombre de un contacto y su mail. Al presionar un botón almacenar dichos valores en una lista con estado y mostrar todos los contactos en pantalla, además añadirlos al final de un archivo de texto. Agregar un botón que lo pueda eliminar de la lista.

Finalmente cada vez que se inicie el programa si el archivo de texto existe proceder a leer los datos de los contactos.

Creamos el proyecto llamado 'Compose12'.

La interfaz visual a implementar es similar al concepto anterior, con la salvedad que los datos no se pierden cuando se detiene el programa:

eliminar elemento de una lista Jetpack Compose

El código que debemos implementar para poder eliminar elementos de una lista que mantiene el estado es:

package com.tutorialesprogramacionya.compose12

import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.Button
import androidx.compose.material.Divider
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import java.io.File

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        leerDatos()
        setContent {
            AdministrarContactos()
        }
    }

    fun leerDatos() {
        val file = File(filesDir, "contactos.txt")
        if (file.exists()) {
            val lista = file.readLines()
            var indice = 0
            while (indice < lista.size) {
                contactos.add(Contacto(lista.get(indice), lista.get(indice + 1)))
                indice += 2
            }
        }
    }
}

data class Contacto(val nombre: String, val mail: String)

val contactos = mutableStateListOf<Contacto>()


@Composable
fun AdministrarContactos() {
    val activity = LocalContext.current

    var nombre by remember { mutableStateOf("") }
    var mail by remember { mutableStateOf("") }
    Column() {
        OutlinedTextField(value = nombre,
            onValueChange = { nombre = it },
            label = { Text("Nombre de contacto") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(5.dp)
        )
        OutlinedTextField(value = mail,
            onValueChange = { mail = it },
            label = { Text("mail") },
            modifier = Modifier
                .fillMaxWidth()
                .padding(5.dp)
        )
        Button(onClick = {
            val nuevoContacto = Contacto(nombre, mail)
            contactos.add(nuevoContacto)
            val path = activity.getFilesDir()
            val file = File(path, "contactos.txt")
            file.appendText("${nombre}\n${mail}\n")
            nombre = ""
            mail = ""
        }) {
            Text(text = "Agregar", modifier = Modifier.fillMaxWidth())
        }
        LazyColumn() {
            itemsIndexed(contactos) { indice, contacto ->
                MostrarContacto(indice, contacto)
            }
        }
    }
}

@Composable
fun MostrarContacto(indice: Int, contacto: Contacto) {
    val context = LocalContext.current
    Text(text = contacto.nombre)
    Text(text = contacto.mail)
    Image(painter = painterResource(id = android.R.drawable.ic_delete), contentDescription = "",
        modifier = Modifier.clickable {
            contactos.removeAt(indice)
            grabarCambios(context)
        })
    Divider(
        modifier = Modifier
            .fillMaxWidth()
            .width(4.dp), color = Color.Black
    )
}

fun grabarCambios(context: Context) {
    val file = File(context.filesDir, "contactos.txt")
    file.delete()
    for (contacto in contactos) {
        file.appendText("${contacto.nombre}\n${contacto.mail}\n")
    }
}

Inmediatamente se inicia el Activity procedemos a llamar a un método donde se leerá el archivo de texto:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        leerDatos()
        setContent {
            AdministrarContactos()
        }
    }

El método 'leerDatos' verifica si existe el archivo 'contactos.text', en caso afirmativo llamamos al método readLines que nos retorna una lista con un elemento por cada línea del archivo de texto.

Como veremos posteriormente guardamos el nombre del contacto en una línea y su mail en otra. Procedemos mediante un while a extraer dos elementos consecutivos de la lista, crear un objeto de la clase Contacto y lo insertamos en nuestra lista dinámica para que se muestren automáticamente en pantalla:

    fun leerDatos() {
        val file = File(filesDir, "contactos.txt")
        if (file.exists()) {
            val lista = file.readLines()
            var indice = 0
            while (indice < lista.size) {
                contactos.add(Contacto(lista.get(indice), lista.get(indice + 1)))
                indice += 2
            }
        }
    }

La función lambda que agrega un elemento en la lista dinámica, también agrega dos líneas al final del archivo de texto:

        Button(onClick = {
            val nuevoContacto = Contacto(nombre, mail)
            contactos.add(nuevoContacto)
            val path = activity.getFilesDir()
            val file = File(path, "contactos.txt")
            file.appendText("${nombre}\n${mail}\n")
            nombre = ""
            mail = ""
        }) {

El proceso de borrado es más costoso, debido a que tenemos que recrear el archivo de texto nuevamente (lo borramos al archivo y volvemos a cargar con todos los elementos de la lista dinámica que ya no tiene el eliminado):

fun grabarCambios(context: Context) {
    val file = File(context.filesDir, "contactos.txt")
    file.delete()
    for (contacto in contactos) {
        file.appendText("${contacto.nombre}\n${contacto.mail}\n")
    }
}

Este proyecto lo puede descargar en un zip desde este enlace: Compose12.zip