# MVVM - aplicación de lotería Vamos a hacer una primera aplicación, donde no implementaremos por ahora la parte del Model (o sea, no manejaremos una base de datos) Nos servirá para hacer una aproximación sencilla al patrón MVVM. La aplicación hará lo siguiente: - primero haremos una pequeña app contador para explicar de manera fácil cómo usar el viewModel - después haremos una vista donde podremos usando viewModel elegir 6 números random 1 al 60, los números no se pueden repetir. Creamos la aplicación y de nuevo lo primero es dejar preparada la estructura de nuestra app: ![image.png](https://hackmd.io/_uploads/SkqXEBMmT.png) Cómo ves, crearemos un package view pero además otro con viewModels, ya ves que hemos creado la estructura que cuadra con VVM, a falta del model. En el paquete **views** creamos un archivo kotlin, **ContadorView.** Creamos una caja donde mostraremos el contador(por ahora a 0) y un accion buttón, por ahora, sin acción al pulsar. ``` @Composable fun Contador() { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { Text( text = "0", fontWeight = FontWeight.Bold, fontSize = 40.sp ) FloatingActionButton( onClick = { }, modifier = Modifier .align(Alignment.BottomEnd) .padding(15.dp) ) { Icon( imageVector = Icons.Default.Add, contentDescription = "", tint = Color.Blue ) } } } ``` En el mainActivity llamamos al Contador() y vemos cómo nos va quedando: ![image.png](https://hackmd.io/_uploads/r1hPdSzXa.png) Brutal! Ahora vamos a ver cómo manejamos los estados dentro del ViewModel ## Manejo de estados con ViewModel Antes de comenzar, se supone que ya has leído: [-Codelab: etapas del ciclo de vida de una Activity](https://developer.android.com/codelabs/basic-android-kotlin-compose-activity-lifecycle?hl=es-419&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-4-pathway-1%3Fhl%3Des-419%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-activity-lifecycle#0) Y si no lo has hecho como refuerzo a lo que vamos a ver: [- Codelab: ViewModel y el estado en Compose](https://developer.android.com/codelabs/basic-android-kotlin-compose-viewmodel-and-state?hl=es-419&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-4-pathway-1%3Fhl%3Des-419%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-viewmodel-and-state#0) Pero si eso es muy largo aquí un bonito artículo resumen, eso sí, en inglés: [Artículo sobre ViewModel](https://www.composables.com/tutorials/viewmodels-in-jetpack-compose) Sea como sea, como siempre se ve más fácil en la práctica. Dentro de viewModel vamos a crear el fichero ContadorViewModel: ``` class ContadorViewModel: ViewModel() { private val _contador = mutableStateOf(0) val contador : State<Int> = _contador fun add(){ _contador.value = _contador.value + 1 } // var contador = mutableStateOf(0) } ``` Como ves, no es un @Composable, es una clase de tipo [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel?hl=es-419#kotlin) La idea es que tengamos una variable que pueda ser leida pero no modificada por las vistas, esa variable en este caso será "contador", las modificaciones deberán realizarse siempre desde ContadorViewModel, mientras que en las vistas sólo se solicitará la modificación o se leerá el valor. ` private val _contador = mutableStateOf(0)` Creamos una variable privada de la clase _contador, se pone con _ porque es por convenio cómo se indica que almacenará un estado que no puede ser modificado por las vistas. Valor inicial 0. `val contador : State<Int> = _contador` Creamos contador, esta vez pública, donde copiamos el valor de la privada _contador. Como ves es del tipo State\<Int>, hablamos de los observables State/stateOf en este artículo: [- El estado y Composer](https://developer.android.com/jetpack/compose/state?hl=es-419) Pero digamos que lo que hacemos es contador varíe automáticamente en el momento que varía _contador en cualquier otra pantalla. ``` fun add(){ _contador.value = _contador.value + 1 } ``` La función add será llamada desde fuera y por eso es pública. Para poder utilizar correctamente los estados del ViewModel debemos añadirlo tanto en el MainActivity como en ContadorView por lo que: ``` //ContadorView @Composable fun Contador(viewModel: ContadorViewModel) { Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { Text( text = viewModel.contador.value.toString(), fontWeight = FontWeight.Bold, fontSize = 40.sp ) FloatingActionButton( onClick = { viewModel.add() }, modifier = Modifier .align(Alignment.BottomEnd) .padding(15.dp) ) { Icon( imageVector = Icons.Default.Add, contentDescription = "", tint = Color.Blue ) } } } ``` Como ves, pasamos el viewModel: ContadorViewModel y ya leemos el valor del contador para mostrarlo. ` text = viewModel.contador.value.toString(),` Y además cuando hacemos click en el botón llamamos al método add del viewModel. `onClick = { viewModel.add() },` En el MainActivity ``` class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val viewModel : ContadorViewModel by viewModels() setContent { LoteriaTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Contador(viewModel = viewModel) } } } } } ``` Ahora al pulsar el botón flotante el contador debería incrementarse. ## App Lotería y uso de la programación declarativa Vamos a crear la parte de la Lotería, creando en viewModel la clase LoteriaViewModel ``` class LoteriaViewModel: ViewModel() { private val _lottoNumbers = mutableStateOf(emptyList<Int>()) val lottoNumbers: State<List<Int>> = _lottoNumbers fun generateLottoNumbers(){ _lottoNumbers.value = (1..60).shuffled().take(6).sorted() } } ``` ## Vista de Lotería Creamos el archivo LoteriaView en Views ``` @Composable fun LoteriaView(viewModel: LoteriaViewModel) { val lottoNumbers = viewModel.lottoNumbers.value Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { if (lottoNumbers.isEmpty()) { Text(text = "Loteria", fontSize = 40.sp, fontWeight = FontWeight.Bold) } else { LotteryNumbers(numbers = lottoNumbers) } Button(onClick = { viewModel.generateLottoNumbers() }) { Text(text = "Generar", fontSize = 20.sp, fontWeight = FontWeight.Bold) } } } @Composable fun LotteryNumbers(numbers: List<Int>) { LazyRow( contentPadding = PaddingValues( horizontal = 16.dp, vertical = 8.dp ) ) { items(numbers) { number -> Box( contentAlignment = Alignment.Center, modifier = Modifier .padding(horizontal = 4.dp) .size(48.dp) .background(Color.Red, CircleShape) ) { Text( text = number.toString(), color = Color.White, fontSize = 24.sp ) } } } } ``` Para que se vea esta aplicación deberemos cambiar el MainActivity ``` class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //val viewModel : ContadorViewModel by viewModels() val viewModel : LoteriaViewModel by viewModels() setContent { LoteriaTheme { // A surface container using the 'background' color from the theme Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { // Contador(viewModel = viewModel) LoteriaView(viewModel = viewModel) } } } } } ```