【问题标题】:View models for RecyclerView items查看 Recycler 的模型查看项目
【发布时间】:2020-08-05 11:01:05
【问题描述】:

我的活动有一个谷歌的 ViewModel 来获取一些模型项目。然后将这些项目转换为 RecyclerView 的适配器项目。一个 RecyclerView 支持的适配器项也有很多种。

我想为这些模型对象中的每一个拥有单独的视图模型对象,以便我可以将更复杂的逻辑封装在那个“小”视图模型中。

目前,当我有一些仅与某些适配器项相关的异步逻辑(需要在 onCleared() 中停止)时,我必须以某种方式通过主视图模型路由回调,以便正确取消注册所有内容。

我正在考虑使用ViewModelProvider::get(key, modelClass),但我的物品会随着时间的推移而变化,我找不到“清除”旧物品的好方法。

您是如何在项目中处理这些案例的?

编辑:添加更多关于我的关注的信息,也许用不同的词:我希望我的“小”视图模型与它所代表的模型项一样长。这意味着:

  • 我必须在这些项目的父项接收的相同场景中接收 onCleared() 回调
  • 当项目不再存在时,我必须接收 onCleared() 回调

编辑:请尝试将其与以片段作为项目的 ViewPager 进行比较。每个单独的模型项都表示为带有其 ViewModel 的片段。我想实现类似的东西,但对于 RecyclerView。

【问题讨论】:

  • 我建议不要对每个项目使用 ViewModel,因为 ViewModel 基本上连接到 Activity/Fragment 生命周期,如果你尝试将它绑定到每个项目,它会破坏这个目的(它将在尽管整个回收器视图中的上下文是相同的项目)除此之外,您可以将该 VM 公开给您的适配器的实时数据,并通过生命周期所有者参考在您的适配器或 ViewHolder 中观察它。
  • 抱歉,我无法理解您的担忧。你能用不同的方式描述它吗?
  • RecyclerView 项目不是 ViewModelStoreOwners,您可能不希望它们成为其中之一。 ViewModel 不是视图模型,它是跨配置更改的数据缓存。您绝对不需要为每个 recyclerview 项目提供一个。

标签: android android-recyclerview android-architecture-components android-viewmodel


【解决方案1】:

androidx.lifecycle.ViewModel's 并不意味着默认用于 RecyclerView 项目

为什么?

ViewModel is AAC(Android 架构组件),其唯一目的是在 Android Activity/Fragment 生命周期的配置更改中幸存下来,以便可以通过 ViewModel 持久化数据以用于此类案例。

这是通过在与托管活动相关的存储中缓存 VM 实例来实现的。

这就是为什么它不应该在 RecyclerView (ViewHolder) Items 直接上使用,因为 Item View 本身将是 Activity/Fragment 的一部分,它 ( RecyclerView/ViewHolder) 不包含任何特定的 API 来提供ViewModelStoreOwner(对于给定的 Activity/Fragment 实例,ViewModel 基本上是从中派生出来的)

获取 ViewModel 的简单语法是:

ViewModelProvider(this).get(ViewModel::class.java)

& 这里 this 将被称为 Activity/Fragment 上下文。

因此,即使您最终在 RecyclerView 项目中使用 ViewModel,它也会给您相同的实例,因为上下文可能是 Activity/Fragment 在 RecyclerView 中是相同的,这对我来说没有意义。所以 ViewModel 对 RecyclerView 没用,或者对这种情况贡献不大。


TL;DR

解决方案?

您可以在RecyclerView.Adapter 类中直接从您的Activity/Fragment 的ViewModel 中传入您需要观察的LiveData 对象。您还需要提供 LifecycleOwner 以便适配器开始观察给定的实时数据。

所以您的 Adapter 类如下所示:

class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
    
    init {
        liveDataToObserve.observe(lifecycleOwner) { t ->
            // Notify data set or something...
        }
    }

}

如果不是这种情况,并且您希望在 ViewHolder 类上使用它,那么您可以在 onCreateViewHolder 方法期间将您的 LiveData 对象与 lifecycleOwner 一起传递给您的 ViewHolder 实例。

加分!

如果您在 RecyclerView 项目上使用数据绑定,那么您可以轻松地从绑定类中获取 lifecyclerOwner 对象。您需要做的就是在onCreateViewHolder() 期间设置它,如下所示:

class RecyclerViewAdapter(private val liveDataToObserve: LiveData<T>, private val lifecycleOwner: LifecycleOwner) : RecyclerView.Adapter<ViewHolder>() {
    
    override fun onCreateViewHolder: ViewHolder {
        // Some piece of code for binding
        binding.lifecycleOwner = this@RecyclerViewAdapter.lifecycleOwner
        // Another piece of code and return viewholder
    }

}

class ViewHolder(private val someLiveData: LiveData<T>, binding: ViewDataBinding): RecyclerView.ViewHolder(binding.root) {
    
    init {
        someLiveData.observe(requireNotNull(binding.lifecycleOwner)) { t->
            // set your UI by live data changes here
        }
    }
}

所以,是的,您可以为您的 ViewHolder 实例使用包装器类来为您提供开箱即用的 LiveData,但如果包装器类扩展 ViewModel 类,我会不鼓励这样做。

一旦担心模仿ViewModelonCleared() 方法,您可以在包装类上创建一个方法,当ViewHolder 通过方法onViewRecycled()onViewDetachedFromWindow() 被回收或从窗口分离时调用最适合您的情况。


编辑评论@Mariusz:关于使用Activity/Fragment作为LifecycleOwner的担忧是正确的。但将其解读为 POC 可能会有些许误解。

只要使用lifecycleOwner 观察给定RecyclerViewHolder 项目中的LiveData,就可以这样做,因为LiveData 是生命周期感知组件,它在内部处理对生命周期的订阅,因此可以安全使用。即使您可以使用onViewRecycled()onViewDetachedFromWindow() 方法显式删除观察。

关于ViewHolder内部的异步操作:

  1. 如果您正在使用协程,那么您可以使用来自lifecycleOwnerlifecycleScope 来调用您的操作,然后将数据提供给特定的观察LiveData,而无需明确处理清除案例LifecycleScope 需要为你照顾它)

  2. 如果不使用协程,那么您仍然可以进行异步调用并将数据提供回观察 LiveData 并且不必担心在 onViewRecycled()onViewDetachedFromWindow() 回调期间清除异步操作。这里重要的是LiveData,它尊重给定LifecycleOwner 的生命周期,而不是正在进行的异步操作。

【讨论】:

  • 感谢您的回答。二想解决两件事:1)这个输入LifecycleOwner的来源是什么?活动/片段?如果是,那么我不确定这是否是我们应该观察的正确 Lifecycle 对象。请注意,使用这种方法,即使项目被滚动出屏幕,生命周期仍处于 RESUMED 状态 2) 使用 onViewRecycled() 或 onViewDetachedFromWindow() 将使我的异步操作在给定项目的配置更改时停止,而我不会不想那样做。
  • @Mariusz 编辑了帖子,请检查一次,如果它消除了您的疑虑,请告诉我。
  • 感谢您的更新。我看到您建议将异步操作范围限定为生命周期(所有者)或 onViewRecycled/DetachedFromWindow(),但我希望即使在配置更改时也能继续我的异步操作,并且根据我对您评论的理解,它无法实现。
  • 嗯.. 视图模型是公开属性和命令的视图的抽象 (wiki)。让我们想象一下,第二天我们需要创建一个动态列表视图,之前是一整套带有自己视图模型的不同屏幕,然后它们的数量乘以 10 倍,每个屏幕都有自己的状态。例如,像一些带有日历的富卡列表/网格视图媒体新闻仪表板。我认为 ViewModel 旨在用于我们需要对视图进行抽象的所有地方_
  • 我同意,我一点也不介意。通常,当有人在 android 中询问 MVVM 或视图模型时,他们使用的是 AndroidX 提供的库,这可能会导致 IMO 如此混乱。
【解决方案2】:

虽然 Android 在 Android 架构组件 中使用 ViewModels 是事实,但这并不意味着它们只是 AAC 的一部分。实际上,ViewModelsMVVM Architecture Pattern 的组件之一,它不仅仅与 Android 相关。因此,ViewModel 的实际目的是在 Android 的生命周期更改中保存数据。但是,由于在没有 View 引用的情况下公开其数据,因此非常适合 Android 特定情况,在这种情况下,可以重新创建 View 而不会影响保存的组件它的状态(ViewModel)。尽管如此,它还有其他好处,例如促进关注点分离

同样重要的是要提到,与 ViewPager-Fragments 案例相比,您的案例不能 100%,因为主要区别在于 ViewHolders 将是 在项目之间回收。即使 ViewPagerFragment 被销毁并重新创建,它们仍将代表具有相同数据的相同 Fragment。这就是为什么他们可以安全地绑定他们已经存在的ViewModel 提供的数据。但是,在 ViewHolder 的情况下,当它被重新创建时,它可能代表一个全新的项目,因此它假定的 ViewModel 可能提供的数据可能不正确,引用了旧项目。

话虽如此,您可以轻松地将ViewHolder 变成ViewModelStoreOwner

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), ViewModelStoreOwner {

    private var viewModelStore: ViewModelStore = ViewModelStore()

    override fun getViewModelStore(): ViewModelStore = viewModelStore
}

如果ViewModel 提供的数据与ViewHolder 的项目(所有项目之间的共享状态)无关,这仍然很有用。但是,如果不是这种情况,那么您需要通过调用viewModelStore.clear() 使ViewModelStore 无效,并可能在ViewHolderonViewRecycled 中创建一个新的ViewModel 实例。无论视图生命周期如何,您都会失去保持状态的优势,但有时仍然可以遵循关注点分离 .

最后,关于使用LiveData实例控制状态的选项,无论是由ViewHolder的共享或特定ViewModel提供还是通过Adapter传递,你需要LifecycleOwner 来观察它。使用当前FragmentActivity 生命周期的更好方法是仅使用特定ViewHolder 的实际生命周期,因为它们实际上是创建和销毁的,方法是使它们实现LifecycleOwner 接口。我创建了一个小的library 正是这样做的。

【讨论】:

  • 感谢您的回答。 1)你是对的,我与 ViewPager 的比较不正确 - Fragment 需要创建自己的 ViewModel。 2)关于您的答案中的 ViewModel 和 ViewModel - 我的项目的 ViewModel 应该与其父项位于同一个 ViewModelStore 中。我不希望在 onViewRecycled() 之类的方法中调用 onCleared(),因为即使视图不可见,我仍可能希望保持一些长时间运行的操作或其状态。 3)关于您的库 - 它不处理视图 ViewHolder 的 Activity/Fragment 主机暂停的场景。
【解决方案3】:

不知道谷歌是否对嵌套 ViewModel 有很好的支持,看起来没有。 值得庆幸的是,我们不需要坚持androidx.lifecycle.ViewModel 在我们需要的地方应用 MVVM 方法。我决定写一个小例子:

片段,没有任何变化:

    @Override public void onCreate(@Nullable Bundle savedInstanceState) {
        final ItemListAdapter adapter = new ItemListAdapter();
        binding.getRoot().setAdapter(adapter);

        viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
        viewModel.getItems().observe(getViewLifecycleOwner(), adapter::submitList);
    }

ItemListAdapter,除了填充视图之外,它还负责通知项目的观察者——他们是否继续收听。在我的示例适配器中,ListAdapter 扩展了 RecyclerView.Adapter,因此它接收项目列表。这是无意的,只是编辑了一些我已经拥有的代码。使用不同的基本实现可能要好得多,但出于演示目的是可以接受的:

    @Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new Holder(parent);
    }

    @Override public void onBindViewHolder(Holder holder, int position) {
        holder.lifecycle.setCurrentState(Lifecycle.State.RESUMED);
        holder.bind(getItem(position));
    }

    @Override public void onViewRecycled(Holder holder) {
        holder.lifecycle.setCurrentState(Lifecycle.State.DESTROYED);
    }

    // Idk, but these both may be used to pause/resume, while bind/recycle for start/stop.
    @Override public void onViewAttachedToWindow(Holder holder) { }
    @Override public void onViewDetachedFromWindow(Holder holder) { }

Holder. 它实现了 LifecycleOwner,它允许自动取消订阅,只是从 androidx.activity.ComponentActivity 源复制的,所以一切都应该没问题:D:

static class Holder extends RecyclerView.Holder implements LifecycleOwner {

    /*pkg*/ LifecycleRegistry lifecycle = new LifecycleRegistry(this);

    /*pkg*/ Holder(ViewGroup parent) { /* creating holder using parent's context */ }

    /*pkg*/ void bind(ItemViewModel viewModel) {
        viewModel.getItem().observe(this, binding.text1::setText);
    }

    @Override public Lifecycle getLifecycle() { return lifecycle; }
}

List view-model,"classique" androidx-ish ViewModel,但是很粗糙,也提供嵌套视图模型。请注意,在此示例中,所有视图模型在构造函数中立即开始运行,直到命令清除父视图模型!不要在家里尝试这个!

public class ItemListViewModel extends ViewModel {

    private final MutableLiveData<List<ItemViewModel>> items = new MutableLiveData<>();

    public ItemListViewModel() {
        final List<String> list = Items.getInstance().getItems();

        // create "nested" view-models which start background job immediately
        final List<ItemViewModel> itemsViewModels = list.stream()
                .map(ItemViewModel::new)
                .collect(Collectors.toList());

        items.setValue(itemsViewModels);
    }

    public LiveData<List<ItemViewModel>> getItems() { return items; }

    @Override protected void onCleared() {
        // need to clean nested view-models, otherwise...
        items.getValue().stream().forEach(ItemViewModel::cancel);
    }
}

Item 的视图模型,使用一点 rxJava 来模拟一些后台工作和更新。我故意不将其实现为androidx....ViewModel,只是为了强调视图模型不是谷歌命名的视图模型,而是表现为视图模型。但在实际程序中,它很可能会扩展:

// Wow, we can implement ViewModel without androidx.lifecycle.ViewModel, that's cool!
public class ItemViewModel {

    private final MutableLiveData<String> item = new MutableLiveData<>();

    private final AtomicReference<Disposable> work = new AtomicReference<>();

    public ItemViewModel(String topicInitial) {
        item.setValue(topicInitial);
        // start updating ViewModel right now :D
        DisposableHelper.set(work, Observable
            .interval((long) (Math.random() * 5 + 1), TimeUnit.SECONDS)
                    .map(i -> topicInitial + " " + (int) (Math.random() * 100) )
                    .subscribe(item::postValue));
    }

    public LiveData<String> getItem() { return item; }

    public void cancel() {
        DisposableHelper.dispose(work);
    }

}

本示例中的一些注释:

  • “父”视图模型存在于活动范围内,因此它的所有数据(嵌套视图模型)也是如此。
  • 在本例中,所有嵌套的虚拟机立即开始运行。这不是我们想要的。我们要相应地修改构造函数、onBind、onRecycle 和相关方法。
  • 请对内存泄漏进行测试。

【讨论】:

  • 感谢您的回答!您的回答几乎可以解决我的问题。我认为您唯一错过的是当其 Activity 处于后台(但仍处于活动状态)时,Holder 的生命周期仍处于 RESUMED 状态,但可以通过额外的一组回调来改进它。
  • 感谢您的注意:tbh,我没有检查它。但如果是这种情况,请查看onViewDetachedFromWindow - 它可能会有所帮助。
  • 不相关:如果有人好奇 LiveData 如何依赖于生命周期 - 请查看 this 来源。
【解决方案4】:

我通过 aeracode 遵循了这个奇妙的答案 HERE,但有一个例外。而不是ViewModel,我使用了非常适合我的Rx BehaviourSubject。 在协程的情况下您可以使用StateFlow

clas MyFragment: Fragment(){

   private val listSubject = BehaviorSubject.create<List<Items>>()
   ...
   private fun observeData() {
        viewModel.listLiveData.observe(viewLifecycleOwner) { list ->
            listSubject.onNext(list)
        }
   }
}

回收站视图

class MyAdapter(
    private val listObservable: BehaviorSubject<List<Items>>
) : RecyclerView.Adapter<MyViewHolder>() {
   [...]
   override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.bindToData(getItem(position))
    }

    override fun onViewRecycled(holder: MyViewHolder) {
        holder.onViewRecycled()
    }
    ...
    class MyViewHolder(val binding: LayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {

        private var disposable: Disposable? = null

        fun bindToData(item: Item) = with(binding) {
            titleTv.text = item.title
            disposable = listObservable.subscribe(::setItemList) <- Here You listen
        }

        fun onViewRecycled() {
            disposable?.dispose()
        }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-28
    • 2019-05-07
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 2019-06-27
    • 2016-12-31
    相关资源
    最近更新 更多