Consigli per l'architettura Android

Questa pagina presenta una serie di best practice e consigli per l'architettura. Adottale per migliorare la qualità, la robustezza e la scalabilità della tua app. Inoltre, semplifica la manutenzione e il test della tua app.

Le best practice riportate di seguito sono raggruppate per argomento. Ognuna ha una priorità che riflette in che misura il team lo consiglia. L'elenco di priorità è il seguente:

  • Vivamente consigliato:è consigliabile implementare questa pratica a meno che non entri in conflitto. in base al tuo approccio.
  • Consigliato: questa prassi potrebbe migliorare la tua app.
  • (Facoltativo) In determinate circostanze, questa prassi può migliorare la tua app.
di Gemini Advanced.

Architettura a più livelli

La nostra architettura a più livelli consigliata favorisce la separazione dei problemi. it ottimizza l'UI da modelli di dati, rispetta il principio della singola fonte attendibile, e segue i principi del flusso di dati unidirezionale. Ecco alcune delle migliori 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 un'unica 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 chiaramente 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.
di Gemini Advanced. Ulteriori best practice per i livelli di UI qui.
Il livello dati deve esporre i dati dell'applicazione utilizzando un repository.
Fortemente consigliato

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

  • Database, Datastore, SharedPreferences, API 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.

Ulteriori best practice sulle coroutine qui.

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

Livello UI

Il livello UI ha il compito di visualizzare i dati dell'applicazione sullo schermo. e fungono da punto principale di interazione dell'utente. Ecco alcune best practice per il livello UI:

Consiglio Descrizione
Segui il flusso di dati unidirezionali (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 loro vantaggi si applicano alla tua app.
Fortemente consigliato
Utilizza ViewModels AAC per gestire la logica di business e recuperare i dati dell'applicazione per esporre lo stato dell'interfaccia utente (Compose o Android View).

Scopri altre best practice per ViewModel qui.

Scopri i vantaggi di ViewModels qui.

Utilizza la raccolta degli stati dell'interfaccia utente sensibile al ciclo di vita.
Fortemente consigliato
Raccogli lo stato della UI dalla UI utilizzando il generatore di coroutine appropriato per il ciclo di vita: repeatOnLifecycle nel sistema di vista e collectAsStateWithLifecycle in Jetpack Compose.

Scopri di più su repeatOnLifecycle.

Scopri di più su collectAsStateWithLifecycle.

Non inviare eventi dal ViewModel alla UI.
Fortemente consigliato
Elaborare immediatamente l'evento nel ViewModel e causare un aggiornamento dello stato con il risultato della gestione dell'evento. Scopri di più sugli eventi UI qui.
Utilizza un'applicazione a singola attività.
Consigliato
Utilizza Frammenti di navigazione o Scrittura di 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
Usa Jetpack Compose per creare nuove app per smartphone, tablet, pieghevoli e Wear OS.

Il seguente snippet illustra come raccogliere lo stato dell'interfaccia utente in un ambiente sensibile al ciclo di vita. modo:

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

Compose

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

ViewModel

I ViewModel devono fornire lo stato dell'interfaccia utente e l'accesso ai livello dati. Ecco alcune best practice per i ViewModel:

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

ViewModel interagisce con i livelli di dati o dominio utilizzando:

  • i flussi Kotlin per ricevere i dati delle applicazioni,
  • suspend per eseguire azioni utilizzando viewModelScope.
Utilizza ViewModels a livello di schermo.
Fortemente consigliato

Non utilizzare ViewModels in elementi riutilizzabili dell'interfaccia utente. Dovresti utilizzare ViewModels in:

  • Componenti componibili a livello di schermo
  • Attività/frammenti nelle viste,
  • Destinazioni o grafici quando utilizzi Jetpack Navigation.
Utilizza classi standard di stato/titolare nei componenti dell'interfaccia utente riutilizzabili.
Fortemente consigliato
Utilizza classi normali di titolari di stato per gestire la complessità nei componenti dell'interfaccia utente riutilizzabili. In questo modo, lo stato può essere sollevato e controllato esternamente.
Non utilizzare AndroidViewModel.
Consigliato
Utilizza il corso ViewModel, non AndroidViewModel. La classe Application non deve essere utilizzata nel ViewModel. Puoi invece spostare la dipendenza nell'interfaccia utente o nel livello dati.
Espone uno stato dell'interfaccia utente.
Consigliato
I ViewModels devono esporre i dati nell'interfaccia utente tramite una singola proprietà denominata uiState. Se la UI mostra più dati non correlati, la VM può esporre più proprietà dello stato dell'interfaccia utente.
  • Dovresti rendere uiState un StateFlow.
  • Devi creare uiState utilizzando l'operatore stateIn con il criterio WhileSubscribed(5000) (esempio) se i dati provengono come flusso di dati da altri livelli della gerarchia.
  • Per casi più semplici in cui non sono presenti flussi di dati provenienti dal livello dati, è accettabile utilizzare un elemento MutableStateFlow esposto come StateFlow immutabile (esempio).
  • 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 Sealed se i diversi stati sono esclusivi.

Il seguente snippet 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 software Android ciclo di vita:

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. Usa invece LifecycleObserver. Se l'app deve eseguire operazioni quando il ciclo di vita raggiunge un determinato Lifecycle.State, utilizza l'API repeatOnLifecycle.

Il seguente snippet illustra come eseguire operazioni in base a un Stato 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) {
                // ...
            }
        }
    }
}

Compose

@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 osservare per la gestione delle dipendenze tra i componenti:

Consiglio Descrizione
Utilizza l'inserimento di dipendenze.
Fortemente consigliato
Utilizza le best practice per l'inserimento delle dipendenze, principalmente iniezione da parte del costruttore quando possibile.
Limita l'ambito a un componente quando necessario.
Fortemente consigliato
Assegna l'ambito a un container delle dipendenze quando il tipo contiene dati modificabili che devono essere condivisi o quando il tipo è costoso da inizializzare ed è ampiamente utilizzato nell'app.
Utilizza Hilt.
Consigliato
Utilizza Hilt o l'inserimento manuale delle dipendenze nelle app semplici. Utilizza Hilt se il tuo progetto è abbastanza complesso. Ad esempio, se hai:
  • Più schermate con ViewModels: integrazione
  • Utilizzo di WorkManager: integrazione
  • Utilizzo avanzato della navigazione, ad esempio dell'integrazione dei ViewModels con l'ambito del 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 più o meno semplice come un'app Hello World, dovresti testarlo almeno con:

  • ViewModel di test delle unità, inclusi i flussi.
  • Entità del livello dati di test delle unità. ovvero repository e origini dati.
  • Test della navigazione dell'interfaccia utente utili come test di regressione in CI.
Preferisci i falsi alle simulazioni.
Fortemente consigliato
Scopri di più nella documentazione sull'utilizzo del test Doppio in Android.
Testa StateFlows.
Fortemente consigliato
Durante il test di StateFlow:

Per ulteriori informazioni, consulta la guida Che cosa testare nei DAC Android.

Modelli

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

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

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

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

Convenzioni di denominazione

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

Consiglio Descrizione
Metodi di denominazione.
Facoltativo
I metodi devono essere costituiti da frasi verbali. Ad esempio, makePayment().
Proprietà di denominazione.
Facoltativo
Le proprietà devono essere una frase sostantivo. Ad esempio, inProgressTopicSelection.
Denominazione dei flussi di dati.
Facoltativo
Quando una classe espone uno stream Flow, LiveData o qualsiasi altro stream, 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>>
Denominazioni delle interfacce.
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 OfflineFirstNewsRepository oppure InMemoryNewsRepository. Se non riesci a trovare un nome appropriato, utilizza DefaultNewsRepository. Le implementazioni false devono essere precedute dal prefisso Fake, come in FakeAuthorsRepository.