Recomendaciones para la arquitectura de Android

En esta página, se presentan varias recomendaciones y prácticas recomendadas para la arquitectura. Adopta estas funciones para mejorar la calidad, la solidez y la escalabilidad de tu app. También facilitan el mantenimiento y la prueba de tu app.

Las siguientes prácticas recomendadas se agrupan por tema. Cada una tiene una prioridad que refleja cuán recomendable es según el equipo. La lista de prioridades es la siguiente:

  • Muy recomendada: Implementa esta práctica, a menos que entre en conflicto con tu estrategia.
  • Recomendada: Es probable que esta práctica mejore tu app.
  • Opcional: Esta práctica puede mejorar tu app en determinadas circunstancias.

Arquitectura en capas

En la arquitectura en capas que recomendamos, se prefiere la separación de problemas. Controla la IU a partir de modelos de datos, cumple con el principio de fuente de confianza única y sigue los principios del flujo unidireccional de datos. Estas son algunas prácticas recomendadas para la arquitectura en capas:

Recomendación Descripción
Usa una capa de datos claramente definida. La capa de datos expone los datos de la aplicación al resto de la app y contiene la gran mayoría de su lógica empresarial.
  • Crea repositorios incluso si contienen una sola fuente de datos.
  • En las apps pequeñas, puedes colocar tipos de capas de datos en un módulo o paquete data.
Usa una capa de la IU claramente definida. La capa de la IU muestra los datos de la aplicación en la pantalla y sirve como punto principal de la interacción con el usuario. Jetpack Compose es el kit de herramientas moderno recomendado para compilar la IU de tu app.
  • En las apps pequeñas, puedes colocar tipos de capas de datos en un módulo o paquete ui.
Si deseas obtener más información sobre las prácticas recomendadas de la capa de la IU, consulta Capa de la IU.
Expón los datos de la aplicación desde la capa de datos con un repositorio.

Asegúrate de que los componentes de la capa de la IU, como los elementos componibles o los ViewModels, no interactúen directamente con una fuente de datos. Estos son algunos ejemplos de fuentes de datos:

  • Bases de datos, DataStore, SharedPreferences, APIs de Firebase
  • Proveedores de ubicación GPS
  • Proveedores de datos Bluetooth
  • Proveedores de estado de conectividad de red
Usa corrutinas y flujos. Usa corrutinas y flujos para establecer la comunicación entre capas.

Para obtener más información sobre las prácticas recomendadas para corrutinas, consulta Prácticas recomendadas para corrutinas en Android.

Usa una capa de dominio. Usa una capa de dominio con casos de uso si necesitas reutilizar la lógica empresarial que interactúa con la capa de datos en varios ViewModels o si deseas simplificar la complejidad de la lógica empresarial de un ViewModel en particular.

Capa de la IU

La función de la capa de la IU es mostrar los datos de la aplicación en la pantalla y servir como punto principal de interacción con el usuario. Estas son algunas prácticas recomendadas para la capa de la IU:

Recomendación Descripción
Sigue el Flujo unidireccional de datos. Sigue los principios del flujo unidireccional de datos, en el que los ViewModels exponen el estado de la IU usando el patrón del observador y reciben acciones de la IU a través de llamadas de método.
Usa AAC de ViewModels si sus beneficios se aplican a tu app. Usa AAC ViewModels para controlar la lógica empresarial y recuperar datos de la aplicación a fin de exponer el estado de la IU a la IU.

Para obtener más información sobre las prácticas recomendadas de ViewModel, consulta Recomendaciones de arquitectura.

Para obtener más información sobre los beneficios de los ViewModels, consulta El ViewModel como contenedor de estado de lógica empresarial.

Usa la recopilación de estado de la IU optimizada para ciclos de vida. Recopila el estado de la IU con el compilador de corrutinas optimizado para ciclos de vida adecuado, collectAsStateWithLifecycle.

Obtén más información sobre collectAsStateWithLifecycle.

No envíes eventos del ViewModel a la IU. Procesa el evento inmediatamente en ViewModel y genera una actualización de estado con el resultado del control del evento. Para obtener más información sobre los eventos de la IU, consulta Cómo controlar eventos de ViewModel.
Usa una aplicación de una sola actividad. Usa Navigation 3 para navegar entre pantallas y establecer vínculos directos a tu app si esta tiene más de una pantalla.
Usa Jetpack Compose. Usa Jetpack Compose para compilar apps nuevas para teléfonos, tablets, dispositivos plegables y Wear OS.

En el siguiente fragmento, se describe cómo recopilar el estado de la IU de manera optimizada para los ciclos de vida:

  @Composable
  fun MyScreen(
      viewModel: MyViewModel = viewModel()
  ) {
      val uiState by viewModel.uiState.collectAsStateWithLifecycle()
  }

ViewModel

Los ViewModels son responsables de proporcionar el estado de la IU y acceder a la capa de datos. Estas son algunas prácticas recomendadas para ViewModels:

Recomendación Descripción
Mantén los ViewModels independientes del ciclo de vida de Android. En los ViewModels, no conserves una referencia a ningún tipo relacionado con el ciclo de vida. No pases Activity, Context ni Resources como dependencia. Si algo necesita un Context en el ViewModel, evalúa con atención si está en la capa correcta.
Usa corrutinas y flujos.

ViewModel interactúa con las capas de datos o de dominio de la siguiente manera:

  • Flujos de Kotlin para recibir datos de aplicaciones
  • Funciones suspend para realizar acciones con viewModelScope
Usa ViewModels a nivel de la pantalla.

No uses ViewModels en piezas de IU reutilizables. Debes usar ViewModels en lo siguiente:

  • Elementos componibles a nivel de pantalla
  • Actividades o fragmentos en vistas
  • Destinos o gráficos cuando se usa Jetpack Navigation
Usa clases contenedoras de estados sin formato en componentes de IU reutilizables. Usa clases contenedoras de estados sin formato para controlar la complejidad en componentes de IU reutilizables. Cuando haces esto, el estado se puede elevar y controlar de forma externa.
No uses AndroidViewModel. Usa la clase ViewModel, no AndroidViewModel. No uses la clase Application en ViewModel. En su lugar, mueve la dependencia a la IU o a la capa de datos.
Expón un estado de IU. Haz que tus ViewModels expongan datos a la IU a través de una sola propiedad llamada uiState. Si la IU muestra varios datos no relacionados, la VM puede exponer varias propiedades del estado de la IU.
  • Haz que uiState sea un StateFlow.
  • Crea el uiState con el operador stateIn y la política WhileSubscribed(5000) si los datos provienen como un flujo de datos de otras capas de la jerarquía. (Consulta este ejemplo de código).
  • Para los casos más simples sin transmisiones de datos provenientes de la capa de datos, resulta aceptable usar un MutableStateFlow expuesto como StateFlow inmutable.
  • Puedes optar por que ${Screen}UiState sea una clase de datos que pueda contener datos, indicadores de carga y errores. Esta clase también podría ser sellada si los diferentes estados son exclusivos.

En el siguiente fragmento, se describe cómo exponer el estado de la IU desde un ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Ciclo de vida

Sigue las prácticas recomendadas para trabajar con el ciclo de vida de la actividad:

Recomendación Descripción
Usa efectos optimizados para ciclos de vida en elementos componibles en lugar de anular las devoluciones de llamada del ciclo de vida de Activity.

No anules los métodos de ciclo de vida de Activity, como onResume, para ejecutar tareas relacionadas con la IU. En su lugar, usa LifecycleEffects de Compose o ámbitos de corrutinas optimizados para ciclos de vida:

En el siguiente fragmento, se describe cómo realizar operaciones en función de un determinado estado de ciclo de vida:

  @Composable
  fun LocationChangedEffect(
    locationManager: LocationManager,
    onLocationChanged: (Location) -> Unit
  ) {
    val currentOnLocationChanged by rememberUpdatedState(onLocationChanged)

    LifecycleStartEffect(locationManager) {
        val listener = LocationListener { newLocation ->
            currentOnLocationChanged(newLocation)
        }

        try {
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                1000L,
                1f,
                listener,
            )
        } catch (e: SecurityException) {
            // TODO: Handle missing permissions
        }

        onStopOrDispose {
            locationManager.removeUpdates(listener)
        }
    }
  }

Control de dependencias

Sigue las prácticas recomendadas cuando administres dependencias entre componentes:

Recomendación Descripción
Usa la inserción de dependencias. Usa las prácticas recomendadas de inserción de dependencias, en especial, la inserción del constructor, cuando sea posible.
Define el alcance para un componente cuando sea necesario. Define el alcance para un contenedor de dependencias cuando el tipo contenga datos mutables que se deban compartir o resulte costoso inicializar el tipo y se use mucho en la app.
Usa Hilt. Usa Hilt o la inserción manual de dependencias en apps simples. Usa Hilt si tu proyecto es lo suficientemente complejo, por ejemplo, si incluye cualquiera de los siguientes elementos:
  • Varias pantallas con ViewModels
  • Usa WorkManager
  • Tiene ViewModels con alcance para la pila de actividades de navegación

Prueba

Estas son algunas recomendaciones para las pruebas:

Recomendación Descripción
Comprende qué probar.

A menos que el proyecto sea tan simple como una app de "Hello World", pruébalo. Como mínimo, incluye lo siguiente:

  • Pruebas de unidades para ViewModels, incluidos los flujos
  • Pruebas de unidades para entidades de la capa de datos, es decir, repositorios y fuentes de datos
  • Pruebas de navegación de la IU que son útiles como pruebas de regresión en CI
Opta por simulaciones en lugar de muestras. Para obtener más información sobre el uso de objetos simulados, consulta Cómo usar pruebas dobles en Android.
Prueba StateFlows. Cuando pruebes StateFlow, haz lo siguiente:

Para obtener más información, consulta Qué probar en Android y Cómo probar tu diseño de Compose.

Modelos

Ten en cuenta estas prácticas recomendadas cuando desarrolles modelos en tus apps:

Recomendación Descripción
Crea un modelo por capa en apps complejas.

En apps complejas, crea modelos nuevos en diferentes capas o componentes cuando hacerlo tenga sentido. Considera los siguientes ejemplos:

  • Una fuente de datos remota puede asignar el modelo que recibe a través de la red a una clase más simple con solo los datos que necesita la app.
  • Los repositorios pueden asignar modelos DAO a clases de datos más simples con solo la información que necesita la capa de la IU.
  • ViewModel puede incluir modelos de capas de datos en las clases UiState.

Convenciones de nombres

A la hora de nombrar tu base de código, debes tener en cuenta las siguientes prácticas recomendadas:

Recomendación Descripción
Nombra métodos.
Opcional
Usa frases verbales para nombrar los métodos, por ejemplo, makePayment().
Nombra propiedades.
Opcional
Usa frases nominales para nombrar propiedades, por ejemplo, inProgressTopicSelection.
Nombra flujos de datos.
Opcional
Cuando una clase expone una transmisión de flujo o cualquier otra, la convención de nombres es get{model}Stream. Por ejemplo, getAuthorStream(): Flow<Author>. Si la función devuelve una lista de modelos, usa el nombre del modelo en plural: getAuthorsStream(): Flow<List<Author>>.
Nombra las implementaciones de interfaces.
Opcional
Usa nombres significativos para las implementaciones de interfaces. Usa Default como prefijo si no se encuentra un nombre mejor. Por ejemplo, para una interfaz NewsRepository, puedes tener un OfflineFirstNewsRepository o InMemoryNewsRepository. Si no encuentras un nombre adecuado, usa DefaultNewsRepository. Agrega el prefijo Fake a las implementaciones simuladas, como en FakeAuthorsRepository.

Recursos adicionales

Para obtener más información sobre la arquitectura de Android, consulta los siguientes recursos adicionales:

Documentación

Mira contenido