#Flow avec ViewModel et Compose 🌊

Les Flows sont un concept central dans le développement Android moderne avec Kotlin. Ils permettent de gérer les flux de données de manière réactive et sont particulièrement bien adaptés pour l’architecture MVVM avec Compose.

#Qu’est-ce qu’un Flow ? 🤔

Un Flow est un flux de données asynchrone qui peut émettre plusieurs valeurs séquentiellement. C’est l’équivalent Kotlin des Observables en RxJava ou des Streams en Java.

#Pourquoi utiliser les Flows ? 🎯

  1. Programmation réactive : Parfait pour gérer des données qui changent dans le temps
  2. Support natif des coroutines : Intégration parfaite avec l’écosystème Kotlin
  3. Cold streams : Les Flows ne commencent à émettre que lorsqu’un collecteur est présent

#Flow vs StateFlow 🆚

kotlin
// Flow basique
val simpleFlow = flow {
    emit(1)
    delay(500)
    emit(2)
    delay(500)
    emit(3)
}

// StateFlow
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter.asStateFlow()
FlowStateFlow
Peut émettre plusieurs valeursMaintient toujours une valeur courante
Ne stocke pas de valeurStocke la dernière valeur émise
Démarre à chaque collectionHot stream (toujours actif)
Idéal pour les données éphémèresParfait pour l’état de l’UI

#Utilisation dans un ViewModel 🏗️

Les ViewModels utilisent généralement StateFlow pour l’état de l’UI :

kotlin
class OrderViewModel : ViewModel() {
    // UI State
    private val _uiState = MutableStateFlow<OrderUiState>(OrderUiState.Initial)
    val uiState: StateFlow<OrderUiState> = _uiState.asStateFlow()

    // Events (utilisation d'un SharedFlow pour les événements ponctuels)
    private val _events = MutableSharedFlow<OrderEvent>()
    val events = _events.asSharedFlow()

    fun loadOrders() {
        viewModelScope.launch {
            _uiState.value = OrderUiState.Loading
            try {
                val orders = repository.getOrders()
                _uiState.value = OrderUiState.Success(orders)
            } catch (e: Exception) {
                _uiState.value = OrderUiState.Error(e.message)
            }
        }
    }
}

sealed class OrderUiState {
    data object Initial : OrderUiState()
    data object Loading : OrderUiState()
    data class Success(val orders: List<Order>) : OrderUiState()
    data class Error(val message: String?) : OrderUiState()
}

#Collection dans Compose 🎨

Compose fournit des extensions pratiques pour collecter les Flows :

kotlin
@Composable
fun OrderScreen(viewModel: OrderViewModel) {
    // Collecte du StateFlow
    val uiState by viewModel.uiState.collectAsState()

    // Gestion des différents états
    when (uiState) {
        is OrderUiState.Initial -> {
            // Afficher l'état initial
        }
        is OrderUiState.Loading -> {
            LoadingIndicator()
        }
        is OrderUiState.Success -> {
            val orders = (uiState as OrderUiState.Success).orders
            OrderList(orders = orders)
        }
        is OrderUiState.Error -> {
            val message = (uiState as OrderUiState.Error).message
            ErrorMessage(message = message)
        }
    }
}

#Bonnes Pratiques 🎯

  1. StateFlow pour l’UI State

    • Utilisez StateFlow pour reprĂ©senter l’état de votre UI
    • Exposez un StateFlow immuable depuis votre ViewModel
  2. SharedFlow pour les Events

    • Utilisez SharedFlow pour les Ă©vĂ©nements ponctuels
    • Parfait pour les snackbars, navigations, etc.
  3. Flow pour les données asynchrones

    • Utilisez Flow simple pour les opĂ©rations asynchrones comme les appels rĂ©seau
    • Transformez en StateFlow dans le ViewModel si nĂ©cessaire
  4. Gestion des erreurs

    • Utilisez try-catch dans les coroutines
    • ReflĂ©tez les erreurs dans l’UI state
kotlin
// Exemple d'utilisation combinée
class OrderViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<OrderUiState>(OrderUiState.Initial)
    val uiState = _uiState.asStateFlow()

    init {
        // Transformation d'un Flow en StateFlow
        repository.getOrdersFlow()
            .map { OrderUiState.Success(it) }
            .catch { emit(OrderUiState.Error(it.message)) }
            .onStart { emit(OrderUiState.Loading) }
            .onEach { _uiState.value = it }
            .launchIn(viewModelScope)
    }
}

#Points Avancés 🚀

#1. StateFlow vs LiveData

StateFlow est le successeur moderne de LiveData:

  • Support natif des coroutines
  • Plus flexible et puissant
  • Meilleure intĂ©gration avec Compose

#2. Flow Operators

Les Flows supportent de nombreux opérateurs :

  • map, filter, transform
  • combine, merge, zip
  • catch, retry, onEach
kotlin
// Exemple d'opérateurs Flow
repository.getOrdersFlow()
    .map { orders -> orders.filter { it.isActive } }
    .catch { /* gestion d'erreur */ }
    .collect { /* utilisation des données */ }

đź’ˇ Astuce

L’opérateur combine ne fonctionne que si les flows ont émis au moins une valeur !

#3. Testing

Les Flows sont testables grâce à TestCoroutineDispatcher :

kotlin
@Test
fun `test flow emissions`() = runTest {
    val viewModel = OrderViewModel(testRepository)
    viewModel.loadOrders()

    val states = mutableListOf<OrderUiState>()
    val job = launch { viewModel.uiState.toList(states) }

    assertEquals(OrderUiState.Loading, states[0])
    assertEquals(OrderUiState.Success(testOrders), states[1])

    job.cancel()
}

#✅ Critères de Validation

CritèreValidation
Vous comprenez la différence entre Flow et StateFlow[ ]
Vous savez utiliser StateFlow dans un ViewModel[ ]
Vous savez collecter un Flow dans un Composable[ ]
Vous maîtrisez la gestion des états avec Flow[ ]
Vous comprenez les opérateurs de base des Flows[ ]

đź’ˇ Conseil

Les Flows sont un outil puissant mais peuvent sembler complexes au début. Commencez par les cas simples avec StateFlow dans vos ViewModels, puis explorez progressivement les fonctionnalités plus avancées.