Vamos a partir de la solución del ejercicio 3 del tema 3.6 3_formulario_add_contacto_agenda. Si no dispones de una solución del mismo puedes pedírsela al profesor. Una vez copiado vamos a renombrarlo a 2_intents_agenda
En primer lugar vamos ha hacer que los métodos en ContactoRepository simulen un retardo aleatorio entre 0 y 1 segundo. Para ello vamos a crear un método privado retardoAleatorio() que nos lo genere.
private fun retardoAleatorio() = Thread.sleep((Math.random() * 1000).toLong())
Al introducir este retardo los métodos de CRUD ahora deberán ser asíncronos y ejecutarse en el contexto de un hilo secundario. Para ello vamos a utilizar withContext(Dispatchers.IO) siguiendo el siguiente esquema.
suspend fun get(): MutableList<Contacto> = withContext(Dispatchers.IO) { retardoAleatorio() dao.get().map { it.toContacto() }.toMutableList() }
Cambia el resto de métodos de ContactoRepository para que sigan el mismo esquema.
Vamos ahora a modificar .ui.features.formcontacto.ContactoViewModel para que use los métodos asíncronos del repositorio.
fun setContactoState(idContacto: Int) { viewModelScope.launch { ... } } ... is ContactoEvent.OnSaveContacto -> { ... runBlocking { if (editandoContactoExistenteState) { contactoRepository.update(c) } else { contactoRepository.insert(c) } } ... } ...
✋ Importante: Fíjate que el
inserty elupdatese ejecutan dentro de un bloquerunBlocking. Esto es para crear un contexto de corrutina bloqueante para evitar efectos colaterales al volver posteriormente a la pantalla de de listar contactos. Esto sucede porque cómo esto pude tardar un tiempo, si lo hacemos totalmente asíncrono puede suceder que se cargue la lista antes de la inserción o actualización y no se vea reflejados los cambio. Por eso, hasta que no termine el proceso no saldremos de la pantalla bloqueado el hilo principal.
Aunque no los vamos aver, usandoFlowsse solucionará el problema y podremos hacerlo de forma totalmente asíncrona.
También, podríamos utilizar algún tipo de componente que nos indique espera comoCircularProgressIndicatorde Material Design.
Vamos ahora a modificar ahora .ui.features.vercontactos.ListaContactosViewModel para que use los métodos asíncronos del repositorio.
Marcaremos como suspendido el método privado getContactos() y su llamada en el constructor. Por esa razón ya no podremos llamarla directamente en la definición de la propiedad sino en una función init quedando un código similar al siguiente.
private var _listaContactosState by mutableStateOf(mutableListOf<ContactoUiState>()) val listaContactosState: List<ContactoUiState> get() = _listaContactosState private suspend fun getContactos(): MutableList<ContactoUiState> = contactoRepository.get().map { it.toContactoUiState() }.toMutableList() init { viewModelScope.launch { _listaContactosState = getContactos() } }
Por último, debes realizar las operaciones de borrado y actualización de contactos de forma asíncrona en su callback usando viewModelScope.launch { ... }.
Siguiendo los pasos vistos en el tema y ayudándonos de las utilidades definidas en RegistroContratosCompose.kt vamos primero a añadir los permisos necesarios a AndroidManifest.xml y después vamos a .ui.features.formcontacto.FormContactoScreen y en la función de composición CabeceraFoto vamos a definir registrar los contratos para hacer las operaciones definidas en los OutlinedIconButton para cambiar el avatar.
📌 Nota: Si ves que no se actualiza el componente ImagenContacto. Posiblemente es porque no se recompone al cambiar el valor de la propiedad
foto. Para solucionarlo puedes usarrememberindicándole que se recompute cuando cambie el valor defoto. Por ejemplo:val imagenSinFoto = rememberVectorPainter(image = Icons.Filled.Face2) var painterFoto = remember(foto) { foto?.let { BitmapPainter(it) } ?: imagenSinFoto }
Para ello vamos a .ui.features.vercontactos.ContactoListItem donde definimos la tarjeta donde se muestran en la lista los datos de un contacto.
Fíjate que en la función de composición AccionesContacto donde aparecen los botones de icono con las posibles acciones a realizar sobre el contacto seleccionado nos faltan por definir dos callbacks para las acciones de llamar onLlamarClicked y enviar correo onCorreoClicked.
Siguiendo los pasos vistos en el tema y ayudándonos de las utilidades definidas en RegistroContratosCompose.kt vamos a definir dichos callbacks.