Best practices to use realm with a recycler view?

前端 未结 5 647
再見小時候
再見小時候 2020-12-04 15:54

Do you guys have any best practices regarding using realm with a recyclerview ? I know it\'s generic question but I found nothing on it on the internet. For example I run in

5条回答
  •  清歌不尽
    2020-12-04 16:18

    Some of the answers above include reflection, not to mention that a sectioned RecyclerView would cause complications. They also do not support adding and removing items. Here is my version of the RecyclerView Adapter that works with Realm, supports a sectioned RecyclerView, also adds and removes items at arbitrary positions if need be

    Here is our AbstractRealmAdapter that takes care of all the low level stuff, displaying headers, footers, items, loading data inside RealmResults, managing item types

    import io.realm.Realm;
    import io.realm.RealmObject;
    import io.realm.RealmResults;
    
    public abstract class AbstractRealmAdapter
            extends RecyclerView.Adapter {
    
        public static final int HEADER_COUNT = 1;
        public static final int FOOTER_COUNT = 1;
    
        //Our data source
        protected RealmResults mResults;
    
        public AbstractRealmAdapter(Realm realm) {
            //load data from subclasses
            mResults = loadData(realm);
            notifyDataSetChanged();
        }
    
    
        public int getHeaderCount() {
            return hasHeader() ? HEADER_COUNT : 0;
        }
    
        public int getFooterCount() {
            return hasFooter() ? FOOTER_COUNT : 0;
        }
    
        public boolean isHeader(int position) {
            if (hasHeader()) {
                return position < HEADER_COUNT;
            } else {
                return false;
            }
        }
    
        public boolean isFooter(int position) {
            if (hasFooter()) {
                return position >= getCount() + getHeaderCount();
            } else {
                return false;
            }
        }
    
        @Override
        public long getItemId(int i) {
            return i;
        }
    
    
        @Override
        public final int getItemViewType(int position) {
            if (isHeader(position)) {
                return ItemType.HEADER.ordinal();
            } else if (isFooter(position)) {
                return ItemType.FOOTER.ordinal();
            } else {
                return ItemType.ITEM.ordinal();
            }
        }
    
        /**
         * @param position the position within our adapter inclusive of headers,items and footers
         * @return an item only if it is not a header or a footer, otherwise returns null
         */
        public T getItem(int position) {
            if (!isHeader(position) && !isFooter(position) && !mResults.isEmpty()) {
                return mResults.get(position - getHeaderCount());
            }
            return null;
        }
    
    
        @Override
        public final int getItemCount() {
            return getHeaderCount() + getCount() + getFooterCount();
        }
    
        public final int getCount() {
            return mResults.size();
        }
    
        public abstract boolean hasHeader();
    
        public abstract boolean hasFooter();
    
    
        public void setData(RealmResults results) {
            mResults = results;
            notifyDataSetChanged();
        }
    
        protected abstract RealmResults loadData(Realm realm);
    
        public enum ItemType {
            HEADER, ITEM, FOOTER;
        }
    }
    

    To add items by some method or remove items by swipe to delete, we have an extension in the form of AbstractMutableRealmAdapter that looks as shown below

    import android.support.v7.widget.RecyclerView;
    
    import io.realm.Realm;
    import io.realm.RealmObject;
    
    public abstract class AbstractMutableRealmAdapter
            extends AbstractRealmAdapter implements OnSwipeListener {
    
        private Realm realm;
    
        public AbstractMutableRealmAdapter(Realm realm) {
            //call the superclass constructor to load data from subclasses into realmresults
            super(realm);
            this.realm = realm;
        }
    
        public void add(T item, boolean update) {
            realm.beginTransaction();
            T phraseToWrite = (update == true) ? realm.copyToRealmOrUpdate(item) : realm.copyToRealm(item);
            realm.commitTransaction();
            notifyItemRangeChanged(0, mResults.size());
        }
    
        @Override
        public final void onSwipe(int position) {
            if (!isHeader(position) && !isFooter(position) && !mResults.isEmpty()) {
                int itemPosition = position - getHeaderCount();
                realm.beginTransaction();
                T item = mResults.get(itemPosition);
                item.removeFromRealm();
                realm.commitTransaction();
                notifyItemRemoved(position);
            }
        }
    
    }
    

    Notice the use of the interface OnSwipeListener which looks like this

    public interface OnSwipeListener {
        /**
         * @param position the position of the item that was swiped within the RecyclerView
         */
        void onSwipe(int position);
    }
    

    This SwipeListener is used to perform a Swipe to delete inside our TouchHelperCallback which in turn is used to delete the objects from Realm directly and looks as follows

    import android.support.v7.widget.RecyclerView;
    import android.support.v7.widget.helper.ItemTouchHelper;
    
    public class TouchHelperCallback extends ItemTouchHelper.Callback {
    
        private final OnSwipeListener mSwipeListener;
    
        public TouchHelperCallback(OnSwipeListener adapter) {
            mSwipeListener = adapter;
        }
    
        /**
         * @return false if you dont want to enable drag else return true
         */
        @Override
        public boolean isLongPressDragEnabled() {
            return false;
        }
    
        /**
         * @return true of you want to enable swipe in your RecyclerView else return false
         */
        @Override
        public boolean isItemViewSwipeEnabled() {
            return true;
        }
    
        @Override
        public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            //We want to let the person swipe to the right on devices that run LTR and let the person swipe from right to left on devices that run RTL
            int swipeFlags = ItemTouchHelper.END;
            return makeMovementFlags(0, swipeFlags);
        }
    
        @Override
        public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
                              RecyclerView.ViewHolder target) {
            return false;
        }
    
        @Override
        public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            mSwipeListener.onSwipe(viewHolder.getAdapterPosition());
        }
    }
    

    The full implementation demo is available here for review https://github.com/slidenerd/SpamWordList/tree/spamphraser_with_realmresults_base Feel free to suggest any improvements

    I replaced the notifyXXX methods with notifyDataSetChanged, RealmResults objects are live objects which means they automatically change when the data is updated, I tried calling notifyXXX methods and they caused an RecyclerView inconsistency exception, I am well aware of the fact that notifyDataSetChanged() would mess with animations, will keep you guys updated on a solution that overcomes the inconsistency error and at the same time provides a good adapter experience

提交回复
热议问题