Formulario de Preorden en Android Studio con Compose
Clase 16 de 19 • Curso de Android: Modo Offline con Room y Realm
Resumen
La creación de formularios en Android Compose es una habilidad fundamental para desarrolladores que buscan construir aplicaciones modernas y funcionales. Mediante el uso de ViewModels y estados reactivos, podemos crear interfaces de usuario que respondan eficientemente a las interacciones del usuario mientras mantenemos una arquitectura limpia y mantenible.
¿Cómo crear una pantalla de formulario con Compose?
Cuando desarrollamos aplicaciones Android con Jetpack Compose, la creación de formularios requiere una estructura organizada que separe la lógica de negocio de la interfaz de usuario. Para implementar una pantalla de creación de preórdenes, necesitamos configurar un ViewModel que maneje los datos y eventos, junto con una Composable que presente la interfaz al usuario.
Implementando el ViewModel para la pantalla de creación
El primer paso es crear un ViewModel específico para nuestra pantalla de creación de preórdenes. Este componente será responsable de manejar la lógica de negocio y comunicarse con el repositorio.
@HiltViewModel
class PreorderViewModel @Inject constructor(
private val preorderRepository: PreorderRepository
) : ViewModel() {
sealed class CreateEvent {
data object Success : CreateEvent()
data object Error : CreateEvent()
}
private val _eventFlow = MutableSharedFlow<CreateEvent>()
val eventFlow = _eventFlow.asSharedFlow()
fun savePreorder(customerName: String, product: String) {
viewModelScope.launch {
try {
val result = preorderRepository.savePreorder(
Preorder(
customerName = customerName,
product = product
)
)
_eventFlow.emit(CreateEvent.Success)
} catch (e: Exception) {
_eventFlow.emit(CreateEvent.Error)
}
}
}
}
En este ViewModel:
- Utilizamos
@HiltViewModel
para la inyección de dependencias - Creamos una clase sellada
CreateEvent
para manejar los diferentes estados de la operación - Implementamos un
SharedFlow
para emitir eventos de notificación - Definimos una función
savePreorder
que se comunica con el repositorio
La diferencia clave entre StateFlow
y SharedFlow
es que el primero almacena el último valor emitido y es ideal para estados de UI, mientras que el segundo es más adecuado para eventos y notificaciones sin necesidad de mantener un estado.
Diseñando la Composable del formulario
Una vez que tenemos nuestro ViewModel, podemos crear la Composable que mostrará el formulario al usuario:
@Composable
fun CreateScreen(viewModel: PreorderViewModel = hiltViewModel()) {
val context = LocalContext.current
var productName by remember { mutableStateOf("") }
var customerName by remember { mutableStateOf("") }
var isButtonEnabled by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
viewModel.eventFlow.collectLatest { event ->
when (event) {
is PreorderViewModel.CreateEvent.Success -> {
Toast.makeText(context, "Preorden guardada y enviada correctamente al servidor", Toast.LENGTH_SHORT).show()
productName = ""
customerName = ""
isButtonEnabled = false
}
is PreorderViewModel.CreateEvent.Error -> {
Toast.makeText(context, "Preorden fue guardada localmente, pero el envío al servidor falló", Toast.LENGTH_SHORT).show()
productName = ""
customerName = ""
isButtonEnabled = false
}
}
}
}
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.top_image),
contentDescription = "Logo de la aplicación",
modifier = Modifier
.padding(bottom = 24.dp)
.fillMaxWidth()
.height(280.dp)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop
)
TextField(
value = productName,
onValueChange = {
productName = it
isButtonEnabled = productName.isNotEmpty() && customerName.isNotEmpty()
},
label = { Text("Nombre del producto") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
value = customerName,
onValueChange = {
customerName = it
isButtonEnabled = productName.isNotEmpty() && customerName.isNotEmpty()
},
label = { Text("Nombre del cliente") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { viewModel.savePreorder(customerName, productName) },
enabled = isButtonEnabled,
modifier = Modifier.fillMaxWidth()
) {
Text("Guardar preorden")
}
}
}
En esta Composable:
- Utilizamos
remember
para mantener el estado de los campos del formulario - Implementamos un
LaunchedEffect
para observar los eventos del ViewModel - Creamos una interfaz con campos de texto para el nombre del producto y del cliente
- Añadimos validación para habilitar el botón solo cuando ambos campos están completos
- Incluimos retroalimentación visual mediante Toast para informar al usuario sobre el resultado de la operación
¿Cómo manejar los estados y eventos en Compose?
El manejo adecuado de estados y eventos es crucial para crear interfaces de usuario reactivas y coherentes. En nuestra implementación, utilizamos diferentes enfoques para gestionar el estado de la UI y las notificaciones de eventos.
Diferencia entre StateFlow y SharedFlow
Cuando trabajamos con flujos en Kotlin, es importante entender las diferencias entre estos dos tipos:
- StateFlow: Mantiene y emite el último valor. Es ideal para representar estados de UI que necesitan persistir.
- SharedFlow: No almacena valores por defecto. Es perfecto para eventos de notificación que ocurren una vez y no necesitan ser recordados.
En nuestro ejemplo, utilizamos un SharedFlow para notificar cuando una preorden ha sido guardada exitosamente o cuando ha ocurrido un error, ya que estas son notificaciones puntuales que no necesitan mantenerse en el tiempo.
Validación de formularios
La validación de formularios es un aspecto importante para garantizar que los datos ingresados sean correctos antes de procesarlos:
isButtonEnabled = productName.isNotEmpty() && customerName.isNotEmpty()
Esta simple validación asegura que el botón de guardar solo se active cuando ambos campos contienen texto, evitando envíos de datos incompletos.
¿Cómo integrar la pantalla en la navegación de la aplicación?
Para que nuestra pantalla de creación sea accesible dentro de la aplicación, necesitamos integrarla en el sistema de navegación. En el contexto de una aplicación que utiliza Compose Navigation, esto implica añadir nuestra Composable a la ruta correspondiente:
// En el MainScreen o componente de navegación principal
NavHost(navController = navController, startDestination = "home") {
// Otras rutas...
composable("create") {
CreateScreen()
}
}
Con esta configuración, cuando el usuario navegue a la ruta "create", se mostrará nuestra pantalla de creación de preórdenes.
La implementación de formularios en Compose nos permite crear interfaces de usuario intuitivas y reactivas que mejoran la experiencia del usuario mientras mantienen una arquitectura limpia. El uso de ViewModels, estados reactivos y eventos nos ayuda a separar claramente las responsabilidades y crear código más mantenible y testeable.
¿Has implementado formularios en tus aplicaciones Android? Comparte en los comentarios tus experiencias o dudas sobre el manejo de estados y eventos en Compose.