Android StateFlow初探
前言:
最近在学习StateFlow,感觉很好用,也很神奇,于是记录了一下.
1.简介:
StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 value 属性读取当前状态值。如需更新状态并将其发送到数据流,请为 MutableStateFlow 类的 value
属性分配一个新值。
2.和Flow、LiveData联系,官网解释如下:
StateFlow、Flow 和 LiveData
StateFlow
和 LiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。
但请注意,StateFlow
和 LiveData 的行为确实有所不同:
StateFlow
需要将初始状态传递给构造函数,而LiveData
不需要。- 当 View 进入
STOPPED
状态时,LiveData.observe()
会自动取消注册使用方,而从StateFlow
或任何其他数据流收集数据的操作并不会自动停止。如需实现相同的行为,您需要从Lifecycle.repeatOnLifecycle
块收集数据流。
3.MainViewModel代码:
package com.example.stateflowdemo.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.stateflowdemo.model.LoginUIState
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@ExperimentalCoroutinesApi
class MainViewModel :ViewModel(){
private val _loginUIState = MutableStateFlow<LoginUIState>(LoginUIState.Empty)
val loginUiState: StateFlow<LoginUIState> = _loginUIState
fun login(username:String,password: String) = viewModelScope.launch {
_loginUIState.value = LoginUIState.Loading
delay(2000L)
if(username == "android" && password == "123456") {
_loginUIState.value = LoginUIState.Success
} else {
_loginUIState.value = LoginUIState.Error("账号或密码不正确,请重试")
}
}
}
4.LoginUIState代码:
sealed class LoginUIState {
object Success : LoginUIState()
data class Error(val message: String) : LoginUIState()
object Loading : LoginUIState()
object Empty : LoginUIState()
}
5.测试代码:
package com.example.stateflowdemo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.example.stateflowdemo.databinding.ActivityMainBinding
import com.example.stateflowdemo.model.LoginUIState
import com.example.stateflowdemo.viewmodel.MainViewModel
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.ExperimentalCoroutinesApi
@OptIn(ExperimentalCoroutinesApi::class)
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel:MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
initViewModel()
}
private fun initView() {
binding.btnLogin.setOnClickListener {
viewModel.login(
binding.etUsername.text.toString(),
binding.etPassword.text.toString()
)
}
}
private fun initViewModel() {
lifecycleScope.launchWhenStarted {
viewModel.loginUiState.collect {
listOf(
when (it) {
is LoginUIState.Success -> {
Snackbar.make(
binding.root,
"Successfully logged in",
Snackbar.LENGTH_LONG
).show()
binding.progressBar.isVisible = false
}
is LoginUIState.Error -> {
Snackbar.make(
binding.root,
it.message,
Snackbar.LENGTH_LONG
).show()
binding.progressBar.isVisible = false
}
is LoginUIState.Loading -> {
binding.progressBar.isVisible = true
}
else -> Unit
}
)
}
}
}
}
6.布局代码如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Username"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/textInputLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etUsername"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ems="15" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="Password"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etPassword"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPassword"
android:ems="15" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnLogin"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="Login"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout2"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout2" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/btnLogin"
app:layout_constraintEnd_toEndOf="@+id/textInputLayout2"
app:layout_constraintStart_toStartOf="@+id/textInputLayout2"
app:layout_constraintTop_toTopOf="@+id/btnLogin" />
</androidx.constraintlayout.widget.ConstraintLayout>
7.实现的效果图如下:
8.demo源码地址:
https://gitee.com/jackning_admin/state-flow-sample