Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
240 views
in Technique[技术] by (71.8m points)

android - Best practices to use realm with a recycler view?

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 into a lot of troubles trying to implement a simple color change on a row . For example consider this typical usage:

public class User extends RealmObject {
   @PrimaryKey
   String name;

   boolean isSelected;
   ... 

   constructor, getter and setters 
}

public class UserAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private RealmResults<User> users;

    public UserAdapter(RealmResults<User> users) {
        this.users = users;
    }

   ...

   public void markAsSelected(int position){
      // get the old selected user and deselect it
      notifyItemChanged(? how do i get the position given my User has no index ?);

      // mark as selected the new user at position
   }

I ran into a lot of issues since I couldn't find anything on the internet. I know this is because I don't know how to properly use realm. But finding the right way is a struggle in itself . I read all their documentation but to no avail.

EDIT : Since I was asked to --> Instead of saying "I have a bunch of issues with [that]", describe your issue(s) and we'll try to provide insights and answers to your incomprehensions.

So my problem is simple :

I have a RealmUser :

public class RealmUser extends RealmObject {

    @PrimaryKey
    private String key;

    private String name;
    private boolean isSelected;
    private boolean editMode;
    private RealmList<RealmItemList> lists;


    public RealmUser() {}

    public RealmUser(String name, RealmList<RealmItemList> lists, boolean isSelected , boolean editMode) {
        this.key = UUID.randomUUID().toString();
        this.name = name;
        this.isSelected = isSelected;
        this.editMode = editMode;
        if (lists ==null){
            this.lists = new RealmList<RealmItemList>();
        }else{
            this.lists = lists;
        }
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public boolean isSelected() {
        return isSelected;
    }

    public void setSelected(boolean isSelected) {
        this.isSelected = isSelected;
    }

    public boolean isEditMode() {
        return editMode;
    }

    public void setEditMode(boolean editMode) {
        this.editMode = editMode;
    }

    public RealmList<RealmItemList> getLists() {
        return lists;
    }

    public void setLists(RealmList<RealmItemList> lists) {
        this.lists = lists;
    }


}

Which I put in a RealmResults array using :

RealmResults users = realm.where(RealmUser.class).findAll();

I pass my user array to my custom user adapter :

public class UserAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private RealmResults<RealmUser> users;

    public UserAdapter(RealmResults<RealmUser> users) {
        this.users = users;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        LayoutInflater inflater = LayoutInflater.from(parent.getContext());

        if(viewType == 1){
            View v = inflater.inflate(R.layout.detail_user, parent, false);
            return new UserHolder(v);
        }else if(viewType == 2){
            View v = inflater.inflate(R.layout.edit_user, parent, false);
            return new editUserHolder(v);
        }else {
            return null;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        RealmUser user = users.get(position);
        String userName = user.getName();
        boolean isSelected = user.isSelected();

        if (holder instanceof UserHolder ){
            UserHolder uHolder = (UserHolder) holder;
            uHolder.userText.setText(userName);
            if (isSelected){
                uHolder.userContainer.setBackgroundColor(Color.parseColor("#607D8B"));
            }
        }else if(holder instanceof editUserHolder){
            editUserHolder eUserHolder = (editUserHolder) holder;
            eUserHolder.userEditContainer.setBackgroundColor(Color.parseColor("#eeeeee"));
        }



    }

    @Override
    public int getItemViewType(int position) {
        RealmUser user = users.get(position);

        if (user.isEditMode()){
            return 2;
        }else {
            return 1;
        }

    }

    @Override
    public int getItemCount() {
        return users.size();
    }

    public void markAsSelected(int position, DrawerLayout mDrawerLayout , Toolbar toolbar, Realm realm){
        // Here is my problem : How do I get the already selected user asuming there is one in my db and notify the UI that I changed that item. 

}

That has a custom click Listener : that gets recyclerview item that was clicked using :

public class UserClickListener implements RecyclerView.OnItemTouchListener{

    public static interface OnItemClickListener{
        public void onItemClick(View v, int position);
    }

    private OnItemClickListener mListener;
    private GestureDetector mGestureDetector;


    public UserClickListener(Context context, final RecyclerView recyclerView, OnItemClickListener listener)
    {
        mListener = listener;

        mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {

            @Override
            public boolean onSingleTapConfirmed(MotionEvent e) {
                View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                if(childView != null && mListener != null)
                {
                    mListener.onItemClick(childView, recyclerView.getChildPosition(childView));
                    return true;
                }
                return false;
            }



        });

    }


    @Override
    public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
        View childView = view.findChildViewUnder(e.getX(), e.getY());

        if(childView != null && mListener != null && mGestureDetector.onTouchEvent(e))
        {
            mListener.onItemClick(childView, view.getChildPosition(childView));
        }

        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {

    }
}

Which I add to my recyclerView with addOnItemTouchListener :

mListRecycler.addOnItemTouchListener(new UserClickListener(getActivity(), mListRecycler, new UserClickListener.OnItemClickListener(){

            @Override
            public void onItemClick(View view, int position)
            {
                UserAdapter myadapter = (UserAdapter) mListRecycler.getAdapter();
                myadapter.markAsSelected(position, mDrawerLayout , mToolbar, realm);
            }
    }));
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

ANSWER FOR 0.89.0 AND ABOVE

For the latest versions, you should use RealmRecyclerViewAdapter in the realm-android-adapters repository.

Versions:

  • Use 1.5.0 up to 2.X

  • Use 2.1.1 up to 4.X

  • Use 3.0.0 above 5.X


OLD ANSWER FOR OLD VERSIONS:

I made this RealmRecyclerViewAdapter based on the implementation of RealmBaseAdapter.

This is for v0.89.0 AND ABOVE

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();
    }
}

This is for v0.84.0 AND ABOVE, BUT OLDER THAN v0.89.0 (updated for v0.87.5):

public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> { //put this in `io.realm`
    protected LayoutInflater inflater;
    protected RealmResults<T> realmResults;
    protected Context context;
    private final RealmChangeListener listener;

    public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults, boolean automaticUpdate) {
        if (context == null) {
            throw new IllegalArgumentException("Context cannot be null");
        }
        this.context = context;
        this.realmResults = realmResults;
        this.inflater = LayoutInflater.from(context);
        this.listener = (!automaticUpdate) ? null : new RealmChangeListener() {
            @Override
            public void onChange() {
                notifyDataSetChanged();
            }
        };

        if (listener != null && realmResults != null) {
            realmResults.realm.handlerController.addChangeListenerAsWeakReference(listener);
        }
    }

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

    /**
     * Returns the item associated with the specified position.
     *
     * @param i index of item whose data we want.
     * @return the item at the specified position.
     */
    public T getItem(int i) {
        if (realmResults == null) {
            return null;
        }
        return realmResults.get(i);
    }

    /**
     * Returns the current ID for an item. Note that item IDs are not stable so you cannot rely on the item ID being the
     * same after {@link #notifyDataSetChanged()} or {@link #updateRealmResults(RealmResults)} has been called.
     *
     * @param i index of item in the adapter.
     * @return current item ID.
     */
    @Override
    public long getItemId(int i) {
        // TODO: find better solution once we have unique IDs
        return i;
    }

    /**
     * Updates the RealmResults associated to the Adapter. Useful when the query has been changed.
     * If the query does not change you might consider using the automaticUpdate feature.
     *
     * @param queryResults the new RealmResults coming from the new query.
     */
    public void updateRealmResults(RealmResults<T> queryResults) {
        if (listener != null) {
            // Making sure that Adapter is refreshed correctly if new RealmResults come from another Realm
            if (this.realmResults != null) {
                this.realmResults.realm.removeChangeListener(listener);
            }
            if (queryResults != null) {
                queryResults.realm.addChangeListener(listener);
            }
        }

        this.realmResults = queryResults;
        notifyDataSetChanged();
    }

    public void addChangeListenerAsWeakReference(RealmChangeListener realmChangeListener) {
        if(realmResults != null) {
            realmResults.realm.handlerController.addChangeListenerAsWeakReference(realmChangeListener);
        }
    }
}

This is for OLDER THAN 0.84.0:

public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
        extends RecyclerView.Adapter<VH> { //put this in `io.realm`
    protected LayoutInflater inflater;
    protected RealmResults<T> realmResults;
    protected Context context;

    private final RealmChangeListener listener;

    public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults, boolean automaticUpdate) {
        if(context == null) {
            throw new IllegalArgumentException("Context cannot be null");
        }
        this.context = context;
        this.realmResults = realmResults;
        this.inflater = LayoutInflater.from(context);
        this.listener = (!automaticUpdate) ? null : new RealmChangeListener() {
            @Override
            public void onChange() {
                notifyDataSetChanged();
            }
        };

        if(listener != null && realmResults != null) {
            realmResults.getRealm()
                    .addChangeListener(listener);
        }
    }

    @Override
    public long getItemId(int i) {
        // TODO: find better solution once we have unique IDs
        return i;
    }

    public T getItem(int i) {
        if(realmResults == null) {
            return null;
        }
        return realmResults.get(i);
    }

    public void updateRealmResults(RealmResults<T> queryResults) {
        if(listener != null) {
            // Making sure that Adapter is refreshed correctly if new RealmResults come from another Realm
            if(this.realmResults != null) {
                realmResults.getRealm().removeChangeListener(listener);
            }
            if(queryResults != null) {
                queryResults.getRealm().addChangeListener(listener);
            }
        }

        this.realmResults = queryResults;
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        if(realmResults == null) {
            return 0;
        }
        return realmResults.size();
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...