Saltar a contenido

View Model

Usar esta docuentación:
* Guía inicial * Codelab para viewmodel: scrambleum

En Compose tenemos State para mantener y pasar datos entre componentes.

Con ViewModel iremos un paso más alla liberando a Activity y Fragment del mantenimiento del estado de la app.

Forma parte de Jetpack y se corresponde con la arquitectura MVVM (Nodel-View-View-Model).

Primer vistazo a ViewModel

El objetivo es desacoplar la lógica de presentación de los componentes.

ViewModel se situa entre el modelo (capa de datos ) y el UI

Alt text

La idea principal es que los estados van hacia abajo, del viewModel al UI y los eventos(pulsaciones usuario) haciea arriba del UI al ViewModel.

--REVISAR--
ViewModel: * La vista recibe actualizaciones del estado IU desde el ViewModel * Viewmodel recibe actualizaciones mediante suscripción a otro componente: LiveData

LiveData:

  • Es un componente observable
  • Contiene un estado y cuando hay cambio avisa a los subscriptores.

Las Activities y Fragment se subscriben a LiveData a través del ViewModel.

LiveData está pendiente del ciclo de vida de Activity y Fragment (onDestroy, etc).

stateDiagram-v2 direction LR View --> ViewModel state ViewModel { direction RL [*] --> LiveData LiveData --> [*] } ViewModel --> View
Modelo de subscripción:
stateDiagram-v2 Activity1 --> LiveData: Observe Fragment -->LiveData: Observe Activity2 -->LiveData: Observe

Partimos de un ejemplo con una pantalla principal con el estado y un componente que lee un nombre y actualiza el estado y un Text de salida

@Composable
fun MainScreen(){
    val nameState = remember {mutableStateOf("")}

    Surface(
        color =
        modifier=
    ){
        MainLayout(nameState.value
           ){
               newName -> nameState.value = newName
           }
    }
}

En MainLayout añadimos dos parámetros: name y una lambda *cammbioTexto"

@Composable
fun MainLayout(
    name: String,
    cambiaTexto: (String)-> Unit
    ){
    Column {
        Text()
        TextField(
            value=name,
            onValueChanged=cambioTexto
        )
        Text(
            text= name
        )
    }
}

Transformamos este código para incluir ViewModel. En un nuevo fichero creamos una clase derivada con un atributo LiveData para el estado y una función para recibir los eventos.

MainViewModel
class MainViewModel: ViewModel() {
    val nameState: MutableLiveData("")

    fun onTextFieldChange(nuevoTexto: String){

    }
}

Hemos creado el estado nameState

Ahora cambiamos MainScreen para admitir el ViewModel, que damos valor por defecto para no cambiar en otras parte del código.
Cambiamos la asignación de nameState
También cambiamos la lambda por una notificación del viewmovel con el métodos público

@Composable 
fun MainScreen(viewModel: MainViewModel = MainViewModel())){
    val nameState = viewModel.nameState.observeAsState("")

    Surface(
        color =
        modifier=
    ){
        MainLayout(nameState.value
           ){
               newName -> viewModel.onTextFieldChange(newName)
           }
    }
}

Textfield ligado con state

Parámetros de Textfield * value: String texto a mostrar * onValueChange: (String)->Unit lambda de evento por cada cambio en la entrada.

Usaremos los estados para comparar las nuevas entradas.

Por ejemplo: Para el nombre del estudiante. En el componente padre añadimos el estado newStudenteState

@Composable  
fun MainScreen() {  
    val studentsState = remember { mutableStateListOf("Esther", "Jaime") }  
    val newStudentState = remember { mutableStateOf("") }  
    Surface(  
        color = Color.LightGray,  
        modifier = Modifier.fillMaxSize()  
    ) {  
        StudentList(  
            studentsState,  
            { studentsState.add(newStudentState.value) },  
            newStudentState.value,  
            { newStudent -> newStudentState.value = newStudent }  
        )  
    }  
}
Modificamos la clase hija para pasar el nombre inicial y una función lambda para devolver el evento de nuevo valor:

@Composable  
fun StudentList(  
    students: List<String>,  
    onButtonClick: () -> Unit,  
    studentName: String,  
    onStudentNameChange: (String) -> Unit  
) {  
    Column(  
        modifier = Modifier.fillMaxSize(),  
        horizontalAlignment = Alignment.CenterHorizontally  
    ) {  
        for (student in students) {  
            StudentText(name = student)  
        }  
        TextField(  
            value = studentName,  
            onValueChange = onStudentNameChange  
        )  
        Button(  
            onClick = onButtonClick
        ) {  
            Text(text = "Add new student")  
        } 
    }
}