Recommandations pour l'architecture Android

Cette page présente plusieurs bonnes pratiques et recommandations concernant l'architecture. Adoptez-les pour améliorer la qualité, la robustesse et l'évolutivité de votre application. Elles facilitent également la gestion et le test de votre application.

Les bonnes pratiques ci-dessous sont regroupées par thème. Chacune d'elles a une priorité indiquant à quel point la recommandation est forte. Voici la liste des priorités :

  • Fortement recommandé : appliquez cette pratique, sauf si elle entre fondamentalement en conflit avec votre approche.
  • Recommandé : il est probable que cette pratique améliore votre application.
  • Facultatif : cette pratique peut améliorer votre application dans certains cas.

Architecture multicouche

Notre architecture multicouche recommandée favorise la séparation des tâches. Elle pilote l'interface utilisateur à partir de modèles de données, est conforme au principe de référence unique et suit les principes du flux de données unidirectionnel. Voici quelques bonnes pratiques pour l'architecture multicouche :

Recommandation Description
Utilisez une couche de données clairement définie. La couche de données expose les données de l'application au reste de l'application et contient la grande majorité de la logique métier de votre application.
  • Créez des dépôts même s'ils ne contiennent qu'une seule source de données.
  • Dans les petites applications, vous pouvez choisir de placer les types de couches de données dans un module ou un package data.
Utilisez une couche d'interface utilisateur bien définie. La couche d'interface utilisateur affiche les données de l'application à l'écran et sert de point principal d'interaction utilisateur. Jetpack Compose est le kit d'outils moderne recommandé pour créer l'UI de votre application.
  • Dans les petites applications, vous pouvez choisir de placer les types de couches de données dans un module ou un package ui.
Pour en savoir plus sur les bonnes pratiques pour la couche d'interface utilisateur, consultez Couche d'interface utilisateur.
Exposez les données d'application de la couche de données à l'aide d'un dépôt.

Assurez-vous que les composants de la couche d'interface utilisateur tels que les composables ou les ViewModels n'interagissent pas directement avec une source de données. Voici quelques exemples de sources de données :

  • API Database, DataStore, SharedPreferences et Firebase.
  • Fournisseurs de géolocalisation GPS.
  • Fournisseurs de données Bluetooth.
  • Fournisseurs d'état de connectivité réseau.
Utilisez des coroutines et des flux. Utilisez des coroutines et des flux pour communiquer entre les couches.

Pour en savoir plus sur les bonnes pratiques concernant les coroutines, consultez Bonnes pratiques concernant les coroutines sur Android.

Utilisez une couche de domaine. Utilisez une couche de domaine avec des cas d'utilisation si vous devez réutiliser la logique métier qui interagit avec la couche de données sur plusieurs ViewModels, ou si vous souhaitez simplifier la logique métier d'un ViewModel particulier.

Couche d'interface utilisateur

Le rôle de la couche d'interface utilisateur est d'afficher les données de l'application à l'écran et de servir de point principal d'interaction utilisateur. Voici quelques bonnes pratiques pour la couche d'interface utilisateur :

Recommandation Description
Suivez les principes du flux de données unidirectionnel (UDF). Suivez les principes du flux de données unidirectionnel (UDF), selon lesquels les ViewModels exposent l'état de l'UI à l'aide du modèle d'observateur et reçoivent les actions de l'UI via des appels de méthode.
Utilisez des ViewModels AAC si cela peut être utile pour votre application. Utilisez des ViewModels AAC pour gérer la logique métier et récupérer les données de l'application pour exposer l'état de l'UI à l'UI.

Pour en savoir plus sur les bonnes pratiques concernant les ViewModel, consultez Recommandations d'architecture.

Pour en savoir plus sur les avantages des ViewModels, consultez ViewModel en tant que conteneur d'état de logique métier.

Collectez l'état de l'UI en tenant compte du cycle de vie. Collectez l'état de l'UI depuis celle-ci à l'aide du compilateur de coroutines qui tient compte du cycle de vie, collectAsStateWithLifecycle.

En savoir plus sur collectAsStateWithLifecycle

N'envoyez pas d'événements du ViewModel à l'UI. Traitez l'événement immédiatement dans ViewModel et mettez à jour l'état avec le résultat de la gestion de l'événement. Pour en savoir plus sur les événements d'UI, consultez Gérer les événements ViewModel.
Utilisez une application à activité unique. Utilisez Navigation 3 pour naviguer entre les écrans, et utilisez des liens profonds vers votre application si elle comporte plusieurs écrans.
Utilisez Jetpack Compose. Utilisez Jetpack Compose pour créer des applications pour téléphones, tablettes, appareils pliables et Wear OS.

L'extrait de code suivant montre comment collecter l'état de l'interface utilisateur en tenant compte du cycle de vie :

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

ViewModel

Les ViewModels fournissent l'état de l'UI et accèdent à la couche de données. Voici quelques bonnes pratiques pour les ViewModels :

Recommandation Description
Maintenez les ViewModels indépendants du cycle de vie d'Android. Dans les ViewModels, ne faites référence à aucun type lié au cycle de vie. Ne transmettez pas d'Activity, d'Context ni d'Resources en tant que dépendance. Si un élément a besoin d'un Context dans le ViewModel, évaluez soigneusement s'il se trouve dans la bonne couche.
Utilisez des coroutines et des flux.

Le ViewModel interagit avec les couches de données ou de domaine à l'aide des éléments suivants :

  • Flux Kotlin pour recevoir des données d'application
  • Fonctions suspend pour effectuer des actions avec viewModelScope
Utilisez les ViewModels au niveau de l'écran.

N'utilisez pas de ViewModels dans les éléments réutilisables de l'UI. Vous devez utiliser les ViewModels dans les éléments suivants :

  • Composables au niveau de l'écran
  • Activités/Fragments dans Views
  • Destinations ou graphiques lorsque vous utilisez Jetpack Navigation
Utilisez des classes de conteneurs d'état simples dans les composants d'UI réutilisables. Utilisez des classes de conteneurs d'état simples pour gérer la complexité des composants d'UI réutilisables. Cela permet de hisser et de contrôler l'état en externe.
N'utilisez pas AndroidViewModel. Utilisez la classe ViewModel, pas AndroidViewModel. N'utilisez pas la classe Application dans le ViewModel. Déplacez plutôt la dépendance vers l'UI ou la couche de données.
Exposez un état de l'interface utilisateur. Faites en sorte que vos ViewModels exposent les données à l'UI via une seule propriété appelée uiState. Si l'interface utilisateur affiche plusieurs données sans liens entre elles, le ViewModel peut exposer plusieurs propriétés d'état de l'interface utilisateur.
  • Définissez uiState comme StateFlow.
  • Créez l'uiState à l'aide de l'opérateur stateIn avec la règle WhileSubscribed(5000) si les données arrivent sous la forme d'un flux de données provenant d'autres couches de la hiérarchie. (Consultez cet exemple de code.)
  • Dans les cas plus simples où aucun flux de données ne provient de la couche de données, il est possible d'utiliser un MutableStateFlow exposé en tant que StateFlow immuable.
  • Vous pouvez choisir d'utiliser ${Screen}UiState comme classe de données pouvant contenir des données, des erreurs et des signaux de chargement. Cette classe peut également être scellée si les différents états sont exclusifs.

L'extrait de code suivant montre comment exposer l'état de l'interface utilisateur à partir d'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
            )

    // ...
}

Cycle de vie

Suivez les bonnes pratiques pour utiliser le cycle de vie des activités :

Recommandation Description
Utilisez des effets tenant compte du cycle de vie dans les composables au lieu de remplacer les rappels de cycle de vie Activity.

N'ignorez pas les méthodes de cycle de vie Activity, telles que onResume, pour exécuter des tâches liées à l'UI. Utilisez plutôt LifecycleEffects de Compose ou des champs d'application des coroutines tenant compte du cycle de vie :

  • Utilisez LifecycleStartEffect pour effectuer un travail synchrone lorsque votre activité démarre et s'arrête.
  • Utilisez LifecycleResumeEffect pour effectuer un travail synchrone lorsque votre activité reprend et s'interrompt.
  • Utilisez repeatOnLifecycle pour effectuer des tâches asynchrones en réponse à des événements de cycle de vie.
  • Collectez des données asynchrones à partir de Flows à l'aide de collectAsStateWithLifecycle.

L'extrait de code suivant montre comment effectuer des opérations en fonction d'un certain état du cycle de vie :

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

Gérer les dépendances

Suivez les bonnes pratiques lorsque vous gérez des dépendances entre des composants :

Recommandation Description
Utilisez l'injection de dépendances. Dans la mesure du possible, suivez les bonnes pratiques concernant l'injection de dépendances, en particulier l'injection de constructeur.
Si nécessaire, limitez la portée à un composant. Limitez la portée à un conteneur de dépendances lorsque le type contient des données modifiables devant être partagées, ou que son initialisation est coûteuse et qu'il est largement utilisé dans l'application.
Utilisez Hilt. Utilisez Hilt ou une injection de dépendances manuelle dans les applications simples. Utilisez Hilt si votre projet est suffisamment complexe (par exemple, s'il inclut l'un des éléments suivants) :
  • Utilisation de plusieurs écrans avec ViewModels
  • Utilise WorkManager
  • Dispose de ViewModels limités à la pile "Retour" de navigation

Tests

Voici quelques bonnes pratiques pour les tests :

Recommandation Description
Identifiez les éléments à tester.

À moins que le projet ne soit aussi simple qu'une application "Hello World", testez-le. Au minimum, incluez les éléments suivants :

  • Tests unitaires pour les ViewModels, y compris les Flows
  • Tests unitaires pour les entités de la couche de données (dépôts et sources de données)
  • Tests de navigation dans l'UI utiles pour effectuer des tests de régression dans CI
Préférez les faux aux simulations. Pour en savoir plus sur l'utilisation des faux, consultez Utiliser des doubles de test sur Android.
Testez les StateFlows. Lorsque vous testez StateFlow, procédez comme suit :

Pour en savoir plus, consultez Éléments à tester dans Android et Tester votre mise en page Compose.

Modèles

Appliquez ces bonnes pratiques lorsque vous développez des modèles dans vos applications :

Recommandation Description
Créez un modèle par couche dans les applications complexes.

Dans les applications complexes, créez des modèles dans différentes couches ou différents composants lorsque cela est pertinent. Exemples :

  • Une source de données distante peut mapper le modèle qu'elle reçoit via le réseau sur une classe plus simple avec uniquement les données dont l'application a besoin.
  • Les dépôts peuvent mapper des modèles DAO à des classes de données plus simples avec seulement les informations dont la couche d'interface utilisateur a besoin.
  • ViewModel peut inclure des modèles de couches de données dans les classes UiState.

Conventions d'attribution de noms

Lorsque vous nommez votre codebase, vous devez tenir compte des bonnes pratiques suivantes :

Recommandation Description
Nom des méthodes.
Facultatif
Utilisez des expressions verbales pour nommer les méthodes (par exemple, makePayment()).
Nom des propriétés.
Facultatif
Utilisez des groupes nominaux pour nommer les propriétés (par exemple, inProgressTopicSelection).
Nom des flux de données.
Facultatif
Lorsqu'une classe expose un flux Flow ou autre, la convention d'attribution de noms est get{model}Stream. Par exemple, getAuthorStream(): Flow<Author>. Si la fonction renvoie une liste de modèles, utilisez le nom du modèle au pluriel : getAuthorsStream(): Flow<List<Author>>.
Nom des implémentations d'interface.
Facultatif
Utilisez des noms explicites pour les implémentations d'interfaces. Utilisez Default comme préfixe si vous ne trouvez aucun nom plus adapté. Par exemple, pour une interface NewsRepository, vous pouvez utiliser OfflineFirstNewsRepository ou InMemoryNewsRepository. Si vous ne trouvez pas de nom approprié, utilisez DefaultNewsRepository. Ajoutez le préfixe Fake aux fausses implémentations, par exemple FakeAuthorsRepository.

Ressources supplémentaires

Pour en savoir plus sur l'architecture Android, consultez les ressources supplémentaires suivantes :

Documentation

Afficher le contenu