Compose之ViewModel内更新UI的三种方式

1,681 阅读2分钟

本文介绍了在ViewModel内如何使用State/Flow/LivData触发重组更新UI,以及注意事项

1.前提:

重组由State#value触发,即State#value变化后,会触发重组。参考Compse之重组

2.在Compose的ViewModel中通过LiveData、Flow、State去操作数据,进而更新UI

  • 对于 LiveData,需要将 LiveData 转换为 State
  • 对于 Flow,需要将 Flow 转换为 State

3.小例子:

在可组合方法Greeting内,先展示原始数据,从TestRepo中获取数据后,赋值给s,触发重组,再次绘制Text

class TestViewModel : ViewModel() {
    //Repository中间层 管理所有数据来源 包括本地的及网络的
    private val mTestRepo = TestRepository()

    private var _str1 = mutableStateOf("")
    var str1: State<String> = _str1

    private var _str2 = MutableStateFlow("")
    var str2 = _str2.asStateFlow()

    private val _str3 = MutableLiveData("")
    val str3: LiveData<String> = _str3

    fun getDateFromServer() {
        launchRequest {
            val result = mTestRepo.getDateFromServer()
            if (result.items.isNotEmpty()) {
                _str1.value = "1111"
                _str2.value = "2222"
                Log.d("lym", "getDateFromServer: ${_str1.value}, ${_str2.value}")

                //MutableLiveData#setValue(T) 必须在主线程中调用
                //MutableLiveData#postValue(T) 既可以在主线程中调用, 也可以在子线程中调用
                //_str3.value = "3333" //error:Cannot invoke setValue on a background thread
                _str3.postValue("3333")
            }
        }
    }
}
class MainActivity : ComponentActivity() {
    private val mViewModel: TestViewModel by lazy {
        ViewModelProvider(this)[TestViewModel::class.java]
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Theme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
                ) {
                    Log.d("lym", "onCreate: ")
                    Greeting(mViewModel)
                }
            }
        }
    }
}

@SuppressLint("StateFlowValueCalledInComposition")
@Composable
fun Greeting(vm: TestViewModel, modifier: Modifier = Modifier) {
    // 将 Flow<T> 转换成 State<T>
    val str2Value by vm.str2.collectAsStateWithLifecycle()
    // 将 Flow<T> 转换成 State<T>
    val str2ValueAnother by vm.str2.collectAsState()
    // 将 LiveData<T> 转换成 State<T>
    val str3Value by vm.str3.observeAsState()

    LaunchedEffect(Unit) {
        vm.getDateFromServer("Android", 0, 1)
    }

//    Log.d("lym", "Greeting: ${vm.str1.value}")
//    val s = vm.str1.value
    Log.d("lym", "Greeting: $str2Value")
    val s = str2Value
//    Log.d("lym", "Greeting: $str2ValueAnother")
//    val s = str2ValueAnother

//    Log.d("lym", "Greeting: $str3Value")
//    val s = str3Value

    val text = if (!TextUtils.isEmpty(s)) s else "Android"
    Text(
        text = "Hello ${text}!", modifier = modifier
    )
}

3.1 Jetpace的组件ViewModel也是MVVM架构的ViewModel层,需要添加依赖

implementation "androidx.lifecycle:lifecycle-viewmodel:2.6.2"

3.2 Flow.collectAsState() & Flow.collectAsStateWithLifecycle()都可,如何选择

在 Android开发中使用collectAsStateWithLifecycle()方法来收集数据流,它会以生命周期感知型方式从 Flow 收集值。此时需要添加依赖

implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.1"

collectAsState() 不是生命周期感知的,通常用于跨平台的场景下(Compose也可以跨平台),且collectAsState不需引入其他库。

注意:Greeting方法内,无val str2ValueAnother by vm.str2.collectAsState(),不会触发重组,即Text不会更新。collectAsState作用就是把Flow变为State, str2ValueAnother = State#value,State#value变化,触发重组。若只定义val str2ValueAnother by vm.str2.collectAsState(),但不使用str2ValueAnother,也不会触发重组

3.3 LiveData.obseverAsState()

observeAsState()会开始观察此 LiveData,并在LiveData有数据更新时,自动将其转换为State ,进而触发可组合项的重组。此时需要添加依赖:

implementation "androidx.compose.runtime:runtime-livedata:1.1.1"

3.4 执行网络请求时,使用viewModelScope.launch来启动协程,引入方式:

implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'

4.参考

Jetpack Compose之在ViewModel中应该使用哪种State

Jetpack Compose | State状态管理及界面刷新 - 掘金 (juejin.cn)

Android Compose UI状态管理之生命周期(一) - 掘金 (juejin.cn)

[Compose] ViewModel - 掘金 (juejin.cn)