Ce tutoriel vous aidera à apprendre l'utilisation de StateFlow dans Android à l'aide d'exemples simples et isolés, étape par étape.

Qu'est-ce que Stateflow ?

StateFlow est un flux observable de détenteurs d'états qui émet les mises à jour de l'état actuel et du nouvel état à ses collecteurs.

Dans Android, StateFlow est un excellent outil pour les classes qui ont besoin de maintenir un état observable et mutable.

Par exemple, un StateFlow peut être exposé depuis le YourViewModel afin que le View puisse écouter les mises à jour de l'état de l'interface utilisateur et faire en sorte que l'état de l'écran survive aux changements de configuration.

Voici un exemple d'utilisation du code :

class LatestNewsViewModel(
    private val newsRepository: NewsRepository
) : ViewModel() {

    // Backing property to avoid state updates from other classes
    private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
    // The UI collects from this StateFlow to get its state updates
    val uiState: StateFlow<LatestNewsUiState> = _uiState

    init {
        viewModelScope.launch {
            newsRepository.favoriteLatestNews
                // Update View with the latest favorite news
                // Writes to the value property of MutableStateFlow,
                // adding a new element to the flow and updating all
                // of its collectors
                .collect { favoriteNews ->
                    _uiState.value = LatestNewsUiState.Success(favoriteNews)
                }
        }
    }
}

// Represents different states for the LatestNews screen
sealed class LatestNewsUiState {
    data class Success(news: List<ArticleHeadline>): LatestNewsUiState()
    data class Error(exception: Throwable): LatestNewsUiState()
}

Voyons maintenant quelques exemples complets.

Exemple 1 : Kotlin Android Simple Stateflow Exemple

Un exemple simple et isolé pour vous donner une idée de la façon d'utiliser Stateflow dans une application complète.

L'application vous aide également à apprendre ce qui suit :

  • Viewmodel
  • Coroutines Kotlin
  • StateFlow
  • Viewbinding

Étape 1 : Créer un projet

Commencez par créer un projet Android Studio vide.

Étape 2 : Dépendances

Ajoutez les dépendances suivantes dans votre app/build.gradle :

    // architectural components
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

    // coroutines
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

    // activity ktx for viewmodel
    implementation "androidx.activity:activity-ktx:1.1.0"

    // coroutine lifecycle scopes
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"

Step 3 : Activer Java8 et ViewBinding

Dans le même app/build.gradle, allez-y et activez Java8 et ViewBinding à l'intérieur de la fermeture android{} :

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures {
        viewBinding true
    }

Étape 4 : Conception de la mise en page

Concevez une mise en page MainActivity avec un tas d'edittexts et un bouton :

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parent_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="vertical">

        <com.google.android.material.textfield.TextInputLayout
            style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="30dp"
            android:layout_marginEnd="30dp"
            android:hint="@string/login">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/login_field"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.textfield.TextInputLayout
            style="@style/Widget.MaterialComponents.TextInputLayout.FilledBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="30dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="30dp"
            android:hint="@string/password">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/password_field"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />
        </com.google.android.material.textfield.TextInputLayout>

        <com.google.android.material.button.MaterialButton
            android:id="@+id/login_b"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="30dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="30dp"
            android:text="@string/login"
            app:elevation="10dp" />
    </LinearLayout>

    <ProgressBar
        android:id="@+id/progress_bar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

Étape 5 : Création d'un ViewModel

Créez un ViewModel où nous utiliserons Stateflow pour émettre des mises à jour vers l'interface utilisateur :

Créez un MainViewModel.kt puis commencez par ajouter des importations :

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

Étendez la classe androidx.lifecycle.ViewModel :

class MainViewModel : ViewModel() {

Définissez deux champs d'instance : un objet MutableStateFlow et un objet StateFlow :

    private val _loginState = MutableStateFlow<LoginUIState>(LoginUIState.Empty)
    val loginUIState: StateFlow<LoginUIState> = _loginState

Créez maintenant une fonction pour simuler le processus de connexion :

    fun login(username: String, password: String) = viewModelScope.launch {
        _loginState.value = LoginUIState.Loading
        // fake network request time
        delay(2000L)
        if (username == "raheem" && password == "android") {
            _loginState.value = LoginUIState.Success
        } else {
            _loginState.value = LoginUIState.Error("Incorrect password")
        }
    }

Créez une classe scellée pour contenir les états de l'ui de connexion :

    sealed class LoginUIState {
        object Success : LoginUIState()
        data class Error(val message: String) : LoginUIState()
        object Loading : LoginUIState()
        object Empty : LoginUIState()
    }
}

Voici le code complet :

MainViewModel.kt


import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class MainViewModel : ViewModel() {

    private val _loginState = MutableStateFlow<LoginUIState>(LoginUIState.Empty)
    val loginUIState: StateFlow<LoginUIState> = _loginState

    // simulate login process
    fun login(username: String, password: String) = viewModelScope.launch {
        _loginState.value = LoginUIState.Loading
        // fake network request time
        delay(2000L)
        if (username == "raheem" && password == "android") {
            _loginState.value = LoginUIState.Success
        } else {
            _loginState.value = LoginUIState.Error("Incorrect password")
        }
    }

    // login ui states
    sealed class LoginUIState {
        object Success : LoginUIState()
        data class Error(val message: String) : LoginUIState()
        object Loading : LoginUIState()
        object Empty : LoginUIState()
    }
}

Étape 6 : Création de l'activité principale (MainActivity)

Voici le code complet pour le MainActivity.kt :

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.flow.collect
import xyz.teamgravity.stateflow.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.apply {

            // login button
            loginB.setOnClickListener {
                viewModel.login(loginField.text.toString().trim(), passwordField.text.toString().trim())
            }

            // collect data and respond
            lifecycleScope.launchWhenCreated {
                viewModel.loginUIState.collect {
                    when (it) {
                        is MainViewModel.LoginUIState.Loading -> {
                            progressBar.visibility = View.VISIBLE
                        }

                        is MainViewModel.LoginUIState.Success -> {
                            Snackbar.make(parentLayout, "Successfully logged in", Snackbar.LENGTH_SHORT).show()
                            progressBar.visibility = View.GONE
                        }

                        is MainViewModel.LoginUIState.Error -> {
                            Snackbar.make(parentLayout, it.message, Snackbar.LENGTH_SHORT).show()
                            progressBar.visibility = View.GONE
                        }

                        else -> Unit
                    }
                }
            }
        }
    }
}

Exécuter

Copiez le code ou téléchargez-le dans le lien ci-dessous, construisez et exécutez.

Référence

Voici les liens de référence :

Télécharger Exemple

Catégorisé: