realm / realm-android-adapters

Adapters for combining Realm Java with Android UI components and framework classes
realm.io
Apache License 2.0
415 stars 135 forks source link

RecyclerView sample #5

Open cmelchior opened 8 years ago

cmelchior commented 8 years ago

We are seeing a number of support issue around RecyclerView and how to handler changelisteners and Realm updates in that case. See e.g https://github.com/realm/realm-java/issues/2408 and https://github.com/realm/realm-java/issues/2650

We should provide a sample using the RecyclerView that could illustrate how to make this work.

Zhuinden commented 8 years ago

I haven't tested this yet, but based on previous incarnations, this sounds about right.

public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
    extends RecyclerView.Adapter<VH> { //put this in `io.realm`

    protected LayoutInflater inflater;
    protected OrderedRealmCollection<T> adapterData;
    protected Context context;
    private final RealmChangeListener listener;

    public RealmRecyclerViewAdapter(Context context, OrderedRealmCollection<T> data) {
        if (context == null) {
            throw new IllegalArgumentException("Context cannot be null");
        }
        this.context = context;
        this.adapterData = data;
        this.inflater = LayoutInflater.from(context);
        this.listener = new RealmChangeListener<RealmResults<T>>() {
            @Override
            public void onChange(RealmResults<T> results) {
                notifyDataSetChanged();
            }
        };

        if (data != null) {
            addListener(data);
        }
    }

    private void addListener(OrderedRealmCollection<T> data) {
        if (data instanceof RealmResults) {
            RealmResults realmResults = (RealmResults) data;
            realmResults.addChangeListener(listener);
        } else if (data instanceof RealmList) {
            RealmList realmList = (RealmList) data;
            realmList.realm.handlerController.addChangeListenerAsWeakReference(listener);
        } else {
            throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
        }
    }

    private void removeListener(OrderedRealmCollection<T> data) {
        if (data instanceof RealmResults) {
            RealmResults realmResults = (RealmResults) data;
            realmResults.removeChangeListener(listener);
        } else if (data instanceof RealmList) {
            RealmList realmList = (RealmList) data;
            realmList.realm.handlerController.removeWeakChangeListener(listener);
        } else {
            throw new IllegalArgumentException("RealmCollection not supported: " + data.getClass());
        }
    }

    /**
     * Returns how many items are in the data set.
     *
     * @return the number of items.
     */
    @Override
    public int getItemCount() {
        if (adapterData == null) {
            return 0;
        }
        return adapterData.size();
    }

    /**
     * Get the data item associated with the specified position in the data set.
     *
     * @param position Position of the item whose data we want within the adapter's
     * data set.
     * @return The data at the specified position.
     */
    public T getItem(int position) {
        if (adapterData == null) {
            return null;
        }
        return adapterData.get(position);
    }

    /**
     * Get the row id associated with the specified position in the list. Note that item IDs are not stable so you
     * cannot rely on the item ID being the same after {@link #notifyDataSetChanged()} or
     * {@link #updateData(OrderedRealmCollection)} has been called.
     *
     * @param position The position of the item within the adapter's data set whose row id we want.
     * @return The id of the item at the specified position.
     */
    @Override
    public long getItemId(int position) {
        // TODO: find better solution once we have unique IDs
        return position;
    }

    /**
     * Updates the data associated with the Adapter.
     *
     * Note that RealmResults and RealmLists are "live" views, so they will automatically be updated to reflect the
     * latest changes. This will also trigger {@code notifyDataSetChanged()} to be called on the adapter.
     *
     * This method is therefore only useful if you want to display data based on a new query without replacing the
     * adapter.
     *
     * @param data the new {@link OrderedRealmCollection} to display.
     */
    public void updateData(OrderedRealmCollection<T> data) {
        if (listener != null) {
            if (adapterData != null) {
                removeListener(adapterData);
            }
            if (data != null) {
                addListener(data);
            }
        }

        this.adapterData = data;
        notifyDataSetChanged();
    }
}
cmelchior commented 8 years ago

That is probably a good starting ground, but I am not sure it captures the complexity of some of the use cases we are seeing (e.g drag-n-drop sorting).

Zhuinden commented 8 years ago

That's true, and I think that requires stable IDs, and on top of that notifyDatasetChanged() also means no predictive animations on insertion/removal...

I'm pretty sure that when we needed drag-and-drop sorting, we had detached copies (or at least a list that was not a RealmResults<T>), and we also had our own field called rank. I'm not sure this is a problem that can be handled generally, but I could be wrong.

JM-Agrimap commented 8 years ago

Why not look at getting Thorben Primke's versions integrated as a starting point since those are the ones you reference in the "samples" section of the website?

https://github.com/thorbenprimke/realm-recyclerview/blob/master/library/src/main/java/co/moonmonkeylabs/realmrecyclerview/RealmRecyclerView.java https://github.com/thorbenprimke/realm-recyclerview/blob/master/library/src/main/java/io/realm/RealmBasedRecyclerViewAdapter.java

cmelchior commented 8 years ago

Thorben has done some great work with his RecyclerView, but his library is more like a self-contained UI widget. What we want to do with the classes in this repo is making sure they can be used for building your own custom widget. For that reason his version fits better in its own repository.

Zhuinden commented 7 years ago

Isn't this technically https://github.com/realm/realm-android-adapters/blob/master/example/src/main/java/io/realm/examples/adapters/ui/recyclerview/MyRecyclerViewAdapter.java ?

cmelchior commented 7 years ago

Partly yes, but I believe this issue was also created to address the problems described here https://github.com/realm/realm-java/issues/4245#issuecomment-282432206