【问题标题】:is observeForever lifecycle aware?observeForever 生命周期感知吗?
【发布时间】:2020-04-08 21:46:14
【问题描述】:

我正在使用 MVVM,并且我已经对其进行了不同的实现,但我仍然怀疑的一件事是如何在不将任何生命周期附加到 ViewModel 的情况下从我的 ViewModel 从存储库 (Firebase) 获取数据.

我已经从 ViewModel 实现了observeForever(),但我认为这不是一个好主意,因为我认为我应该使用回调或转换从我的存储库与我的 ViewModel 进行通信。

我在这里留下一个示例,我从 Firebase 获取设备并更新我的 UI,如果我们可以在这里看到,我正在观察来自 UI 的 repo 的数据,但我也在观察来自 ViewModel 的数据从回购,这里是我真的怀疑我是否使用了正确的方法,因为我不知道如果我的视图被破坏,observeForever() 是否会在onCleared() 上被清除,所以它不会保留如果视图死亡,观察者还活着。

用户界面

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val deviceId = editText.text.toString().trim()
            observeData(deviceId)
        }
    }

    fun observeData(deviceId:String){
        viewModel.fetchDeviceData(deviceId).observe(this, Observer {
            textView.text = "Tipo: ${it.devType}"
        })

视图模型

class MainViewmodel: ViewModel() {

    private val repo = Repo()
    fun fetchDeviceData(deviceId:String):LiveData<Device>{
        val mutableData = MutableLiveData<Device>()
        repo.getDeviceData(deviceId).observeForever {
            mutableData.value = it
        }

        return mutableData
    }
}

存储库

class Repo {

    private val db = FirebaseDatabase.getInstance().reference
    fun getDeviceData(deviceId:String):LiveData<Device>{
        val mutableData = MutableLiveData<Device>()
        db.child(deviceId).child("config/device").addListenerForSingleValueEvent(object: ValueEventListener{

            override fun onDataChange(dataSnapshot: DataSnapshot) {
                    val device = dataSnapshot.getValue(Device::class.java)
                    mutableData.value = device
            }

            override fun onCancelled(dataError: DatabaseError) {
                Log.e("Error","handle error callback")
            }
        })

        return mutableData
    }
}

这个例子只是展示了如何从 Firebase 获取设备,它可以工作,但是从我的 ViewModel 中,它一直让我觉得 observeForever() 不是我在寻找在存储库与 ViewModel 之间进行数据通信的东西。

我见过Transformations,但在这种情况下,我只需要将整个 Device 对象传递到我的 UI,因此我不需要将要检索的对象转换为另一个对象

这里应该有什么正确的方法来正确地传达存储库和 ViewModel?

【问题讨论】:

  • 为什么需要Repository层? ViewModel + LiveData 不提供所有这些。您甚至可以将viewModelScope.launch 用于结构化并发。
  • 我曾经有不同的模块来构建我的应用程序,因为存储库应该与数据层通信,为了不编写更多代码,我将这个示例缩小了一些,但我正在寻找的是如何在不从视图模型中永久观察的情况下与其他层交互
  • @IgorGanapolsky 这就是 MVVM 的想法
  • @MasterZzzingKhmer_Cambodia MVVM 不需要存储库层。
  • @IgorGanapolsky 是的,没有必要,但它可以保持代码干净。这是一个单一的责任原则。

标签: android kotlin mvvm android-livedata android-architecture-components


【解决方案1】:

observeForever 生命周期感知吗?

不,这就是为什么它被称为observe永远

我已经从 ViewModel 实现了 observeForever(),但我认为这不是一个好主意

不,不是,你应该使用Transformations.switchMap {

因为我不知道如果我的视图被销毁,observeForever() 是否会在 onCleared() 上被清除,所以如果视图死亡,它不会让观察者保持活动状态。

如果没有在onCleared() 中使用removeObserver(observer) 清除它,那么它不会自行清除,因为它会一直观察 /em>。

我真的怀疑我是否使用了正确的方法,

不,采用被动式方法,您可以做得比这更好。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    button.setOnClickListener {
        val deviceId = editText.text.toString().trim()
        viewModel.onSelectedDeviceChanged(deviceId)
    }

    viewModel.selectedDevice.observe(this, Observer { device ->
        textView.text = "Tipo: ${device.devType}"
    })
}

class MainViewModel(
    private val savedStateHandle: SavedStateHandle,
): ViewModel() {
    private val repo = Repo() // TODO: move to Constructor Argument with ViewModelProvider.Factory

    private val selectedDeviceId: MutableLiveData<String> = savedStateHandle.getLiveData<String>("selectedDeviceId")

    fun onSelectedDeviceChanged(deviceId: String) {
        selectedDeviceId.value = deviceId
    }

    val selectedDevice = Transformations.switchMap(selectedDeviceId) { deviceId ->
        repo.getDeviceData(deviceId)
    }
}

class Repo {
    private val db = FirebaseDatabase.getInstance().reference // TODO: move to constructor arg? Probably

    fun getDeviceData(deviceId:String) : LiveData<Device> {
        return object: MutableLiveData<Device>() {
            private val mutableLiveData = this

            private var query: Query? = null
            private val listener: ValueEventListener = object: ValueEventListener {
                override fun onDataChange(dataSnapshot: DataSnapshot) {
                    val device = dataSnapshot.getValue(Device::class.java)
                    mutableLiveData.value = device
                }

                override fun onCancelled(dataError: DatabaseError) {
                    Log.e("Error","handle error callback")
                }
            }

            override fun onActive() {
                query?.removeEventListener(listener)
                val query = db.child(deviceId).child("config/device")
                this.query = query
                query.addValueEventListener(listener)
            }
    
            override fun onInactive() {
                query?.removeEventListener(listener)
                query = null
            }
        }
    }
}

通过这种方式,您可以使用 LiveData 观察 Firebase 中所做的更改(并因此收到有关您的值的未来更改的通知),而不是只执行一次提取,然后不知道其他地方对相同数据所做的更改.

【讨论】:

  • 我已经使用调试器来更好地理解它是如何工作的,基本上当我们使用 selectedDeviceId 将数据推送到转换内部时,如果至少有一个观察者到 selectedDevice ,它将触发转换,因为转换返回一个新的 MutableLiveData 对象作为 Device ,每次我们使用新的 deviceId 触发新操作时,它将传递给 onInactive,分离当前观察者并使用新值启动新观察者,而不保留第一个观察者的引用
  • 我看到您已关闭this 问题。是的,几乎是一对一的。顺便说一句,“史诗”的答案,投票赞成;)
  • 我有一个问题,如果有一个观察者对源观察者的插拔没有外部回调(LifeCycle.State),那么什么分离观察者的观察者...的观察者(我相信有第四个观察者,因为每个回调有 2 个)?
  • 如果我想继续VM中的数据怎么办?如果我有几个操作,而不是流程中涉及的单个数据?
  • 然后使用switchMap
【解决方案2】:

要使用 ObserveForever,您需要在 ViewModel 的 onClear 中移除观察者。

在这种情况下,我建议您使用 Transformation,即使您只需要一个直接映射而不对数据进行任何处理,这实际上与您对 observerForever 的观察者所做的相同。

【讨论】:

  • 关于 observeForever() 生命周期,它是附加到视图生命周期还是在视图模型中有自己的生命周期?你有什么例子吗?
  • 请注意,使用observerForever时,没有附加生命周期所有者。 observeForever(Observer) 被视为始终处于活动状态,因此将始终收到有关修改的通知。对于这些观察者,您应该手动调用 removeObserver。在此处阅读更多内容developer.android.com/reference/android/arch/lifecycle/LiveData
  • 还有一个问题,这是否被视为反模式?还是这样用没问题?
  • 不客气。我不会说这是一种反模式。在这种情况下,它恰好是直接的数据传递(尽管这在大型项目中很常见)。事实上,使用observerForever 可能更容易出错,因为您可能会忘记删除观察者。如果你想在 repo 和 viewmodel 之间使用 LiveData,Transformations 是一个不错的工具。
【解决方案3】:

observeForever() 不支持生命周期,将继续运行直到调用 removeObserver()。 在您的 ViewModel 中执行此操作,

class MainViewmodel: ViewModel() {

    private val repo = Repo()
    private var deviceData : LiveData<Device>? = null
    fun fetchDeviceData(deviceId:String):LiveData<Device>{
        deviceData = repo.getDeviceData(deviceId)
        return deviceData!!
    }
}

【讨论】:

  • 如果我没记错的话,ViewModel 应该保留缓存的值,而不是直接返回 LiveData 的新实例。否则,ViewModel 不会在配置更改时缓存数据。
  • 此解决方案不允许继续 VM 中的数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-17
  • 1970-01-01
相关资源
最近更新 更多