chromium/ui/android/java/src/org/chromium/ui/modelutil/SimpleRecyclerViewAdapter.java

// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.ui.modelutil;

import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import org.chromium.ui.modelutil.ListObservable.ListObserver;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;

/**
 * A simple adapter for {@link RecyclerView}. This is the RecyclerView version of the
 * {@link ModelListAdapter} with the API surfaces being identical in terms of managing items in the
 * view. In summary, use {@link #registerType(int, ViewBuilder, ViewBinder)} to tell the list
 * adapter how to display a particular item. Updates to the {@link ListObservable} list provided in
 * the constructor will immediately be reflected in the list.
 */
public class SimpleRecyclerViewAdapter
        extends RecyclerView.Adapter<SimpleRecyclerViewAdapter.ViewHolder>
        implements MVCListAdapter {
    /**
     * A simple {@link ViewHolder} that keeps a view, view binder, and an MCP that relate the two.
     */
    public class ViewHolder extends RecyclerView.ViewHolder {
        /** The model change processor currently associated with this view and model. */
        private PropertyModelChangeProcessor<PropertyModel, View, PropertyKey> mCurrentMcp;

        /** The view binder that knows how to apply a model to the view this holder owns. */
        private ViewBinder<PropertyModel, View, PropertyKey> mBinder;

        /** A handle to the model currently held by this view holder. */
        public PropertyModel model;

        /**
         * @param itemView The view to manage.
         * @param binder The binder that knows how to apply a model to the view.
         */
        public ViewHolder(View itemView, ViewBinder<PropertyModel, View, PropertyKey> binder) {
            super(itemView);
            mBinder = binder;
        }

        /**
         * Set the model for this view holder to manage.
         * @param model The model that should be bound to the view.
         */
        void setModel(PropertyModel model) {
            if (mCurrentMcp != null) mCurrentMcp.destroy();
            this.model = model;
            if (this.model == null) return;
            mCurrentMcp = PropertyModelChangeProcessor.create(model, itemView, mBinder);
        }
    }

    /** The data that is shown in the list. */
    protected final ModelList mListData;

    /** The observer that watches the data for changes. */
    private final ListObserver<Void> mListObserver;

    /** A map of view types to view binders. */
    private final SparseArray<Pair<ViewBuilder, ViewBinder>> mViewBuilderMap = new SparseArray<>();

    public SimpleRecyclerViewAdapter(ModelList data) {
        mListData = data;
        mListObserver =
                new ListObserver<Void>() {
                    @Override
                    public void onItemRangeInserted(ListObservable source, int index, int count) {
                        notifyItemRangeInserted(index, count);
                    }

                    @Override
                    public void onItemRangeRemoved(ListObservable source, int index, int count) {
                        notifyItemRangeRemoved(index, count);
                    }

                    @Override
                    public void onItemRangeChanged(
                            ListObservable<Void> source,
                            int index,
                            int count,
                            @Nullable Void payload) {
                        notifyItemRangeChanged(index, count);
                    }

                    @Override
                    public void onItemMoved(ListObservable source, int curIndex, int newIndex) {
                        notifyItemMoved(curIndex, newIndex);
                    }
                };
        mListData.addObserver(mListObserver);
    }

    /** Clean up any state that needs to be. */
    public void destroy() {
        mListData.removeObserver(mListObserver);
    }

    /** @return The ModelList describing RecyclerView children. */
    public ModelList getModelList() {
        return mListData;
    }

    @Override
    public int getItemCount() {
        return mListData.size();
    }

    @Override
    public <T extends View> void registerType(
            int typeId, ViewBuilder<T> builder, ViewBinder<PropertyModel, T, PropertyKey> binder) {
        assert mViewBuilderMap.get(typeId) == null;
        mViewBuilderMap.put(typeId, new Pair<>(builder, binder));
    }

    @Override
    public int getItemViewType(int position) {
        return mListData.get(position).type;
    }

    /**
     * Create a new view of the desired type.
     *
     * @param parent Parent view.
     * @param typeId Type of the view to create.
     * @return Created view.
     */
    protected View createView(ViewGroup parent, int typeId) {
        return mViewBuilderMap.get(typeId).first.buildView(parent);
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ViewHolder(createView(parent, viewType), mViewBuilderMap.get(viewType).second);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        viewHolder.setModel(mListData.get(position).model);
    }

    @Override
    public void onViewRecycled(ViewHolder holder) {
        holder.setModel(null);
    }
}