Flow是否像LiveData一样具有生命周期感知能力?

6
我们知道,LiveData是有生命周期意识的。如果发生配置更改,LiveData对象不会每次都重新从数据库(本地/远程)查询数据,只有在数据有更新时才会更新。

最近我开始使用Kotlin Flow,我必须承认它非常适合用于数据层,即在repo中实现,以便能通知ViewModel。但我也在ViewModel / View层中使用了Kotlin Flow,以便可以根据其状态(sealed class实现)直接在Fragment中collect Flow对象。这种使用Flow的问题是,每次发生配置更改时都会从数据库(本地/远程)检索数据。
在这种情况下应该怎么做? 是否有一种方法可以避免在使用Flow时重新查询,还是应该只在ViewModel / View层中使用LiveData?
示例代码:
sealed class Status<T> {
class Processing<T> : Status<T>()
data class Completed<T>(val value: T) : Status<T>()
data class Error<T>(val error: String) : Status<T>()

companion object {
    fun <T> processing() = Processing<T>()
    fun <T> completed(value: T) = Completed(value)
    fun <T> error(error: String) = Error<T>(error)
    }
}

代码库:

class Repo(database: LocalDatabase){
     fun retrieveUsersData() = flow<Status<List<Users>>>{
    
      emit(Status.processing())

       database.dao.getUsers().collect{
            // Assume db queries successfully and returns list of users
            emit(Status.completed(it))
       }
     }.catch {
            emit(Status.error(it.error.toString()))

     }.flowOn(Dispatchers.IO)
}

ViewModel:

class MyViewModel(application: Application): AndroidViewModel(application) 
{

      private val db = LocalDatabase.getInstance(application)
      private val repo = Repo(db)

      val usersData= repo.retrieveUsersData(dataCollectionType)
}

片段:

class UsersFragment: Fragment(){

   override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
   ): View? {
     
    // Assume all the declarations are done properly
      coroutineScope.launch {
          retrieveUsersData()
      }

   }

   private suspend fun retrieveUsersData(){

        viewModel.retrieveUsersData().collect{ status ->
        // Based on the Status do the actions.
       // When Status is Completed set the data to the adapter.
       }
  }

} // end of Fragment

https://www.youtube.com/watch?v=B8ppnjGPAGE - m0skit0
感谢@m0skit0的即时回复,我会查看一下并告诉您是否解决了问题。 - Kavin Raju S
4个回答

6

实际上有一个asLiveData()函数可以将Flow转换为LiveData。你可以将viewModelScope.coroutineContext和Flow连接起来,这样Flow就会遵循ViewModel的生命周期。以下是基于你的代码的示例。

class MyViewModel(application: Application): AndroidViewModel(application) 
{

      private val db = LocalDatabase.getInstance(application)
      private val repo = Repo(db)

      val usersData = repo.retrieveUsersData(dataCollectionType).asLiveData(viewModelScope.coroutineContext)
}

接着,在视图(activity/fragment)中像往常一样观察这个LiveData。当ViewModel被销毁时,这个转换成LiveData的Flow也会被销毁。

viewModel.userData.observe(viewLifecycleOwner, { status ->
   // Based on the Status do the actions.
   // When Status is Completed set the data to the adapter.
 }

在最佳实践中,

  • Repo <-> ViewModel -- Flow(流)
  • ViewModel <-> View -- LiveData(生命周期感知型数据)

那么如何移除观察者? - Keyvan Norouzi
观察者将根据viewModel的生命周期被销毁/移除。而viewModel的生命周期遵循Activity的生命周期,这意味着当Activity被销毁时,该观察者也会被销毁。 - Teo

1

可能有点晚了,但是有人可能会发现这个答案很有用。 如果您在ViewModel中使用LiveData,则LiveData会在旋转时缓存数据, 并且相同的数据将被填充到视图中,因此不会对数据源进行新的调用。

而如果您使用普通Flow,默认情况下它没有缓存功能,因此在旋转时它将调用数据源。 我猜这就是您的情况所发生的事情。


ViewModel 不是应该保存状态吗? - ir2pid
@ir2pid ViewModel 可以在配置更改后保留状态,但保存状态需要通过 LiveData 或 Flow 进行。 - ferraro

0

我最近在一个使用MVI架构实现的代码库上工作,所以根据你的要求,我强烈建议你使用Flow进行收集。

addRepeatingJob(Lifecycle.State.STARTED) {
        viewModel.viewState.collect(::render)
    }

render 是一个基于状态来通信和更新UI的函数。其中,viewState 是一个 StateFlow<Status<List>>,在viewModel中处理数据响应并根据检索到的数据更新UI状态。

你也可以添加一个 dummyState 以处理隐藏视图或意外关闭。

请查看 https://github.com/DwanZ/archMigrationExample/blob/mvi/app/src/main/java/com/example/archmigrationexample/view/home/ui/HomeViewModel.kt

希望这会有用。


0
据我所知,如果不从视图模型中包装流程,您无法做到这一点。在您的示例中,ViewModel没有保留任何真正的状态,只是转发了流程,因此您不会从其在配置更改后仍然存在中获得任何好处。
针对您的用例,建议采用将流程映射到视图模型内部的liveData架构。m0skit0提到的链接中的视频(https://www.youtube.com/watch?v=B8ppnjGPAGE)展示了lifeData builder的使用方法。
在您的情况下,这将非常简单:
class MyViewModel(application: Application): AndroidViewModel(application) 
{
  private val db = LocalDatabase.getInstance(application)
  private val repo = Repo(db)

  val usersData = liveData {
    emit(repo.retrieveUsersData(dataCollectionType))
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接