Slide 1

Slide 1 text

Modern Android App Architecture Yves Kalume - @kalumeyves Android Developer

Slide 2

Slide 2 text

Android Architecture

Slide 3

Slide 3 text

Android Architecture UI (Activités, Fragments, etc) ViewModel Repository Datasource 1 Datasource 1 Datasource 3

Slide 4

Slide 4 text

📞 La communication 🔗 Les frontières 📝 Les responsabilités Le but de l’architecture

Slide 5

Slide 5 text

🍝 Un code spaghetti Quand on a une mauvaise architecture 🥵 Non évolutif 💸 Dettes techniques ...

Slide 6

Slide 6 text

󰞆 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

Slide 7

Slide 7 text

Et l'utilisateur dans tout ça ? 󰣿

Slide 8

Slide 8 text

Par oĂą commencer ? ó°ź‘

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Architecture moderne Android Couche UI Couche Données

Slide 11

Slide 11 text

Principes de base

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Flux des données unidirectionnel (Unidirectional dataflow) Couche UI Couche Données Évenements Données

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Couche des données (Data layer)

Slide 16

Slide 16 text

Couche UI Repositories Data Sources Couche donnée Couche des données

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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é

Slide 19

Slide 19 text

Exemple

Slide 20

Slide 20 text

class TasksLocalDataSource ( val tasksDao: TasksDao, ) { fun getTasksStream(): Flow {...} suspend fun saveTasks(tasks: List) {...} } TasksLocalDataSource.kt

Slide 21

Slide 21 text

class TasksRemoteDataSource ( val apiClient: ApiClient, ) { suspend fun getTasks(): List {...} } TasksRemoteDataSource.kt

Slide 22

Slide 22 text

class TasksRepository( private val tasksRemoteDataSource: TasksDataSource, private val tasksLocalDataSource: TasksDataSource, ) { fun getTaskStream(): Flow = tasksLocalDataSource.getTaskStream() private suspend fun updateTaskFromRemoteDataSource() { val remoteTasks = tasksRemoteDataSource.getTasks() tasksLocalDataSource.saveTask(remoteTasks) } } TasksRepository.kt

Slide 23

Slide 23 text

Couche UI (UI layer)

Slide 24

Slide 24 text

Couche UI Interface utilisateur Gestionnaire d’état Couche UI Couche Données

Slide 25

Slide 25 text

Couche UI UI State Holder (ViewModel, etc) Données Données (États) événements

Slide 26

Slide 26 text

Couche UI

Slide 27

Slide 27 text

data class TasksUiState( val items: List = emptyList(), val isLoading: Boolean = false, val userMessage: String? = null ) TasksUiState.kt

Slide 28

Slide 28 text

sealed interface TasksUiState { object Loading : TasksUiState object Empty : TasksUiState data class Success(val items: List) : TasksUiState data class Error(val errorMessage: String) : TasksUiState } Ou…

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

@HiltViewModel class TasksViewModel @Inject constructor( private val tasksRepository: TasksRepository, ) : ViewModel() { val uiState: StateFlow = tasksRepository.getTasksStream().map { TasksUiState.Success(...) }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = TasksUiState.Loading ) } TasksViewModel.kt

Slide 31

Slide 31 text

@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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

data class GetFilteredTasksUseCase(private val repository: TasksRepository){ fun execute(): Flow {...} } GetFilteredTasksUseCase.kt

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

data class GetFilteredTasksUseCase(private val repository: TasksRepository){ fun invoke(): Flow {...} } GetFilteredTasksUseCase.kt

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Quelques bonnes pratiques

Slide 38

Slide 38 text

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).

Slide 39

Slide 39 text

Conclusion

Slide 40

Slide 40 text

Merci ! Yves Kalume - @kalumeyves Android Developer, GDG Lubumbashi

Slide 41

Slide 41 text

Quelques liens https://developer.android.com/topic/architecture https://developer.android.com/quality https://github.com/android/nowinandroid https://github.com/android/architecture-samples/