¿Por qué necesitamos una arquitectura?
Imagínate que construyes una app pequeña colocando todo el código en una sola Activity. Al inicio parecerá que funciona bien, pero cuando quieras agregar nuevas features o corregir un error, todo se vuelve un caos, no sabemos donde está cada cosa y cambiar algo rompe cosas.
Una arquitectura nos sirve para organizarnos, por ejemplo piensa en un casa: tienen un lugar para la cocina, otro para dormir, otro para trabajar, etc. Cada espacio tiene un único propósito y es mas fácil encontrar las cosas.
Esta arquitectura divide nuestro proyecto en capas, específicamente en tres capas.
UI Layer
Esta capa es lo que los usuarios ven e interactúan, un botón, un input, etc. Aquí es donde aparecen los ViewModel y conceptos como Compose, Fragments, Activities.
Su responsabilidad es mostrar el estado actual de los datos y reaccionar a las peticiones de los usuarios.
ViewModel
¿Qué es un ViewModel?
Un ViewModel es una clase que contiene y maneja los datos que se mostraran en la pantalla. Expone el estado a la interfaz del usuario (UI) y encapsula la lógica de negocio. Estos datos los almacena en caché y no se tiene que recuperar los datos cada vez que se cambie de Activity.
Veamos un ejemplo de como crear un ViewModel:
class TaskViewModel(
private val getTasksUseCase: GetTasksUseCase
) : ViewModel() {
val tasks: StateFlow<List<Task>> = getTasksUseCase()
.stateIn(viewModelScope, SharingStarted.Lazily, emptyList())
}
De la siguiente manera se usa el ViewModel en un Composable:
@Composable
fun TaskScreen(viewModel: TaskViewModel) {
val tasks by viewModel.tasks.collectAsState()
LazyColumn {
items(tasks) { task -> Text(task.title) }
}
}
Domain Layer
Esta es una capa opcional pero muy recomendable aplicarla, esta contiene las reglas de negocio: qué hace tu app, no cómo lo muestra. Aquí no importa como se muestran ni de donde provienen los datos.
class GetTasksUseCase(private val repository: TaskRepository) {
operator fun invoke(): Flow<List<Task>> = repository.getAllTasks()
}
Es como un "puente" entre la UI y los datos.
Data Layer
Aquí es donde fluyen los datos y se dividen en dos partes:
Repositories
La UI o el dominio no acceden directamente a los datos, tienen que acceder por medio de repositorios, estos coordinan los orígenes de los datos que pueden ser locales, remotos, cache, etc. Los repositorios deciden de donde obtener la información.
class TaskRepository(
private val localDataSource: TaskDao,
private val remoteDataSource: TaskApi
) {
fun getAllTasks(): Flow<List<Task>> = localDataSource.getAll()
}
Data Sources
Por otro lado están los Data Sources que son las fuentes reales de los datos como una base de datos local (Room), una API, archivos, etc.
@Dao
interface TaskDao {
@Query("SELECT * FROM tasks")
fun getAll(): Flow<List<Task>>
}
Flujo de Datos

Flujo de datos de app Android con Koltin
Beneficios que obtienes con esta arquitectura
- Separa las responsabilidades
- El código se vuelve a futuro mas fácil de mantener y probar
- Mas fácil escalar sin romper cosas
- Facilita las pruebas unitarias al separa la lógica de negocio de la UI
- Reduce el acoplamiento entre componentes