【问题标题】:Multiple LiveData/StateFlow for one pair of Fragment-ViewModel一对 Fragment-ViewModel 的多个 LiveData/StateFlow
【发布时间】:2021-06-16 09:23:59
【问题描述】:

我有一个 Fragment,它代表单个 Activity 应用程序中的一个屏幕,以及一个用于该 Fragment 的 ViewModel。

ViewModel 使用多个存储库从多个 API 调用中加载一组数据,并通过 multiple StateFlow 将该数据发送到片段。

假设片段有 2 个视图,每个视图都从与其相关的 StateFlow 中收集数据。直到所有 2 个视图都没有绘制它们的数据,我想显示一些进度条,然后当这些视图接收数据时,将整个片段可见性从不可见设置为可见。

我的问题是:当所有 2 个视图都收到数据时如何正确处理?

存储库:

class Repository(private val name: String) {
    private val _data = MutableStateFlow<String?>(null)
    val data = _data.asStateFlow()

    suspend fun load() {
        // load from the DB/API if no data
        // load fresh from the API if have data

        delay(1000)
        _data.emit("$name data")
    }
}

视图模型:

class ScreenViewModel : ViewModel() {
    // will be injected by Dagger2/Hilt
    private val repository1 = Repository("Repository1")
    private val repository2 = Repository("Repository2")

    private val _flow1 = MutableStateFlow<String?>(null)
    private val _flow2 = MutableStateFlow<String?>(null)

    val flow1 = _flow1.asStateFlow()
    val flow2 = _flow2.asStateFlow()

    init {
        viewModelScope.launch {
            repository1.data.collect {
                _flow1.emit(it)
            }
        }
        viewModelScope.launch {
            repository2.data.collect {
                _flow2.emit(it)
            }
        }

        viewModelScope.launch {
            repository1.load()
        }
        viewModelScope.launch {
            repository2.load()
        }
    }
}

片段:

class Screen : Fragment(/*layoutd*/) {
    private val viewModel: ScreenViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.flow1.filterNotNull().collect {
                draw1(it)
            }
        }
        viewLifecycleOwner.lifecycleScope.launch {
            viewModel.flow2.filterNotNull().collect {
                draw2(it)
            }
        }
    }

    private fun draw1(data: String) {
        // some stuff
    }

    private fun draw2(data: String) {
        // some stuff
    }
}

【问题讨论】:

  • 您可以尝试在 val call1 = async { repository1.load() } val call2 = async { repository2.load() } draw(call1.await(), call2.await()) 这样的延迟调用中调用您的存储库,这样可以确保在调用 draw 方法之前完成两个调用。为了处理状态,您可以使用 Resources 包装器或它的变体,以便您的 UI 可以区分状态。 developer.android.com/jetpack/guide#addendum

标签: android mvvm android-livedata android-viewmodel kotlin-stateflow


【解决方案1】:

对于这种情况,我会选择BindingAdapters

首先在我的 ViewModel 文件中(在 ViewModel 类之外),我将创建一个 Enum Class

class ScreenViewModel : ViewModel() { .... }
enum class LoadingStatus {
  LOADING,
  ERROR,
  FINISHED
}

然后我会在 ViewModel 中创建一个由 LiveData 支持的 MutableLiveData 来监控加载状态。

private val _status = MutableLiveData<LoadingStatus>()
  val status: LiveData<LoadingStatus>
    get() = _status

在初始化块中,我将更改 MutableLiveData 的值

    init {
          viewModelScope.launch {
            
          try {
//before fetching data show progressbar loading
            _status.value = LoadingStatus.LOADING
            repository1.data.collect {
              _flow1.emit(it)
            
    
          }

//after fetching the data change status to FINISED
            _status.value = LoadingStatus.FINISHED
            }
          
          catch (e:Exception) {
            _status.value = LoadingStatus.FINISHED
            
          }
    }  

在一个单独的 Kotlin 文件中,我为 Binding Adapter 编写代码,这会使 ProgressBar 根据状态消失。

@BindingAdapter("apiLoadingStatus")
fun ProgressBar.setApiLoadingStatus(status:LoadingStatus?){

    when(status){

        LoadingStatus.LOADING -> {
            visibility = View.VISIBLE
            
        }
        LoadingStatus.FINISHED -> {
           

            this.visibility = View.GONE
        }
        LoadingStatus.ERROR -> {
             this.visibility = View.GONE
        }

    }
}

然后为 ProgressBar 的 Fragmement XML 包含此代码。注意我在这里使用 DataBinding

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:motion="http://schemas.android.com/tools">

    <!-- TODO: Add data binding node -->

    <data>

        <variable
            name="viewModel"
            type="com.yourPackageName.ViewModel" />

    </data>

    <ImageView
            android:id="@+id/statusImageView"
            android:layout_width="192dp"
            android:layout_height="192dp"
            app:apiLoadingStatus="@{viewModel.status}"

基本上,要使此代码正常工作,您需要启用 Databinding 并在 gradle 文件中添加对 ViewModel/LiveData 的依赖项。

您还可以编写另一个 BindingAdapter 来将片段的视图可见性从不可见更改为可见。

【讨论】:

  • 因此,您的建议只是添加另一个(第三个)StateFlow/LiveDate 来观察并将可见性从不可见切换到可见。但是使用这种方法,无论是否完成 draw1 和 draw2,可见性都可能会发生变化。
猜你喜欢
  • 2020-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-08
相关资源
最近更新 更多