Pular para o conteúdo principal

Exemplo de Kotlin Android StateFlow

Este tutorial ajudará você a aprender sobre o uso do StateFlow no Android usando exemplos isolados passo a passo simples.

O que é Stateflow?

StateFlow é um fluxo observável de detentor de estado que emite a corrente e novas atualizações de estado para seus coletores.

No Android, o StateFlow é uma ótima opção para classes que precisam manter um estado mutável observável.

Por exemplo, um StateFlow pode ser exposto a partir do YourViewModel para que o View possa ouvir atualizações de estado da interface do usuário e inerentemente fazer com que o estado da tela sobreviva às alterações de configuração.

Aqui está o exemplo de uso de código:

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

Vejamos agora alguns exemplos completos.

Exemplo 1: Exemplo de fluxo de estado simples do Android Kotlin

Um exemplo isolado simples para dar uma ideia de como usar o Stateflow em um aplicativo completo.

O aplicativo também ajuda você a aprender o seguinte:

  • Viewmodel
  • Corrotinas Kotlin
  • StateFlow
  • Encadernação

Etapa 1: criar projeto

Comece criando um projeto Android Studio vazio.

Etapa 2: dependências

Adicione as seguintes dependências em seu 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"

Etapa 3: ativar Java8 e ViewBinding

No mesmo app/build.gradle vá em frente e ative o Java8 e o ViewBinding dentro do fechamento do android{}:

    compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '1.8'
}

buildFeatures {
viewBinding true
}

Etapa 4: Layout do projeto

Crie um layout MainActivity com vários edittexts e um botão:

atividade_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>

Etapa 5: criar um ViewModel

Crie um ViewModel onde usaremos o Stateflow para emitir atualizações para a interface do usuário:

Crie um MainViewModel.kt e comece adicionando importações:

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

Estenda a classe androidx.lifecycle.ViewModel:

class MainViewModel : ViewModel() {

Defina dois campos de instância: objetos MutableStateFlow e StateFlow:

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

Agora crie uma função para simular o processo de login:

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

Crie uma classe selada para manter os estados da interface do usuário de login:

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

Aqui está o código completo:

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

Etapa 6: Criar MainActivity

Aqui está o código completo para o 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
}
}
}
}
}
}

Correr

Copie o código ou baixe-o no link abaixo, construa e execute.

Referência

Seguem os links de referência:

Download Exemplo