查看 RecyclerView 项目的模型

Mar*_*usz 15 android android-recyclerview android-viewmodel android-architecture-components

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

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

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

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

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

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

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

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

Jee*_*ede 28

默认情况下,androidx.lifecycle.ViewModel不打算在 RecyclerView 项目上使用

为什么?

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

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

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

获取 ViewModel 的简单语法是:

ViewModelProvider(this).get(ViewModel::class.java)
Run Code Online (Sandbox Code Playgroud)

& 这里this指的是 Activity/Fragment 上下文。

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


TL; 博士

解决方案?

您可以直接传递LiveData对象,你需要从你的活动来观察/片段是ViewModel在你的RecyclerView.Adapter班级。您还需要提供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...
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

如果情况并非如此,并且您希望将它放在ViewHolder类上,那么您可以LiveDataonCreateViewHolder方法期间将对象与lifecycleOwner.

加分点!

如果您在 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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以是的,您可以为您的ViewHolder实例使用包装类来为您提供LiveData开箱即用的服务,但如果包装类正在扩展ViewModel类,我会劝阻它。

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


编辑@Mariusz 的评论:关注使用Activity/Fragment作为LifecycleOwner是正确的。但是将其理解为 POC 会有一些误解。

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

关于里面的异步操作ViewHolder

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

  2. 如果不使用协同程序,那么你仍然可以使你的电话ASYC并提供数据传回观测LiveData和更不用担心在清除异步操作onViewRecycled()onViewDetachedFromWindow()回调。这里重要的是LiveData尊重给定的生命周期LifecycleOwner,而不是正在进行的异步操作。


aer*_*ode 5

不知道谷歌是否对嵌套 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);
    }
Run Code Online (Sandbox Code Playgroud)

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) { }
Run Code Online (Sandbox Code Playgroud)

持有者。它实现了 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; }
}
Run Code Online (Sandbox Code Playgroud)

列出 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Item 的 view-model,使用一些 rxJava 来模拟一些后台工作和更新。我故意不将其实现为androidx....ViewModel,只是为了强调视图模型不是谷歌命名的 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);
    }

}
Run Code Online (Sandbox Code Playgroud)

在此示例中,有几点说明:

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