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

相关推荐
雨白41 分钟前
Jetpack系列(二):Lifecycle与LiveData结合,打造响应式UI
android·android jetpack
kk爱闹2 小时前
【挑战14天学完python和pytorch】- day01
android·pytorch·python
每次的天空4 小时前
Android-自定义View的实战学习总结
android·学习·kotlin·音视频
恋猫de小郭4 小时前
Flutter Widget Preview 功能已合并到 master,提前在体验毛坯的预览支持
android·flutter·ios
断剑重铸之日5 小时前
Android自定义相机开发(类似OCR扫描相机)
android
随心最为安5 小时前
Android Library Maven 发布完整流程指南
android
岁月玲珑5 小时前
【使用Android Studio调试手机app时候手机老掉线问题】
android·ide·android studio
还鮟10 小时前
CTF Web的数组巧用
android
小蜜蜂嗡嗡11 小时前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0011 小时前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体