web-dev-qa-db-fra.com

Composants d'architecture Android: utilisation des éléments ViewModel for RecyclerView

J'expérimente avec les composants d'architecture et je souhaite créer un ViewModel pour chaque élément d'un RecyclerView. Je ne suis pas sûr que ce soit formellement correct ou si je devrais m'en tenir à la "vieille façon".

J'ai cet adaptateur: 

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

ce qui fonctionne bien mais c'est très basique. Comment puis-je le mettre à jour afin que chaque élément ait son propre ViewModel associé? est-ce que c'est possible?

EDIT: en jouant avec, j'ai essayé de mettre dans ViewModels de la manière suivante:

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

cela fonctionne mais est-ce la bonne façon de le faire?

17
jack_the_beast

Drôle, mais répondez - C’est une manière correcte, qui devrait être acceptée:) Vous pouvez effectuer un nettoyage du code et supprimer GalleryItemViewModel de PostViewHolder, car vous créez une référence dure et ne l’utilisez pas . Puis, directement dans onBindViewHolder(). utilisez-le comme holder.binding.setViewModel(vm); 

Ceci est un exemple de code link with MVVM qui peut vous aider.

3
Stanislav Bondar

Tout d’abord, la bonne implémentation de ViewModel devrait être en étendant Android.Arch.lifecycle.ViewModel. Les exemples d'extension BaseObservable qui font de la classe ViewModel une classe de données, mais il doit s'agir d'une classe de présentation, car ils remplacent le présentateur du modèle MVP.

ViewModelProviders.of(context).get(Class.class) renvoie le même ViewModel pour chaque appel et vous permet de partager les mêmes données entre les vues.

En outre, la classe ViewModel ne doit pas contenir de classes minimales de l'environnement Android ou ne doit pas en contenir, et ne doit conserver aucune référence aux classes d'affichage, car elle risquerait de survivre à l'affichage.

Dans votre deuxième exemple, vous obtenez probablement le même ViewModel d’Activity/Fragment en utilisant 

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

Pouvez-vous partager le fichier de présentation et comment vous l'implémentez avec la classe ViewModel?

Le lien pour l'échantillon dans la réponse acceptée n'est pas un exemple correct pour MVVM et la liaison de données.

La classe ViewModel du lien défini comme second exemple:

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

}

Il s'agit d'une classe de données et non d'une classe ViewModel en tant que page de composants d'architecture États, elle importe également des classes de vue, ce qui est mauvais pour les tests unitaires.

Il s'agit de la liaison de données + du didacticiel RecyclerView. Un nom correct ne doit pas être ..Modèle pour cette classe. Consultez ce tutoriel pour la classe de données et le lier avec RecyclerView.

2
Thracian