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
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