Cómo diseñar una navegación y listas en Android con Kotlin

Una parte importante al explorar cualquier lenguaje de programación en la parte de diseño de interfaces, es el diseño de tablas o listas, en este artículo les explicaré cómo diseñar listas en Android, específicamente usando Kotlin. Siendo este post la segunda entrega de diseño de interfaces, en seguida de los formularios, no solo tocaré el tema de listas, también las navegaciones, parte esencial de cualquier tipo de aplicación.

1. Creación del proyecto

Para la creación del proyecto, sencillamente nos vamos a crear una Empty Activity, para efectos de este ejemplo Prueba, seleccionando el lenguaje Kotlin, esperamos a que se genere el proyecto, que todas las barras de progreso se hayan llenado, y modificamos el class MainActivity.

[MainActivity.kt]
...
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            PruebaTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) {
                    innerPadding -> AppContent(
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

En seguida eliminamos las funciones que vienen abajo de la clase, a día que escribo este artículo deben ser algo como Greeting y GreetingPreview, para añadir las funciones que se aprecian abajo.

[MainActivity.kt]
...
@Composable
fun AppContent(modifier: Modifier = Modifier) {
    val navController = rememberNavController()

    NavHost(navController = navController, startDestination = "productos") {
        composable("productos") { LstProductosContent(navController, modifier) }
        composable("ventas") { LstVentasContent(navController, modifier) }
    }
}

@Preview(showBackground = true)
@Composable
fun AppContentPreview() {
    PruebaTheme {
        AppContent()
    }
}

Explico un poco el código de la función AppContent, para ser precisos, la creación de la constante navController, el cual recibe servirá como el primer parámetro para la creación del elemento NavHost, el segundo parámetro será la vista que aparecerá por defecto al iniciar la aplicación, al interior del elemento se encuentra la configuración de las vistas, cada una apuntando a una diferente función, productos -> LstProductosContent, ventas -> LstVentasContent, así sucesivamente conforme vaya creciendo la aplicación. El siguiente paso será crear las funciones vinculadas a las vistas.

Para poder utilizar la navegación será requerido que modifiquemos el archivo build.gradle.kts, el cual podremos localizar rapidamente si ponemos la vista en modo proyecto, al interior de la carpeta app.

Ubicado el archivo procedemos a colocar las siguientes cuatro dependencias, coloco los puntos suspensivos (…), porque toda configuración y código es diferente según el proyecto.

[build.gradle.kts]
plugins {
    ...
}

android {
    ...
}

dependencies {
    ...
    implementation("androidx.navigation:navigation-compose:2.7.3")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
}

2. Configuración de vistas

Comencemos creando las funciones mencionadas previamente LstProductosContent y LstVentasContent.

[MainActivity.kt]
...
@Composable
fun LstProductosContent(navController: NavHostController, modifier: Modifier) {
    val scrollState = rememberScrollState()

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(24.dp)
            .horizontalScroll(scrollState)
            .padding(8.dp),
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.Top
    ) {
        Button(
            onClick = {
                navController.navigate("ventas")
            },
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Transparent,
                contentColor = Color.Blue
            ),
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(
                "Ventas",
                style = TextStyle(textDecoration = TextDecoration.Underline),
                textAlign = TextAlign.Start,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Spacer(modifier = Modifier.height(16.dp))
    }
}

@Composable
fun LstVentasContent(navController: NavHostController, modifier: Modifier) {
    val scrollState = rememberScrollState()

    Column(
        modifier = modifier
            .fillMaxSize()
            .padding(24.dp)
            .horizontalScroll(scrollState)
            .padding(8.dp),
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.Top
    ) {
        Button(
            onClick = {
                navController.navigate("productos")
            },
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Transparent,
                contentColor = Color.Blue
            ),
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(
                "Productos",
                style = TextStyle(textDecoration = TextDecoration.Underline),
                textAlign = TextAlign.Start,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Spacer(modifier = Modifier.height(16.dp))
    }
}

Explico un poco el código de las funciones, ambas tienen una constante para almacenar el estado del scroll, esto resultará últil para las listas, ya que es muy habitual que estas mismas tomen más pantalla horizontal y esto en su defecto requiere que el usuario pueda desplazarse para revisar esa parte recorrida, en seguida hay un Column que entre sus varias propiedas asignadas recibe el scroll, al interior de la función colocamos un botón que nos permita navegar hacia otra vista, alternando en cada diferente función.

3. Creando una lista

Una vez configuradas las vistas, procedemos a crear una lista, para efectos de este ejemplo será creada en LstProductosContent.

[MainActivity.kt]
...
fun LstProductosContent(navController: NavHostController, modifier: Modifier) {
    data class Producto(val nombre: String, val precio: Double, val existencias: Int)
    val productos = remember {
        mutableStateListOf(
            Producto("Sponch Fresa", 23.0, 10),
            Producto("Emperador Combinado", 22.0, 10),
            Producto("Florentinas Cajeta", 20.0, 8)
        )
    }
    // productos[index] = Producto(nombre, precio, existencias)
    ...
    Column(
        ...
    ) {
        ...
        Button(
            onClick = {
                productos.add(Producto("Canelitas", 21.0, 12))
            },
            colors = ButtonDefaults.buttonColors(
                containerColor = Color.Black,
                contentColor = Color.White
            ),
            modifier = Modifier.fillMaxWidth()
        ) {
            Text(
                "Agregar Producto Prueba",
                style = TextStyle(textDecoration = TextDecoration.Underline),
                textAlign = TextAlign.Start,
                modifier = Modifier.fillMaxWidth()
            )
        }
        Spacer(modifier = Modifier.height(16.dp))
        Row {
            Text("Nombre", modifier = Modifier.width(150.dp), fontWeight = FontWeight.Bold)
            Text("Precio", modifier = Modifier.width(100.dp), fontWeight = FontWeight.Bold)
            Text("Existencias", modifier = Modifier.width(100.dp), fontWeight = FontWeight.Bold)
            Text("Eliminar", modifier = Modifier.width(100.dp), fontWeight = FontWeight.Bold)
        }
        Divider()
        productos.forEachIndexed { index, producto ->
            val bgColor = if (index % 2 == 0) Color(0xFFF5F5F5) else Color.White

            Row (
                modifier = Modifier
                .background(bgColor)
            ) {
                Text(producto.nombre, modifier = Modifier
                    .width(150.dp)
                )
                Text("$ ${producto.precio}", modifier = Modifier
                    .width(100.dp)
                )
                Text("x ${producto.existencias}", modifier = Modifier
                    .width(100.dp)
                )
                Button(onClick = {
                    productos.removeAt(index)
                }) {
                    Text("Eliminar")
                }
            }
        }
    }
}

El preview resultado se aprecia abajo:

El código previo puede agobiar, por lo que he hecho las anotaciones en el resultado, tenemos 2 elementos Button, uno para navegar y el otro para añadir un registro de prueba, al ejecutar podemos ver el dinamismo de la lista, al mismo tiempo ocurrirá lo mismo si desplazamos la lista hacia la derecha para usar el botón para eliminar, los elementos rows como su nombre lo indica serán las filas, la primera row sirve para el encabezado y las otras rows se añaden por iteración en un forEachIndexed aplicado a la lista mutable de productos, precisamente antes del column observamos las respectivas declaraciones del data class y la constante para dicha lista.

Conclusión

El artículo ha llegado a su fin, abordamos la creación de una lista, un ejemplo para agregar una row, así como para eliminarla, suficientes para comprender la creación estas tablas. Espero este post les sea de utilidad y estaré al tanto de cualquier comentario, gracias por su tiempo.

Comments

No comments yet. Why don’t you start the discussion?

    Deja tus comentarios