Creación de UI en Android: Composables y StateFlow con Hilt
Clase 12 de 19 • Curso de Android: Modo Offline con Room y Realm
Contenido del curso
- 4

Implementación de la Capa de Datos con Clean Architecture
10:17 - 5

Integración de Remote Data Storage con Vis Order API en Android
03:01 - 6

Integración de Room como Base de Datos en Android
18:08 - 7

Integración de Real en Android: Dependencias y Objetos de Datos
22:50 quiz de Arquitectura y Almacenamiento de Datos
- 8

Creación de Entidades y Repositorios en la Capa de Dominio en Android Studio
07:05 - 9

Implementación de LocalStorage con Room y Realm en Android Studio
13:33 - 10

Implementación de Repositorios en Android: Remote y Local Storage
11:49 - 11

Implementación de Repositorio para Preórdenes en Android Studio
09:55 quiz de Repositorios y Gestión de Datos
- 12

Creación de UI en Android: Composables y StateFlow con Hilt
26:26 - 13

Corrección de Errores en Android Studio y Visualización de Órdenes
09:59 - 14

Implementación de Barra de Navegación Inferior en Android Compose
13:05 - 15

Creación de un Detail Screen en Jetpack Compose para Android
25:50 - 16

Formulario de Preorden en Android Studio con Compose
20:02 - 17

Listar, visualizar y gestionar preórdenes en Android Studio
23:59 quiz de Capa de Presentación y Navegación
La programación en Android con Jetpack Compose representa un cambio paradigmático en el desarrollo de interfaces de usuario. A través de componentes declarativos y una arquitectura moderna, los desarrolladores pueden crear aplicaciones más mantenibles y escalables. En esta guía, exploraremos cómo implementar la capa de presentación en una aplicación Android utilizando ViewModel y Composables, elementos fundamentales para construir interfaces dinámicas y reactivas.
¿Cómo implementar la capa de presentación con ViewModel?
La capa de presentación es responsable de mostrar datos al usuario y manejar las interacciones. Para implementar esta capa correctamente, necesitamos crear un ViewModel que gestione el estado de nuestra aplicación.
Creación del ViewModel
Para comenzar, necesitamos crear un paquete de presentación y nuestro primer ViewModel:
@HiltViewModel
class OrderViewModel @Inject constructor(
private val orderRepository: OrderRepository
) : ViewModel() {
data class HomeState(
val isLoading: Boolean = false,
val isError: Boolean = false,
val data: List<Order> = emptyList()
)
val homeState: StateFlow<HomeState> = orderRepository.getOrders()
.distinctUntilChanged()
.map { result ->
result.fold(
onSuccess = { orders ->
HomeState(
isLoading = false,
isError = false,
data = orders
)
},
onFailure = {
HomeState(
isLoading = false,
isError = true,
data = emptyList()
)
}
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = HomeState(isLoading = true)
)
}
En este código:
-
Usamos la anotación
@HiltViewModelpara que Hilt pueda inyectar este ViewModel en el grafo de dependencias. -
Creamos una clase de datos
HomeStateque representa el estado de nuestra vista, con tres propiedades:isLoading: indica si los datos están cargandoisError: indica si ocurrió un errordata: contiene la lista de órdenes
-
Utilizamos
StateFlowpara emitir actualizaciones del estado a la UI. -
Transformamos el flujo de datos del repositorio usando operadores como
distinctUntilChangedymap. -
Convertimos el flujo en un
StateFlowusandostateIn, especificando:- El alcance del ViewModel
- Cuándo debe estar activo el flujo
- Un valor inicial (estado de carga)
¿Cómo crear Composables para mostrar datos en la UI?
Una vez que tenemos nuestro ViewModel configurado, necesitamos crear Composables para mostrar los datos en la interfaz de usuario.
Configuración de dependencias
Primero, agreguemos la biblioteca Coil para cargar imágenes:
// En build.gradle del módulo
implementation "io.coil-kt:coil-compose:2.4.0"
Creación del HomeScreen
Ahora, creemos nuestro Composable principal:
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
viewModel: OrderViewModel = hiltViewModel()
) {
val state by viewModel.homeState.collectAsState()
when {
state.isLoading -> {
Column(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(color = MaterialTheme.colors.primary)
}
}
state.isError -> {
Column(
modifier = modifier
.fillMaxSize()
.background(MaterialTheme.colors.background),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Error de datos",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
}
else -> {
LazyColumn(
modifier = modifier
.padding(start = 8.dp, top = 8.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(state.data) { order ->
ItemView(order = order)
}
}
}
}
}
En este Composable:
- Obtenemos una referencia al ViewModel usando
hiltViewModel() - Recopilamos el estado del ViewModel usando
collectAsState() - Utilizamos una estructura
whenpara mostrar diferentes UI según el estado:- Un indicador de progreso circular durante la carga
- Un mensaje de error si algo salió mal
- Una lista de elementos si todo está bien
Creación del ItemView
Para mostrar cada elemento de la lista, creamos un Composable separado:
@Composable
fun ItemView(order: Order) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
val painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current)
.data(order.imageUrl)
.placeholder(R.drawable.progress_indeterminate_horizontal)
.error(R.drawable.stat_notify_error)
.build()
)
Image(
painter = painter,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
)
Column(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
verticalArrangement = Arrangement.Center
) {
Text(
text = order.customerName,
style = MaterialTheme.typography.titleLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = order.item,
style = MaterialTheme.typography.bodyLarge,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterVertically)
.padding(end = 16.dp)
)
}
}
En este Composable:
- Utilizamos Coil para cargar imágenes de forma eficiente con manejo de estados de carga y error
- Mostramos la imagen del producto en forma circular
- Mostramos el nombre del cliente y el nombre del producto en una columna
- Agregamos un icono de flecha para indicar que el elemento es seleccionable
Previsualización del ItemView
Para facilitar el desarrollo, podemos agregar una previsualización:
@Preview(showBackground = true)
@Composable
fun ItemViewPreview() {
ItemView(
order = Order(
id = "1",
customerName = "Cliente de ejemplo",
item = "Producto de ejemplo",
total = 99.99,
imageUrl = "https://example.com/image.jpg"
)
)
}
Esta previsualización nos permite ver cómo se verá nuestro Composable sin necesidad de ejecutar la aplicación.
¿Cómo funciona la arquitectura de presentación en Android?
La arquitectura de presentación en Android con Jetpack Compose sigue un patrón unidireccional de flujo de datos:
- ViewModel: Mantiene el estado de la UI y procesa eventos
- StateFlow: Emite actualizaciones de estado a los Composables
- Composables: Renderizan la UI basada en el estado actual
Beneficios de esta arquitectura:
- Separación de responsabilidades: El ViewModel maneja la lógica de negocio, mientras que los Composables se encargan solo de la UI
- Reactividad: Los cambios en el estado se propagan automáticamente a la UI
- Testabilidad: Es más fácil probar cada componente de forma aislada
- Mantenibilidad: El código es más organizado y fácil de entender
La implementación de la capa de presentación con ViewModel y Composables es fundamental para crear aplicaciones Android modernas y robustas. Con estos componentes, podemos construir interfaces de usuario dinámicas que respondan eficientemente a los cambios de datos y proporcionen una experiencia de usuario fluida.
¿Has implementado alguna vez esta arquitectura en tus proyectos? Comparte tu experiencia en los comentarios y cuéntanos qué desafíos has enfrentado al trabajar con Jetpack Compose.