Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modern Android Architecture

Modern Android Architecture

This talk will cover how to think about Architecture, some recommended principles.

Yves Kalume

February 16, 2023
Tweet

More Decks by Yves Kalume

Other Decks in Programming

Transcript

  1. 🍝 Un code spaghetti Quand on a une mauvaise architecture

    🥵 Non évolutif 💸 Dettes techniques ...
  2. 󰞆 Améliorer la maintenabilité, la qualité et la robustesse Avantages

    d’une bonne architecture 🧱 Scalabilité 🧪 Testabilité 󰞅 L'intégration de nouveau membre dans l'équipe
  3. Séparation des responsabilités (Separation of concerns) • UI gère les

    interactions entre l'interface utilisateur et le système d'exploitation. • UI est piloté par les données, UI Couche de données
  4. Une seule source de vérité (Single Source of Truth) Couche

    Données Données Immuables • Privilégier l’immutabilité • Centraliser toutes les modifications en un seul endroit. Évenements
  5. Couche UI Repositories Data Sources Couche donnée Data Source (Sources

    des données) • Fournit des données • Travaille avec une seule source des données
  6. Couche UI Repositories Data Sources Couche donnée Repositories • Expose

    les données aux couches supérieures • Manage les data sources • Choisi une source de vérité (single source of truth) • Une Repository pour une seule entité
  7. class TasksLocalDataSource ( val tasksDao: TasksDao, ) { fun getTasksStream():

    Flow<List<Task.> {...} suspend fun saveTasks(tasks: List<Task>) {...} } TasksLocalDataSource.kt
  8. class TasksRemoteDataSource ( val apiClient: ApiClient, ) { suspend fun

    getTasks(): List<Task.> {...} } TasksRemoteDataSource.kt
  9. class TasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource,

    ) { fun getTaskStream(): Flow<List<Task.> = tasksLocalDataSource.getTaskStream() private suspend fun updateTaskFromRemoteDataSource() { val remoteTasks = tasksRemoteDataSource.getTasks() tasksLocalDataSource.saveTask(remoteTasks) } } TasksRepository.kt
  10. data class TasksUiState( val items: List<Task> = emptyList(), val isLoading:

    Boolean = false, val userMessage: String? = null ) TasksUiState.kt
  11. sealed interface TasksUiState { object Loading : TasksUiState object Empty

    : TasksUiState data class Success(val items: List<Task>) : TasksUiState data class Error(val errorMessage: String) : TasksUiState } Ou…
  12. @HiltViewModel class TasksViewModel @Inject constructor( private val tasksRepository: TasksRepository, )

    : ViewModel() { val uiState: StateFlow<TasksUiState> } TasksViewModel.kt
  13. @HiltViewModel class TasksViewModel @Inject constructor( private val tasksRepository: TasksRepository, )

    : ViewModel() { val uiState: StateFlow<TasksUiState> = tasksRepository.getTasksStream().map { TasksUiState.Success(...) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = TasksUiState.Loading ) } TasksViewModel.kt
  14. @Composable fun TasksScreen( modifier: Modifier = Modifier, viewModel: TasksViewModel =

    hiltViewModel(), ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() ./ Mettre à jour l’écran when(uiState) { TasksUiState.Empty .> .*Afficher un ecran vide./ TasksUiState.Error .> .*Afficher un message d'erreur./ TasksUiState.Loading .> .*Afficher un Spinner./ TasksUiState.Success .> .*Afficher les données./ } } TasksScreen.kt
  15. Couche domaine (Optionnelle) Couche UI Couche Domaine Couche Données •

    l'encapsulation des codes métiers complexes ou réutilisables dans plusieurs viewmodels • Vous ne devez l'utiliser qu'en cas de besoin • Représente généralement une seule action de l’utilisateur dans L’application
  16. @HiltViewModel class TasksViewModel @Inject constructor( private val getFilteredTasksUseCase: GetFilteredTasksUseCase )

    : ViewModel() { val uiState: StateFlow<TasksUiState> = getFilteredTasksUseCase.execute().map { TasksUiState.Success(...) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = TasksUiState.Loading ) } TasksViewModel.kt
  17. @HiltViewModel class TasksViewModel @Inject constructor( private val getFilteredTasksUseCase: GetFilteredTasksUseCase )

    : ViewModel() { val uiState: StateFlow<TasksUiState> = getFilteredTasksUseCase().map { TasksUiState.Success(...) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = TasksUiState.Loading ) } TasksViewModel.kt
  18. Quelques bonnes pratiques • Exposez le moins possible de chaque

    partie. • Chaque partie doit être testable de manière isolée. • Ne réinventez pas la roue • Chaque composant est responsable de sa politique de concurrence (Threading).