Android StateFlow初探

Android StateFlow初探

前言:

最近在学习StateFlow,感觉很好用,也很神奇,于是记录了一下.

1.简介:

StateFlow 是一个状态容器式可观察数据流,可以向其收集器发出当前状态更新和新状态更新。还可通过其 value 属性读取当前状态值。如需更新状态并将其发送到数据流,请为 MutableStateFlow 类的 value 属性分配一个新值。

2.和Flow、LiveData联系,官网解释如下:

StateFlow、Flow 和 LiveData

StateFlowLiveData 具有相似之处。两者都是可观察的数据容器类,并且在应用架构中使用时,两者都遵循相似模式。

但请注意,StateFlowLiveData 的行为确实有所不同:

  • StateFlow 需要将初始状态传递给构造函数,而 LiveData 不需要。
  • 当 View 进入 STOPPED 状态时,LiveData.observe() 会自动取消注册使用方,而从 StateFlow 或任何其他数据流收集数据的操作并不会自动停止。如需实现相同的行为,您需要从 Lifecycle.repeatOnLifecycle 块收集数据流。

3.MainViewModel代码:

kotlin 复制代码
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代码:

kotlin 复制代码
sealed class LoginUIState {
    object Success : LoginUIState()
    data class Error(val message: String) : LoginUIState()
    object Loading : LoginUIState()
    object Empty : LoginUIState()
}

5.测试代码:

kotlin 复制代码
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.布局代码如下:

kotlin 复制代码
<?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

相关推荐
mmsx4 分钟前
一个黑边 Bug 修了两版:自己算矩阵直接黑屏,借库重建只用了一行 setZoom
android·前端
枢影Kernel9 分钟前
Android CLI 与 Android Skills 最佳实践:把 AI Agent 接入可验证的 Android 开发流程
android
Kapaseker1 小时前
没想到吧!Skill 也可以测试 — 小白都看得懂的 Skill 教程
android·人工智能·kotlin
我命由我123451 小时前
Android 开发问题:使用 AndroidTreeView 时,自定义视图无法撑满父容器
android·java·java-ee·kotlin·android studio·android-studio·android runtime
没文化的阿浩1 小时前
【MySQL】数据类型
android·mysql·adb
恋猫de小郭2 小时前
社区版 Dart macros?一个可以解决 JSON 序列化的Fluter “宏编程”第三方包
android·前端·flutter
mmsx2 小时前
Android Design 项目集合
android
启雀AI16 小时前
培训平台移动端离线学习方案设计与实现:视频缓存、断点续传与进度同步的工程实践
android·学习·缓存·音视频·企业lms
我命由我1234517 小时前
RxJava - 冷数据流与热数据流
android·java·java-ee·android studio·rxjava·android-studio·android runtime
2601_9665635218 小时前
愤怒的小鸟单机版 去广告 安卓+PC端离线纯净版 益智耐玩小游戏 不需联网 老旧手机都可以玩 怀旧手机小游戏
android·智能手机