public final class

StableIdKeyProvider

extends ItemKeyProvider<java.lang.Long>

 java.lang.Object

androidx.recyclerview.selection.ItemKeyProvider<java.lang.Long>

↳androidx.recyclerview.selection.StableIdKeyProvider

Gradle dependencies

compile group: 'androidx.recyclerview', name: 'recyclerview-selection', version: '1.2.0-alpha01'

  • groupId: androidx.recyclerview
  • artifactId: recyclerview-selection
  • version: 1.2.0-alpha01

Artifact androidx.recyclerview:recyclerview-selection:1.2.0-alpha01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.recyclerview:recyclerview-selection com.android.support:recyclerview-selection

Overview

An ItemKeyProvider that provides item keys by way of native stable ids.

The corresponding RecyclerView.Adapter instance must:

  1. Enable stable ids using
  2. Override with a real implementation.

There are trade-offs with this implementation:

  • It necessarily auto-boxes long stable id values into Long values for use as selection keys.
  • It deprives Chromebook users (actually, any device with an attached pointer) of support for band-selection.

See com.example.android.supportv7.widget.selection.fancy.DemoAdapter.KeyProvider in the SupportV7 Demos package for an example of how to implement a better ItemKeyProvider.

Summary

Fields
from ItemKeyProvider<K>SCOPE_CACHED, SCOPE_MAPPED
Constructors
publicStableIdKeyProvider(RecyclerView recyclerView)

Creates a new key provider that uses cached long stable ids associated with the RecyclerView items.

Methods
public abstract java.lang.ObjectgetKey(int position)

public abstract intgetPosition(java.lang.Object key)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public StableIdKeyProvider(RecyclerView recyclerView)

Creates a new key provider that uses cached long stable ids associated with the RecyclerView items.

Parameters:

recyclerView: the owner RecyclerView

Methods

public abstract java.lang.Object getKey(int position)

Returns:

The selection key at the given adapter position, or null.

public abstract int getPosition(java.lang.Object key)

Returns:

the position corresponding to the selection key, or RecyclerView.NO_POSITION if the key is unrecognized.

Source

/*
 * Copyright 2017 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.selection;

import static androidx.core.util.Preconditions.checkArgument;
import static androidx.core.util.Preconditions.checkNotNull;
import static androidx.recyclerview.selection.Shared.DEBUG;

import android.util.Log;
import android.util.SparseArray;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.collection.LongSparseArray;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.OnChildAttachStateChangeListener;
import androidx.recyclerview.widget.RecyclerView.RecyclerListener;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

/**
 * An {@link ItemKeyProvider} that provides item keys by way of native
 * {@link RecyclerView.Adapter} stable ids.
 *
 * <p>The corresponding RecyclerView.Adapter instance must:
 * <ol>
 *     <li> Enable stable ids using {@link RecyclerView.Adapter#setHasStableIds(boolean)}
 *     <li> Override {@link RecyclerView.Adapter#getItemId(int)} with a real implementation.
 * </ol>
 *
 * <p>
 * There are trade-offs with this implementation:
 * <ul>
 *     <li>It necessarily auto-boxes {@code long} stable id values into {@code Long} values for
 *     use as selection keys.
 *     <li>It deprives Chromebook users (actually, any device with an attached pointer) of support
 *     for band-selection.
 * </ul>
 *
 * <p>See com.example.android.supportv7.widget.selection.fancy.DemoAdapter.KeyProvider in the
 * SupportV7 Demos package for an example of how to implement a better ItemKeyProvider.
 */
public final class StableIdKeyProvider extends ItemKeyProvider<Long> {

    private static final String TAG = "StableIdKeyProvider";

    private final SparseArray<Long> mPositionToKey = new SparseArray<>();
    private final LongSparseArray<Integer> mKeyToPosition = new LongSparseArray<>();
    private final ViewHost mHost;

    StableIdKeyProvider(@NonNull ViewHost host) {
        // Provider is based on the stable ids provided by ViewHolders which
        // are only accessible when the holders are attached or yet-to-be-recycled.
        // For that reason we can only satisfy "CACHED" scope key access which
        // limits library features such as mouse-driven band selection.
        super(SCOPE_CACHED);

        checkNotNull(host);
        mHost = host;

        mHost.registerLifecycleListener(
                new ViewHost.LifecycleListener() {
                    @Override
                    public void onAttached(@NonNull View view) {
                        StableIdKeyProvider.this.onAttached(view);
                    }

                    @Override
                    public void onRecycled(@NonNull View view) {
                        StableIdKeyProvider.this.onRecycled(view);
                    }
                }
        );
    }

    /**
     * Creates a new key provider that uses cached {@code long} stable ids associated
     * with the RecyclerView items.
     *
     * @param recyclerView the owner RecyclerView
     */
    public StableIdKeyProvider(@NonNull RecyclerView recyclerView) {
        this(new DefaultViewHost(recyclerView));

        // Adapters used w/ StableIdKeyProvider MUST have StableIds enabled.
        checkArgument(recyclerView.getAdapter().hasStableIds(), "RecyclerView"
                + ".Adapter#hasStableIds must return true.");
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void onAttached(@NonNull View view) {
        ViewHolder holder = mHost.findViewHolder(view);
        if (holder == null) {
            if (DEBUG) {
                Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onAttached event.");
            }
            return;
        }
        int position = mHost.getPosition(holder);
        long id = holder.getItemId();
        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
            mPositionToKey.put(position, id);
            mKeyToPosition.put(id, position);
        }
    }

    @SuppressWarnings("WeakerAccess") /* synthetic access */
    void onRecycled(@NonNull View view) {
        ViewHolder holder = mHost.findViewHolder(view);
        if (holder == null) {
            if (DEBUG) {
                Log.w(TAG, "Unable to find ViewHolder for View. Ignoring onDetached event.");
            }
            return;
        }
        int position = mHost.getPosition(holder);
        long id = holder.getItemId();
        if (position != RecyclerView.NO_POSITION && id != RecyclerView.NO_ID) {
            mPositionToKey.delete(position);
            mKeyToPosition.remove(id);
        }
    }

    @Override
    public @Nullable Long getKey(int position) {
        // TODO: Consider using RecyclerView.NO_ID for consistency w/ getPosition impl.
        // Currently GridModel impl depends on null return values.
        return mPositionToKey.get(position, null);
    }

    @Override
    public int getPosition(@NonNull Long key) {
        return mKeyToPosition.get(key, RecyclerView.NO_POSITION);
    }

    /**
     * A wrapper interface for RecyclerView allowing for easy unit testing.
     */
    interface ViewHost {
        /** Registers View{Holder} lifecycle event listener. **/
        void registerLifecycleListener(@NonNull LifecycleListener listener);

        /**
         * Returns the ViewHolder containing {@code View}.
         */
        @Nullable ViewHolder findViewHolder(@NonNull View view);

        /**
         * Returns the position of the ViewHolder, or RecyclerView.NO_POSITION
         * if unknown.
         *
         * This method supports testing of StableIdKeyProvider independent of
         * a real RecyclerView instance. The correct runtime implementation is
         * {@code return holder.getAbsoluteAdapterPosition}. This implementation
         * depends on a concrete RecyclerView instance, which isn't test friendly
         * given the testing approach in StableIdKeyProviderTest. Thus the
         * introduction of this interface method allowing a test double to
         * supply adapter position as needed to test.
         */
        int getPosition(@NonNull ViewHolder holder);

        /** A View{Holder} lifecycle listener interface. */
        interface LifecycleListener {

            /** Called when view is attached. */
            void onAttached(@NonNull View view);

            /** Called when view is recycled. */
            void onRecycled(@NonNull View view);
        }
    }

    /**
     * Implementation of ViewHost that wraps a RecyclerView instance.
     */
    private static class DefaultViewHost implements ViewHost {
        private final @NonNull RecyclerView mRecyclerView;

        DefaultViewHost(@NonNull RecyclerView recyclerView) {
            checkNotNull(recyclerView);
            mRecyclerView = recyclerView;
        }

        @Override
        public void registerLifecycleListener(@NonNull LifecycleListener listener) {

            mRecyclerView.addOnChildAttachStateChangeListener(
                    new OnChildAttachStateChangeListener() {
                        @Override
                        public void onChildViewAttachedToWindow(@NonNull View view) {
                            listener.onAttached(view);
                        }

                        @Override
                        public void onChildViewDetachedFromWindow(@NonNull View view) {
                            // Cached position <> key data is discarded only when
                            // a view is recycled. See b/145767095 for details.
                        }
                    }
            );

            mRecyclerView.addRecyclerListener(
                    new RecyclerListener() {
                        @Override
                        public void onViewRecycled(@NonNull ViewHolder holder) {
                            listener.onRecycled(holder.itemView);
                        }
                    }
            );
        }

        @Override
        public @Nullable ViewHolder findViewHolder(@NonNull View view) {
            return mRecyclerView.findContainingViewHolder(view);
        }

        @Override
        public int getPosition(@NonNull ViewHolder holder) {
            return holder.getAbsoluteAdapterPosition();
        }
    }
}