Dieses Tutorial wird Ihnen helfen, die Verwendung von StateFlow in Android anhand von einfachen, isolierten Beispielen Schritt für Schritt zu erlernen.

Was ist Stateflow?

StateFlow ist ein beobachtbarer Zustandsfluss, der die aktuellen und neuen Zustandsaktualisierungen an seine Collectors sendet.

In Android eignet sich StateFlow hervorragend für Klassen, die einen beobachtbaren, veränderbaren Zustand beibehalten müssen.

Zum Beispiel kann ein StateFlow vom YourViewModel exponiert werden, so dass die View auf UI-Zustandsaktualisierungen hören kann und der Bildschirmzustand inhärent Konfigurationsänderungen überlebt.

Hier ein Beispiel für die Verwendung des Codes:

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

Schauen wir uns nun einige vollständige Beispiele an.

Beispiel 1: Kotlin Android Simple Stateflow Example

Ein einfaches, isoliertes Beispiel, um Ihnen eine Vorstellung davon zu geben, wie man Stateflow in einer vollständigen App verwendet.

Die App hilft Ihnen auch, das Folgende zu lernen:

  • Viewmodel
  • Kotlin Koroutinen
  • StateFlow
  • Viewbinding

Schritt 1: Projekt erstellen

Beginnen Sie mit der Erstellung eines leeren Android Studio-Projekts.

Schritt 2: Abhängigkeiten

Fügen Sie die folgenden Abhängigkeiten in Ihrer app/build.gradle hinzu:

    // 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"

Schritt 3: Aktivieren Sie Java8 und ViewBinding

In der gleichen app/build.gradle aktivieren Sie nun Java8 und ViewBinding innerhalb des android{}Gebiets:

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures {
        viewBinding true
    }

Schritt 4: Layout entwerfen

Entwerfen Sie ein MainActivity-Layout mit einer Reihe von Edittexten und einem Button:

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>

Schritt 5: Erstellen eines ViewModels

Erstellen Sie ein ViewModel, in dem wir Stateflow verwenden werden, um Updates an die Benutzeroberfläche zu senden:

Erstellen Sie eine MainViewModel.kt und beginnen Sie dann mit dem Hinzufügen von Importen:

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

Erweitern Sie die Klasse androidx.lifecycle.ViewModel:

class MainViewModel : ViewModel() {

Definieren Sie zwei Instanzfelder: ein MutableStateFlow und StateFlow Objekte:

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

Erstellen Sie nun eine Funktion, um den Anmeldevorgang zu simulieren:

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

Erstellen Sie eine versiegelte Klasse, um die Login-Zustände der Benutzeroberfläche zu speichern:

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

Hier ist der vollständige Code:

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

Schritt 6: MainActivity erstellen

Hier ist der vollständige Code für die 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
                    }
                }
            }
        }
    }
}

Run

Kopieren Sie den Code oder laden Sie ihn über den unten stehenden Link herunter, erstellen Sie ihn und führen Sie ihn aus.

Referenz

Hier sind die Referenzlinks:

Download Beispiel

Categorized in: