Contenido del curso
Arquitectura y Almacenamiento de Datos
Repositorios y Gestión de Datos
Capa de Presentación y Navegación
- 12

HomeScreen con ViewModel, StateFlow y Coil
26:24 min - 13

Primera app Android con Hilt, Retrofit y Room
09:58 min - 14

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

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

Formulario de preórdenes con SharedFlow en Compose
Viendo ahora - 17

Lista y sincroniza preórdenes con StateFlow
23:58 min
Optimización y Flexibilidad
Formulario de preórdenes con SharedFlow en Compose
Resumen
Construir un formulario en Jetpack Compose que guarde datos en local y los envíe al servidor parece complejo, pero con un ViewModel bien estructurado y Hilt como inyector de dependencias se vuelve un flujo claro. Aquí verás cómo armar una pantalla de creación de pre-orders con dos campos, validación de botón y notificaciones por eventos.
Cómo se estructura el ViewModel para una pantalla de creación en Compose
El punto de partida es un PreOrderViewModel dentro de la capa presentation. La anotación @HiltViewModel permite que Hilt lo inyecte automáticamente, y el @Inject constructor recibe el PreOrderRepository que conecta con la lógica de almacenamiento [01:10].
A diferencia de un OrderViewModel que expone un state con la última emisión, aquí necesitas notificar acciones puntuales: éxito o error al guardar. Por eso se usa un MutableSharedFlow en lugar de un StateFlow.
¿Cuál es la diferencia entre StateFlow y SharedFlow? El
StateFlowguarda y emite la última actualización, ideal para estados de UI. ElSharedFlowno almacena por defecto el último valor y se usa para notificaciones puntuales como toasts o navegación.
Cómo modelar los eventos con una sealed class
Una sealed class llamada CreateEvent agrupa los posibles resultados con dos data object:
Success: la pre-order se guardó local y remotamente.Error: la pre-order quedó local pero el envío al servidor falló.
El MutableSharedFlow<CreateEvent> se expone con private set para que solo el ViewModel pueda emitir. La función onSavePreOrder(customerName: String, product: String) corre dentro de viewModelScope.launch, ya que savePreOrder del repositorio es una función suspend [03:20].
Cómo se construye la composable CreateScreen paso a paso
La función CreateScreen lleva la anotación @Composable y recibe el PreOrderViewModel por parámetro. Hilt provee la utilidad hiltViewModel() para inyectarlo directamente desde el sistema de navegación.
Dentro de la composable se declaran tres variables con remember y rememberSaveable para sobrevivir a cambios de configuración:
productName: string vacío inicial, almacena el nombre del producto.customerName: string vacío inicial, guarda el nombre del cliente.isButtonEnabled: boolean que activa el botón solo si ambos campos tienen contenido.
kotlin var productName by rememberSaveable { mutableStateOf("") } var customerName by rememberSaveable { mutableStateOf("") } var isButtonEnabled by rememberSaveable { mutableStateOf(false) }
Cómo observar eventos del ViewModel desde la UI
Un LaunchedEffect(Unit) se encarga de coleccionar el eventFlow con collectLatest. Según el evento recibido, se dispara un Toast distinto usando LocalContext.current [05:40]:
- En
Success: "Preorden guardada y enviada correctamente al servidor". - En
Error: "Preorden guardada localmente, pero el envío al servidor falló".
Este patrón mantiene la UI desacoplada del resultado, y permite que el ViewModel notifique sin guardar estado innecesario.
¿Por qué usar LaunchedEffect con SharedFlow? Porque permite recolectar emisiones únicas dentro del ciclo de vida de la composable sin perder eventos durante recomposiciones.
Cómo organizar el layout del formulario con Column y modifiers
El contenedor principal es un Column con Modifier.padding(16.dp).fillMaxSize(), alineado vertical y horizontalmente al centro. Dentro se apilan los siguientes elementos:
- Una
ImageconpainterResource(R.drawable.top_image), padding inferior de 24.dp,fillMaxWidth,height(280.dp), esquinas redondeadas conclipde 8.dp ycontentScale = ContentScale.Crop. - Un
TextFieldpara el nombre del producto, conlabel"Nombre del producto" ymodifierque ocupa todo el ancho. - Un
Spacerde 16.dp como separación. - Un segundo
TextFieldpara el nombre del cliente con su propiolabel. - Un
Spacerde 24.dp antes del botón. - Un
Buttonconenabled = isButtonEnabled,fillMaxWidthy texto "Guardar pre-order".
En cada onValueChange se actualiza la variable correspondiente y se recalcula isButtonEnabled validando que ambos campos sean distintos de vacío [09:15].
Cómo limpiar el formulario después de guardar
Una mejora útil dentro del collectLatest es resetear los campos cuando llega un evento. Al finalizar el guardado se asigna productName = "", customerName = "" e isButtonEnabled = false. Así el usuario ve el formulario limpio y listo para una nueva pre-order [13:50].
Cómo conectar la pantalla con la navegación principal
El último paso es ir al MainScreen, ubicar la ruta create dentro del NavHost y reemplazar el placeholder por la llamada a CreateScreen(). Al compilar el proyecto, Android Studio despliega el formulario en el emulador con sus dos inputs y el botón inicialmente desactivado.
En la prueba con conexión activa, al ingresar "Control PS5 Room" como producto y "Julián" como cliente, el botón se habilita, el guardado funciona y el toast confirma el envío al servidor. En modo avión con "iPad Air Room" para "Carlos", la consola muestra que la API no respondió y el toast indica que solo se guardó localmente.
¿Qué patrón usas tú para notificar resultados desde un ViewModel a tu composable? Cuéntalo en los comentarios.