Компоненты архитектуры Android: использование ViewModel для элементов RecyclerView

Я экспериментирую с компонентами архитектуры и хочу создать ViewModel для каждого элемента RecyclerView. Я не уверен, является ли это формально правильным, или я должен придерживаться "старого способа".

У меня есть этот адаптер:

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.PostViewHolder> {

    private List<Post> list;
    public static class PostViewHolder extends RecyclerView.ViewHolder{
        final ItemPostBinding binding;

        public PostViewHolder(ItemPostBinding binding){
            super(binding.getRoot());
            this.binding = binding;
        }
    }

    @Override
    public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemPostBinding binding = DataBindingUtil
                .inflate(LayoutInflater.from(parent.getContext()), R.layout.item_post,
                        parent, false);


        return new PostViewHolder(binding, parent.getContext());
    }

    @Override
    public void onBindViewHolder(PostViewHolder holder, int position) {
        holder.binding.setPost(list.get(position));
        holder.binding.executePendingBindings();
    }

    @Override
    public int getItemCount() {
        return list == null ? 0 : list.size();
    }

    public void setList(List<Post> list){
        this.list = list;
        notifyDataSetChanged();
    }
}

который работает хорошо, но это очень просто. как мне обновить его, чтобы у каждого элемента была своя собственная ViewModel? это вообще возможно?

ОБНОВЛЕНИЕ: играя с ним, я попытался вставить в ViewModels следующим образом:

public class PostAdapter extends RecyclerView.Adapter<PostAdapter.PostViewHolder> {

    private List<Post> list;
    public static class PostViewHolder extends RecyclerView.ViewHolder{
        final ItemPostBinding binding;
        private final Context context;
        private GalleryItemViewModel viewModel;

        public PostViewHolder(ItemPostBinding binding, Context context){
            super(binding.getRoot());
            this.binding = binding;
            this.context = context;
        }

        public Context getContext(){
            return context;
        }

        public void setViewModel(GalleryItemViewModel viewModel){
            this.viewModel = viewModel;
            binding.setViewModel(viewModel);
        }
    }

    @Override
    public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ItemPostBinding binding = DataBindingUtil
                .inflate(LayoutInflater.from(parent.getContext()), R.layout.item_post,
                        parent, false);


        return new PostViewHolder(binding, parent.getContext());
    }

    @Override
    public void onBindViewHolder(PostViewHolder holder, int position) {
        GalleryItemViewModel vm = ViewModelProviders.of((FragmentActivity) holder.getContext()).get(GalleryItemViewModel.class);
        vm.setPost(list.get(position));
        holder.setViewModel(vm);
    }

    @Override
    public int getItemCount() {
        return list == null ? 0 : list.size();
    }

    public void setList(List<Post> list){
        this.list = list;
        notifyDataSetChanged();
    }
}

это работает, но это правильный способ сделать это?

Ответ 1

Забавно, но ответ - это правильный путь, должны быть приняты :) Вы можете сделать некоторые код очистки и удаления GalleryItemViewModel из PostViewHolder, потому что вы создаете жесткую ссылку, и она не используется. Затем непосредственно в onBindViewHolder() используйте его как holder.binding.setViewModel(vm);

Это ссылка с примером кода MVVM, которая может вам помочь.

Ответ 2

Прежде всего, правильная реализация ViewModel должна быть расширена android.arch.lifecycle.ViewModel. Примеры, которые расширяют BaseObservable что делает класс ViewModel классом данных, но он должен быть классом представления, поскольку он заменяет ведущего шаблона MVP.

Другое дело - ViewModelProviders.of(context).get(Class.class) возвращает один и тот же ViewModel для каждого вызова и позволяет вам делиться одними и теми же данными между представлениями.

Кроме того, класс ViewModel не должен содержать минимальные классы из среды Android и не должен содержать ссылки на классы, поскольку он может пережить взгляды.

В вашем втором примере вы, вероятно, получаете тот же ViewModel из Activity/Fragment, используя

public void setViewModel(GalleryItemViewModel viewModel){
            this.viewModel = viewModel;
            binding.setViewModel(viewModel);
}

Можете ли вы поделиться файлом макета и как его реализовать с помощью класса ViewModel?

Ссылка на образец в принятом ответе не является правильным примером для MVVM и привязки данных.

Класс ViewModel из набора ссылок в качестве второго примера:

public class CommentHeaderViewModel extends BaseObservable {

    private Context context;
    private Post post;

    public CommentHeaderViewModel(Context context, Post post) {
        this.context = context;
        this.post = post;
    }

    public String getCommentText() {
        return Html.fromHtml(post.text.trim()).toString();
    }

    public String getCommentAuthor() {
        return context.getResources().getString(R.string.text_comment_author, post.by);
    }

    public String getCommentDate() {
        return new PrettyTime().format(new Date(post.time * 1000));
    }

}

Это класс данных, а не класс ViewModel как состояния страницы компонентов архитектуры, он также импортирует классы классов, которые плохо подходят для модульного тестирования.

Это привязка данных + учебник RecyclerView, правильное именование не должно быть.ViewModel для этого класса. Ознакомьтесь с этим руководством по классу данных и привяжите его к RecyclerView.

Ответ 3

Убедитесь, что вы присваиваете уникальный идентификатор при получении ViewModel, потому что под капотом ViewModelProviders даст вам тот же экземпляр в противном случае

get (некоторый уникальный идентификатор, GalleryItemViewModel.class);

Поэтому, пожалуйста, попробуйте добавить идентификатор, как показано ниже:

 GalleryItemViewModel vm = ViewModelProviders.of((FragmentActivity) holder.getContext()).get(**some unique id**, GalleryItemViewModel.class);
    vm.setPost(list.get(position));
    holder.setViewModel(vm);