Consigli per l'architettura Android

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

Le best practice riportate di seguito sono raggruppate per argomento. Ognuno ha una priorità che riflette la forza che il team lo consiglia. L'elenco delle priorità è il seguente:

  • Vivamente consigliata: dovresti implementare questa pratica, a meno che non entri in conflitto con il tuo approccio.
  • Consigliato: è probabile che questa pratica migliorerà la tua app.
  • (Facoltativo) Questa pratica può migliorare la tua app in determinate circostanze.

Architettura a più livelli

La nostra architettura a più livelli consigliata favorisce la separazione dei problemi. Gestisce l'interfaccia utente da modelli di dati, è conforme al principio dell'unica fonte attendibile e segue i principi del flusso di dati unidirezionale. Ecco alcune best practice per l'architettura a più livelli:

Consiglio Descrizione
Utilizza un livello dati chiaramente definito.
Fortemente consigliato
Il livello dati espone i dati dell'applicazione al resto dell'app e contiene la maggior parte della logica di business dell'app.
  • Dovresti creare repository anche se contengono solo una singola origine dati.
  • Nelle app di piccole dimensioni, puoi scegliere di inserire i tipi di livelli dati in un pacchetto o modulo data.
Utilizza un livello UI ben definito.
Fortemente consigliato
Il livello UI mostra i dati dell'applicazione sullo schermo e funge da punto principale di interazione dell'utente.
  • Nelle app di piccole dimensioni, puoi scegliere di inserire i tipi di livelli dati in un pacchetto o modulo ui.
Altre best practice per il livello UI.
Il livello dati deve esporre i dati dell'applicazione utilizzando un repository.
Fortemente consigliato

I componenti nel livello dell'interfaccia utente, come i componibili, le attività o i ViewModel, non devono interagire direttamente con un'origine dati. Ecco alcuni esempi di origini dati:

  • API Database, Database, SharedPreferences e Firebase.
  • Fornitori di servizi di localizzazione GPS.
  • Fornitori di dati Bluetooth.
  • Provider dello stato di connettività di rete.
Utilizza coroutine e flussi.
Fortemente consigliato
Utilizza coroutine e flussi per comunicare tra i livelli.

Altre best practice sulle coroutine qui.

Utilizza un livello dominio.
Consigliato nelle app di grandi dimensioni
Per casi d'uso, utilizza un livello dominio se devi riutilizzare la logica di business che interagisce con il livello dati su più ViewModel oppure se vuoi semplificare la complessità della logica di business di un determinato ViewModel.

Livello UI

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

Consiglio Descrizione
Consulta la sezione Flusso di dati unidirezionale (UDF).
Fortemente consigliato
Segui i principi del Flusso di dati unidirezionale (UDF), in cui i ViewModel espongono lo stato dell'interfaccia utente utilizzando il pattern osservatore e ricevono azioni dall'interfaccia utente tramite chiamate al metodo.
Utilizza AAC ViewModels se i relativi vantaggi si applicano alla tua app.
Fortemente consigliato
Utilizza i ViewModel AAC per gestire la logica di business e recuperare i dati dell'applicazione per esporre lo stato dell'interfaccia utente (Scrivi o Visualizzazioni Android).

Consulta altre best practice per ViewModel qui.

Scopri i vantaggi di ViewModels qui.

Utilizza la raccolta dello stato dell'UI sensibile al ciclo di vita.
Fortemente consigliato
Raccogli lo stato della UI dalla UI utilizzando il builder di coroutine sensibile al ciclo di vita appropriato: repeatOnLifecycle nel sistema di visualizzazione e collectAsStateWithLifecycle in Jetpack Compose.

Scopri di più su repeatOnLifecycle.

Scopri di più su collectAsStateWithLifecycle.

Non inviare eventi da ViewModel all'interfaccia utente.
Fortemente consigliato
Elabora l'evento immediatamente nel ViewModel e causa un aggiornamento dello stato con il risultato della gestione dell'evento. Scopri di più sugli eventi UI.
Utilizza un'applicazione per singola attività.
Consigliato
Utilizza Frammenti di navigazione o Scrittura navigazione per spostarti tra le schermate e il link diretto alla tua app se l'app ha più di una schermata.
Utilizza Jetpack Compose.
Consigliato
Utilizza Jetpack Compose per creare nuove app per smartphone, tablet, pieghevoli e Wear OS.

Lo snippet seguente illustra come raccogliere lo stato della UI in un modo rispettoso del ciclo di vita:

Visualizzazioni

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
                }
            }
        }
    }
}

Scrivi

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

Visualizza modello

I ViewModel sono responsabili della fornitura dello stato dell'interfaccia utente e dell'accesso al livello dati. Di seguito sono riportate alcune best practice per i modelli ViewModel:

Consiglio Descrizione
I modelli ViewModel devono essere indipendenti dal ciclo di vita di Android.
Fortemente consigliato
I modelli ViewModel non devono contenere un riferimento a nessun tipo di ciclo di vita. Non trasmettere Activity, Fragment, Context o Resources come dipendenza. Se qualcosa richiede un Context in ViewModel, dovresti valutare fortemente se questo si trova nel livello giusto.
Utilizza coroutine e flussi.
Fortemente consigliato

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

  • il flusso Kotlin per la ricezione dei dati delle applicazioni,
  • suspend le funzioni per eseguire azioni utilizzando viewModelScope.
Utilizzare ViewModels a livello di schermo.
Fortemente consigliato

Non utilizzare ViewModels in parti riutilizzabili dell'interfaccia utente. Devi utilizzare ViewModels in:

  • Componibili a livello di schermo,
  • Attività/frammenti in Visualizzazioni,
  • Destinazioni o grafici quando utilizzi Jetpack Navigation.
Utilizza le classi con stato normale nei componenti riutilizzabili dell'interfaccia utente.
Fortemente consigliato
Utilizza classi standard per gestire la complessità nei componenti riutilizzabili dell'interfaccia utente. In questo modo, lo stato può essere issato e controllato esternamente.
Non utilizzare AndroidViewModel.
Consigliato
Utilizza il corso ViewModel, non AndroidViewModel. La classe Application non deve essere utilizzata nel ViewModel. Sposta invece la dipendenza nell'interfaccia utente o nel livello dati.
Esponi uno stato dell'interfaccia utente.
Consigliato
I ViewModel devono esporre i dati all'interfaccia utente tramite una singola proprietà denominata uiState. Se l'interfaccia utente mostra più dati non correlati, la VM può esporre più proprietà dello stato dell'interfaccia utente.
  • Dovresti trasformare uiState in StateFlow.
  • Dovresti creare l'elemento uiState utilizzando l'operatore stateIn con il criterio WhileSubscribed(5000) (esempio) se i dati provengono da un flusso di dati provenienti da altri livelli della gerarchia.
  • Per i casi più semplici senza flussi di dati provenienti dal livello dati, è accettabile utilizzare un MutableStateFlow esposto come StateFlow immutabile (esempio).
  • Puoi scegliere di avere ${Screen}UiState come classe di dati che può contenere dati, errori e indicatori di caricamento. Questa classe potrebbe anche essere una classe Sealed se i diversi stati sono esclusivi.

Lo snippet seguente illustra come esporre lo stato dell'interfaccia utente 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 lavorare con il ciclo di vita Android:

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

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

Visualizzazioni

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

Scrivi

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

Gestire le dipendenze

Esistono diverse best practice da seguire per gestire le dipendenze tra i componenti:

Consiglio Descrizione
Utilizza l'inserimento delle dipendenze.
Fortemente consigliato
Utilizza le best practice per l'inserimento delle dipendenze, principalmente l'inserimento dei costruttori, se possibile.
Limita l'ambito a un componente quando necessario.
Fortemente consigliato
Assegna l'ambito a un container dipendenze quando il tipo contiene dati modificabili che devono essere condivisi o è costoso da inizializzare ed è ampiamente utilizzato nell'app.
Usa Hilt.
Consigliato
Utilizza Hilt o l'inserimento manuale delle dipendenze nelle app semplici. Usa Hilt se il progetto è abbastanza complesso. Ad esempio, se hai:
  • Più schermate con ViewModels: integrazione
  • Utilizzo di WorkManager: integrazione
  • Utilizzo avanzato della navigazione, come l'integrazione di ViewModels con il grafico di navigazione.

Test

Di seguito sono riportate alcune best practice per i test:

Consiglio Descrizione
Scopri cosa testare.
Fortemente consigliato

A meno che il progetto non sia all'incirca semplice come un'app Hello World, dovresti testarlo almeno con:

  • ViewModel test delle unità, inclusi i flussi.
  • Entità del livello dati del test delle unità. ovvero repository e origini dati.
  • Test di navigazione dell'interfaccia utente utili come test di regressione in CI.
Preferisci i falsi a quelli delle simulazioni.
Fortemente consigliato
Per saperne di più, consulta la documentazione su come utilizzare le copie di test doppie nella documentazione di Android.
Test StateFlows.
Fortemente consigliato
Durante il test di StateFlow:

Per ulteriori informazioni, consulta la guida sui test dei DAC per Android.

Modelli

Quando sviluppi modelli nelle tue app, devi osservare queste best practice:

Consiglio Descrizione
Crea un modello per livello nelle app complesse.
Consigliato

Nelle app complesse, crea nuovi modelli in diversi livelli o componenti quando appropriato. Considera i seguenti esempi:

  • Un'origine dati remota può mappare il modello che riceve attraverso la rete a una classe più semplice con solo i dati necessari per l'app
  • I repository possono mappare modelli DAO a classi di dati più semplici con solo le informazioni necessarie al livello UI.
  • ViewModel può includere modelli del livello dati nelle classi UiState.

Convenzioni di denominazione

Quando assegni un nome al codebase, tieni presente le seguenti best practice:

Consiglio Descrizione
Metodi di denominazione.
Facoltativo
I metodi devono essere una frase verbale. Ad esempio, makePayment().
Denominazione delle proprietà.
Facoltativo
Le proprietà devono essere una frase sostantivo. Ad esempio, inProgressTopicSelection.
Denominare flussi di dati.
Facoltativo
Quando una classe espone uno stream Flow, LiveData o qualsiasi altro flusso, la convenzione di denominazione è get{model}Stream(). Ad esempio, getAuthorStream(): Flow<Author> Se la funzione restituisce un elenco di modelli, il nome del modello deve essere al plurale: getAuthorsStream(): Flow<List<Author>>
Implementazioni delle interfacce di denominazione.
Facoltativo
I nomi delle implementazioni delle interfacce devono essere significativi. Utilizza Default come prefisso se non è possibile trovare un nome migliore. Ad esempio, per un'interfaccia NewsRepository, potresti avere un elemento OfflineFirstNewsRepository o InMemoryNewsRepository. Se non riesci a trovare un nome appropriato, utilizza DefaultNewsRepository. Le implementazioni false devono essere precedute dal prefisso Fake, come in FakeAuthorsRepository.