NestedAdapterWrapper.java

/*
 * Copyright 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.recyclerview.widget;

import static androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY;

import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Preconditions;
import androidx.recyclerview.widget.RecyclerView.Adapter;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

/**
 * Wrapper for each adapter in {@link ConcatAdapter}.
 */
class NestedAdapterWrapper {
    @NonNull
    private final ViewTypeStorage.ViewTypeLookup mViewTypeLookup;
    @NonNull
    private final StableIdStorage.StableIdLookup mStableIdLookup;
    public final Adapter<ViewHolder> adapter;
    @SuppressWarnings("WeakerAccess")
    final Callback mCallback;
    // we cache this value so that we can know the previous size when change happens
    // this is also important as getting real size while an adapter is dispatching possibly a
    // a chain of events might create inconsistencies (as it happens in DiffUtil).
    // Instead, we always calculate this value based on notify events.
    @SuppressWarnings("WeakerAccess")
    int mCachedItemCount;

    private RecyclerView.AdapterDataObserver mAdapterObserver =
            new RecyclerView.AdapterDataObserver() {
                @Override
                public void onChanged() {
                    mCachedItemCount = adapter.getItemCount();
                    mCallback.onChanged(NestedAdapterWrapper.this);
                }

                @Override
                public void onItemRangeChanged(int positionStart, int itemCount) {
                    mCallback.onItemRangeChanged(
                            NestedAdapterWrapper.this,
                            positionStart,
                            itemCount,
                            null
                    );
                }

                @Override
                public void onItemRangeChanged(int positionStart, int itemCount,
                        @Nullable Object payload) {
                    mCallback.onItemRangeChanged(
                            NestedAdapterWrapper.this,
                            positionStart,
                            itemCount,
                            payload
                    );
                }

                @Override
                public void onItemRangeInserted(int positionStart, int itemCount) {
                    mCachedItemCount += itemCount;
                    mCallback.onItemRangeInserted(
                            NestedAdapterWrapper.this,
                            positionStart,
                            itemCount);
                    if (mCachedItemCount > 0
                            && adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) {
                        mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this);
                    }
                }

                @Override
                public void onItemRangeRemoved(int positionStart, int itemCount) {
                    mCachedItemCount -= itemCount;
                    mCallback.onItemRangeRemoved(
                            NestedAdapterWrapper.this,
                            positionStart,
                            itemCount
                    );
                    if (mCachedItemCount < 1
                            && adapter.getStateRestorationPolicy() == PREVENT_WHEN_EMPTY) {
                        mCallback.onStateRestorationPolicyChanged(NestedAdapterWrapper.this);
                    }
                }

                @Override
                public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
                    Preconditions.checkArgument(itemCount == 1,
                            "moving more than 1 item is not supported in RecyclerView");
                    mCallback.onItemRangeMoved(
                            NestedAdapterWrapper.this,
                            fromPosition,
                            toPosition
                    );
                }

                @Override
                public void onStateRestorationPolicyChanged() {
                    mCallback.onStateRestorationPolicyChanged(
                            NestedAdapterWrapper.this
                    );
                }
            };

    NestedAdapterWrapper(
            Adapter<ViewHolder> adapter,
            final Callback callback,
            ViewTypeStorage viewTypeStorage,
            StableIdStorage.StableIdLookup stableIdLookup) {
        this.adapter = adapter;
        mCallback = callback;
        mViewTypeLookup = viewTypeStorage.createViewTypeWrapper(this);
        mStableIdLookup = stableIdLookup;
        mCachedItemCount = this.adapter.getItemCount();
        this.adapter.registerAdapterDataObserver(mAdapterObserver);
    }


    void dispose() {
        adapter.unregisterAdapterDataObserver(mAdapterObserver);
        mViewTypeLookup.dispose();
    }

    int getCachedItemCount() {
        return mCachedItemCount;
    }

    int getItemViewType(int localPosition) {
        return mViewTypeLookup.localToGlobal(adapter.getItemViewType(localPosition));
    }

    ViewHolder onCreateViewHolder(
            ViewGroup parent,
            int globalViewType) {
        int localType = mViewTypeLookup.globalToLocal(globalViewType);
        return adapter.onCreateViewHolder(parent, localType);
    }

    void onBindViewHolder(ViewHolder viewHolder, int localPosition) {
        adapter.bindViewHolder(viewHolder, localPosition);
    }

    public long getItemId(int localPosition) {
        long localItemId = adapter.getItemId(localPosition);
        return mStableIdLookup.localToGlobal(localItemId);
    }

    interface Callback {
        void onChanged(@NonNull NestedAdapterWrapper wrapper);

        void onItemRangeChanged(
                @NonNull NestedAdapterWrapper nestedAdapterWrapper,
                int positionStart,
                int itemCount
        );

        void onItemRangeChanged(
                @NonNull NestedAdapterWrapper nestedAdapterWrapper,
                int positionStart,
                int itemCount,
                @Nullable Object payload
        );

        void onItemRangeInserted(
                @NonNull NestedAdapterWrapper nestedAdapterWrapper,
                int positionStart,
                int itemCount);

        void onItemRangeRemoved(
                @NonNull NestedAdapterWrapper nestedAdapterWrapper,
                int positionStart,
                int itemCount
        );

        void onItemRangeMoved(
                @NonNull NestedAdapterWrapper nestedAdapterWrapper,
                int fromPosition,
                int toPosition
        );

        void onStateRestorationPolicyChanged(NestedAdapterWrapper nestedAdapterWrapper);
    }

}