Consigli per l'architettura Android (visualizzazioni)

Concetti e implementazione di Jetpack Compose

Questa pagina presenta diverse best practice e consigli per l'architettura. Adottali per migliorare la qualità, la robustezza e la scalabilità della tua app. Inoltre, semplificano la manutenzione e il test dell'app.

Livello UI

Il ruolo del livello UI è visualizzare i dati dell'applicazione sullo schermo e fungere da punto principale di interazione dell'utente. Ecco alcune best practice per il livello UI:

  • Devi creare repository anche se contengono una sola origine dati.
  • Nelle app di piccole dimensioni, puoi scegliere di inserire i tipi di livello dati in un pacchetto o modulo data.

Consiglio

Descrizione

Segui il flusso di dati unidirezionale (UDF).

Vivamente consigliato

Segui i principi del flusso di dati unidirezionale (UDF), in cui i ViewModel espongono lo stato della UI utilizzando il pattern Observer e ricevono azioni dalla UI tramite chiamate di metodi.

Utilizza AAC ViewModels se i relativi vantaggi si applicano alla tua app.

Vivamente consigliato

Utilizza AAC ViewModels per gestire la logica di business e recuperare i dati dell'applicazione per esporre lo stato dell'interfaccia utente all'interfaccia utente.

Scopri altre best practice per ViewModel qui.

Scopri i vantaggi di ViewModels qui.

Utilizza la raccolta dello stato dell'interfaccia utente in base al ciclo di vita.

Vivamente consigliato

Raccogli lo stato della UI dalla UI utilizzando il builder di coroutine consapevole del ciclo di vita appropriato, repeatOnLifecycle.

Scopri di più su repeatOnLifecycle.

Non inviare eventi dalla ViewModel alla UI.

Vivamente consigliato

Elabora immediatamente l'evento nel ViewModel e causa un aggiornamento dello stato con il risultato della gestione dell'evento. Scopri di più sugli eventi dell'interfaccia utente qui.

Utilizza un'applicazione con una sola attività.

Consigliati

Utilizza i frammenti di navigazione per spostarti tra le schermate e creare deep link alla tua app se ha più di una schermata.

Il seguente snippet mostra come raccogliere lo stato della UI in modo consapevole del ciclo di vita:

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

Le ViewModel sono responsabili di fornire lo stato della UI e l'accesso al livello di dati. Di seguito sono riportate alcune best practice per i ViewModel:

Consiglio

Descrizione

I ViewModel devono essere indipendenti dal ciclo di vita di Android.

Vivamente consigliato

I ViewModel non devono contenere riferimenti a tipi correlati al ciclo di vita. Non trasmettere Activity, Fragment, Context o Resources come dipendenza. Se qualcosa richiede un Context nel ViewModel, valuta attentamente se si trova nel livello corretto.

Utilizza coroutine e flussi.

Vivamente consigliato

ViewModel interagisce con i livelli di dati o di dominio utilizzando:

  • Flussi Kotlin per la ricezione dei dati delle applicazioni,
  • suspend funzioni per eseguire azioni utilizzando viewModelScope.

Utilizza ViewModels a livello di schermata.

Vivamente consigliato

Non utilizzare ViewModels in parti riutilizzabili dell'UI. Devi utilizzare i ViewModel in:

  • Attività/Frammenti nelle visualizzazioni
  • Destinazioni o grafici quando utilizzi Jetpack Navigation.

Non utilizzare AndroidViewModel.

Vivamente consigliato

Utilizza la classe ViewModel, non AndroidViewModel. La classe Application non deve essere utilizzata nel ViewModel. Sposta invece la dipendenza nell'interfaccia utente o nel livello dati.

Esporre uno stato dell'interfaccia utente.

Consigliati

I ViewModel devono esporre i dati all'UI tramite una singola proprietà denominata uiState. Se la UI mostra più dati non correlati, il ViewModel può esporre più proprietà di stato della UI.

  • Devi impostare uiState come StateFlow.
  • Devi creare uiState utilizzando l'operatore stateIn con le norme WhileSubscribed(5000) (esempio) se i dati provengono da un flusso di dati di altri livelli della gerarchia.
  • Per i casi più semplici senza flussi di dati provenienti dal data layer, è accettabile utilizzare un MutableStateFlow esposto come StateFlow immutabile.
  • Puoi scegliere di utilizzare ${Screen}UiState come classe di dati che può contenere dati, errori e indicatori di caricamento. Questa classe potrebbe anche essere una classe sigillata se i diversi stati sono esclusivi.

Lo snippet seguente mostra come esporre lo stato della UI da 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 di vita

Di seguito sono riportate alcune best practice per l'utilizzo del ciclo di vita di Android:

Consiglio

Descrizione

Non eseguire l'override dei metodi del ciclo di vita in Attività o Frammenti.

Vivamente consigliato

Non eseguire l'override dei metodi del ciclo di vita come onResume in Attività o Frammenti. Utilizza invece LifecycleObserver. Se l'app deve eseguire un'attività quando il ciclo di vita raggiunge un determinato Lifecycle.State, utilizza l'API repeatOnLifecycle.

Il seguente snippet descrive come eseguire le operazioni in base a un determinato stato del ciclo di vita:

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) {
                // ...
            }
        }
    }
}