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