Saltar a contenido

MVVM, LiveData, ViewBinding y RecyclerView (Actualizado 2025)

Este apartado profundiza en el uso moderno de MVVM, LiveData, ViewModel, View Binding y RecyclerView dentro del desarrollo Android con Kotlin + Jetpack (Compose y vistas tradicionales).

Concepto de MVVM

MVVM (Model – View – ViewModel) es un patrón de arquitectura que organiza la app en 3 capas:

  • Model: datos, lógica de negocio y acceso a fuentes externas (BD, API, repositorios).
  • View: interfaz de usuario (Activity, Fragment, Composable).
  • ViewModel: prepara datos para la vista, expone estados observables y gestiona la lógica de UI.

Ejemplo simple de estructura MVVM

Model:

data class Tarea(val id: Int, val titulo: String, val completada: Boolean)

ViewModel:

class TareaViewModel : ViewModel() {

    private val _tareas = MutableLiveData<List<Tarea>>()
    val tareas: LiveData<List<Tarea>> = _tareas

    fun cargarTareas() {
        _tareas.value = listOf(
            Tarea(1, "Estudiar Kotlin", false),
            Tarea(2, "Preparar clase de PMDM", true)
        )
    }
}

Vista (Compose):

@Composable
fun TareasView(viewModel: TareaViewModel = viewModel()) {
    val lista = viewModel.tareas.observeAsState(emptyList())

    Column {
        lista.value.forEach { tarea ->
            Text("${tarea.titulo} - ${if (tarea.completada) "" else ""}")
        }
    }
}

Funcionamiento de MVVM paso a paso

Los pasos actualizados para Android serían:

  1. Crear un nuevo proyecto con Compose.
  2. Definir los modelos (data classes).
  3. Crear el ViewModel con StateFlow o LiveData.
  4. Implementar la vista en Compose o XML.
  5. Suscribir la vista al estado del ViewModel.
  6. Ejecutar la app.

Model (Capa de datos)

Ejemplo moderno con repositorio:

data class Producto(val id: Int, val nombre: String, val precio: Double)

class ProductoRepository {
    fun obtenerProductos(): List<Producto> =
        listOf(
            Producto(1, "Monitor 27"", 199.99),
            Producto(2, "Teclado Mecánico", 89.99)
        )
}

View (Capa UI)

@Composable
fun ProductoItem(producto: Producto) {
    Row {
        Text(producto.nombre)
        Spacer(Modifier.width(16.dp))
        Text("${producto.precio} €")
    }
}

ViewModel (Lógica de la vista)

Ejemplo actualizado con StateFlow:

class ProductoViewModel : ViewModel() {

    private val repositorio = ProductoRepository()

    private val _uiState = MutableStateFlow(emptyList<Producto>())
    val uiState: StateFlow<List<Producto>> = _uiState

    fun cargarProductos() {
        _uiState.value = repositorio.obtenerProductos()
    }
}

Vista observando StateFlow:

@Composable
fun ProductosScreen(viewModel: ProductoViewModel = viewModel()) {
    val productos = viewModel.uiState.collectAsState()

    Column {
        productos.value.forEach { ProductoItem(it) }
    }
}

Gradle (Dependencias)

Ejemplo de dependencias modernas en Kotlin DSL:

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.0")
    implementation("androidx.activity:activity-compose:1.10.0")
    implementation("androidx.compose.ui:ui:1.7.0")
    implementation("androidx.recyclerview:recyclerview:1.4.0")
}

LiveData

LiveData es un contenedor observable que respeta el ciclo de vida.

Ejemplo con LiveData

class ContadorViewModel : ViewModel() {
    private val _contador = MutableLiveData(0)
    val contador: LiveData<Int> = _contador

    fun incrementar() {
        _contador.value = (_contador.value ?: 0) + 1
    }
}

Vista (Compose):

@Composable
fun ContadorScreen(vm: ContadorViewModel = viewModel()) {
    val valor = vm.contador.observeAsState(0)

    Column {
        Text("Valor: ${valor.value}")
        Button(onClick = { vm.incrementar() }) { Text("Sumar") }
    }
}

View Binding

Cómo habilitar ViewBinding (XML)

android {
    buildFeatures {
        viewBinding = true
    }
}

Ejemplo simple:

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.boton.setOnClickListener {
            binding.texto.text = "¡Hola ViewBinding!"
        }
    }
}

RecyclerView

RecyclerView permite mostrar grandes cantidades de datos de forma eficiente.

Layout item (item_producto.xml)

<TextView
    android:id="@+id/txtNombre"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18sp" />

ViewHolder

class ProductoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val txtNombre: TextView = view.findViewById(R.id.txtNombre)
}

Adapter

class ProductoAdapter(private val datos: List<Producto>) :
    RecyclerView.Adapter<ProductoViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductoViewHolder {
        val vista = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_producto, parent, false)
        return ProductoViewHolder(vista)
    }

    override fun onBindViewHolder(holder: ProductoViewHolder, position: Int) {
        val producto = datos[position]
        holder.txtNombre.text = producto.nombre
    }

    override fun getItemCount(): Int = datos.size
}

Actividades

  • AC 507 (RA2 / CE2a / IC1 / 1p). Implementa un RecyclerView que muestre una lista de nombres:

    "Ana", "Carlos", "Marta", "Elena", "Luis"
    

    Requisitos

    • Adapter y ViewHolder propios.
    • Layout item_nombre.xml con un único TextView.
    • El RecyclerView debe mostrarse en la Activity con LinearLayoutManager.

    NO es necesario MVVM todavía.

  • AC 508 (RA2 / CE2a / IC1 / 1p). ViewModel + RecyclerView + Listado de objetos. Combina los conceptos ya aprendidos:

    1. Crea un data class Alumno(nombre: String, nota: Double).
    2. Implementa un AlumnoViewModel que exponga un LiveData<List<Alumno>>.
    3. Prepara la lista inicial en el init del ViewModel.
    4. Muestra los datos en un RecyclerView usando un Adapter.
    5. Muestra en cada elemento:

      • nombre
      • nota
      • texto "APTO" si nota ≥ 5 o "NO APTO" en caso contrario.

    NO se requiere corrutinas ni repository.
    Solo MVVM + RecyclerView + LiveData.

  • PR 509 (RA2 / CE2a / IC1 / 5p). Construir la app TaskManager Ultra Mega Pro, que gestiona una lista de tareas simuladas. Debe cumplir:

    • Arquitectura MVVM completa
    • Carga de datos mediante corrutinas (suspend, viewModelScope.launch)
    • RecyclerView funcional
    • Posibilidad de marcar tareas como completadas

    Evaluación (5 puntos)

    Criterio Puntos
    MVVM bien estructurado 1.5
    Corrutinas correctamente implementadas 1.5
    RecyclerView 1
    Gestión de estados 0.5
    Limpieza y buenas prácticas 0.5
  • PR 510 (RA2 / CE2a / IC1 / 5p). Implementar una aplicación Android que funcione como una Pokédex básica, utilizando tecnologías modernas. La app debe mostrar:

    1. Una lista de Pokémon (imagen).
    2. Una pantalla de detalle.
    • Modelo (mínimo). Se ha de utilizar un fichero que haga las veces de base de datos.
    data class Pokemon(
        val id: Int,
        val nombre: String,
        val tipoPrincipal: String,
        val imagen: Int,
        val descripcion: String,
        var favorito: Boolean
    )
    
    • ViewModel

      • Lista:

        • LiveData/StateFlow con lista
        • loading
        • errors
    • Vista

      • ProgressBar
      • RecyclerView. Item del RecyclerView:

        • Imagen
      • Mensaje “No hay Pokémon”

      • Uso obligatorio de ViewBinding
      • Pantalla de detalle. Debe mostrar:

        • nombre
        • imagen
        • tipo
        • descripción

    Calificación

    Criterio Puntos
    MVVM correcto 1
    LiveData/StateFlow 1
    Corrutinas 1
    RecyclerView 1
    Detalle 1