jack_the_beast
jack_the_beast

Reputation: 1971

Android Architecture Components: using ViewModel for RecyclerView items

I'm experimenting with the Architecture Components, and I want to build a ViewModel for each item of a RecyclerView. I'm not sure if that is formally correct or I should stick with the "old way".

I have this adapter:

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();
    }
}

which works fine but it's very basic. how do I update it so each item has it's own ViewModel associated? is that even possible?

EDIT: playing with it, I've tried to put in ViewModels the following way:

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();
    }
}

it works but is that the correct way to do it?

Upvotes: 36

Views: 29973

Answers (3)

Dan Riza
Dan Riza

Reputation: 467

Make sure you assign an unique identifier when getting the ViewModel, because under the hood the ViewModelProviders will give you the same instance otherwise

get(some unique id, GalleryItemViewModel.class);

So please try adding an id there, like below :

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

Upvotes: 4

Thracian
Thracian

Reputation: 66536

First of all correct implementation of ViewModel should be by extending android.arch.lifecycle.ViewModel. The examples that extending BaseObservable which makes ViewModel class a data class but it should be presentation class since it's replacing the presenter of MVP pattern.

Another thing is ViewModelProviders.of(context).get(Class.class) returns the same ViewModel for every call and it lets you to share same data between views.

Also, ViewModel class should not, or contain minimum classes from Android environment and should not keep any references to view classes since it may outlive the views.

In your second example you probably getting same ViewModel from Activity/Fragment using

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

Can you share layout file and how you implement this with ViewModel class?

Link for sample in accepted answer is not a correct example for MVVM and data binding.

ViewModel class from the link set as you second example:

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));
    }

}

This is a data class, not an ViewModel class as architecture components page states, it also imports view classes which is bad for unit testing.

It's data binding + RecyclerView tutorial, correct naming should not be ..ViewModel for this class. Check out this tutorial for data class and binding it with RecyclerView.

Upvotes: 6

Stanislav Bondar
Stanislav Bondar

Reputation: 6245

Funny, but answer - This is correct way, should be accepted :) You can make some code clean-up and remove GalleryItemViewModel from PostViewHolder, because you are creating hard reference and not using it. Then dirrectly in onBindViewHolder() use it like holder.binding.setViewModel(vm);

This is a link with MVVM code example that can help you.

Upvotes: 10

Related Questions