Recomendaciones para la arquitectura de Android (vistas)

Conceptos y la implementación de Jetpack Compose

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.

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:

  • Debes crear repositorios incluso si solo 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.

Recomendación

Descripción

Sigue el Flujo unidireccional de datos.

Muy recomendado

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.

Muy recomendado

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.

Consulta más prácticas recomendadas para ViewModel aquí.

Consulta los beneficios de ViewModels aquí.

Usa la recopilación de estado de la IU optimizada para ciclos de vida.

Muy recomendado

Recopila el estado de la IU con el compilador de corrutinas optimizado para ciclos de vida adecuado, repeatOnLifecycle.

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

No envíes eventos del ViewModel a la IU.

Muy recomendado

Procesa el evento inmediatamente en ViewModel y genera una actualización de estado con el resultado del control del evento. Obtén más información sobre los eventos de IU aquí.

Usa una aplicación de una sola actividad.

Recomendado

Usa Navigation Fragments para navegar entre pantallas y establecer vínculos directos a tu app si esta tiene más de una.

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

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

ViewModel

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

Recomendación

Descripción

Los ViewModels deben ser independientes del ciclo de vida de Android.

Muy recomendado

Los ViewModels no deben contener referencias a ningún tipo relacionado con el ciclo de vida. No pases Activity, Fragment, Context ni Resources como dependencia. Si algo necesita un Context en el ViewModel, debes evaluar con atención si está en la capa correcta.

Usa corrutinas y flujos.

Muy recomendado

ViewModel interactúa con los datos o las capas de dominio mediante lo siguiente:

  • Flujos de Kotlin para recibir datos de aplicaciones
  • Funciones suspend para realizar acciones con viewModelScope

Usa ViewModels a nivel de la pantalla.

Muy recomendado

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

  • Actividades o fragmentos en vistas
  • Destinos o gráficos cuando se usa Jetpack Navigation

No uses AndroidViewModel.

Muy recomendado

Usa la clase ViewModel, no AndroidViewModel. No se debe usar 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.

Recomendado

Los ViewModels deben exponer datos a la IU a través de una única propiedad llamada uiState. Si la IU muestra varios datos no relacionados, el ViewModel puede exponer varias propiedades del estado de la IU.

  • Debes hacer que uiState sea StateFlow.
  • Debes crear el uiState con el operador stateIn y la política WhileSubscribed(5000) (ejemplo) si los datos provienen como un flujo de datos de otras capas de la jerarquía.
  • 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

Las siguientes son algunas recomendaciones para trabajar con el ciclo de vida de Android:

Recomendación

Descripción

No anules los métodos de ciclo de vida de objetos Activity ni Fragment.

Muy recomendado

No anules métodos del ciclo de vida, como onResume, en actividades o fragmentos. En su lugar, usa LifecycleObserver. Si la app necesita realizar un trabajo cuando el ciclo de vida alcanza un cierto Lifecycle.State, usa la API de repeatOnLifecycle.

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

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}