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);
}
}));
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();
}
}
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<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
public static final int HEADER_COUNT = 1;
public static final int FOOTER_COUNT = 1;
//Our data source
protected RealmResults<T> 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<T> results) {
mResults = results;
notifyDataSetChanged();
}
protected abstract RealmResults<T> 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<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends AbstractRealmAdapter<T, VH> 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
Now that with Realm 0.88.2 we can make a RecyclerView adapter that updates the RecyclerView with more precision than using notifyDataSetChanged() every time. This can be accomplished by using the new ability create custom methods.
Overriding the equals method, in the realm object that will be used with the recycler adapter, is all that will be needed. (You don't actually need to override equals... but you may find that realm objects do not equal each other when you expect them to. This will lead to unnecessary recyclerview updates after running diff)
Then add Google's java-diff-utils to your gradle dependencies
compile 'com.googlecode.java-diff-utils:diffutils:1.3.0'
Using this RealmRecyclerViewAdapter implementation a copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update the RecyclerView as appropriate
public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
protected RealmResults<T> realmResults;
protected List<T> lastCopyOfRealmResults;
int maxDepth = 0;
private RealmChangeListener realmResultsListener;
Realm realm;
public RealmRecyclerViewAdapter(RealmResults<T> realmResults, boolean automaticUpdate) {
this(realmResults, automaticUpdate, 0);
}
/**
*
* #param realmResults
* #param automaticUpdate
* #param maxDepth limit of the deep copy when copying realmResults. All references after this depth will be {#code null}. Starting depth is {#code 0}.
* A copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update
* the RecyclerView as appropriate
*/
public RealmRecyclerViewAdapter(RealmResults<T> realmResults, boolean automaticUpdate, int maxDepth) {
this.realmResultsListener = (!automaticUpdate) ? null : getRealmResultsChangeListener();
if (realmResultsListener != null && realmResults != null) {
realmResults.addChangeListener(realmResultsListener);
}
this.realmResults = realmResults;
realm = Realm.getDefaultInstance();
this.maxDepth = maxDepth;
lastCopyOfRealmResults = realm.copyFromRealm(realmResults, this.maxDepth);
}
#Override
public int getItemCount() {
return realmResults != null ? realmResults.size() : 0;
}
/**
* Make sure this is called before a view is destroyed to avoid memory leaks do to the listeners.
* Do this by calling setAdapter(null) on your RecyclerView
* #param recyclerView
*/
#Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
super.onDetachedFromRecyclerView(recyclerView);
if (realmResultsListener != null) {
if (realmResults != null) {
realmResults.removeChangeListener(realmResultsListener);
}
}
realm.close();
}
/**
* Update the RealmResults associated with 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.
* #param maxDepth limit of the deep copy when copying realmResults. All references after this depth will be {#code null}. Starting depth is {#code 0}.
* A copy of realmResults is made at start, and on every change to compare against future changes. Detected changes are used to update
* the RecyclerView as appropriate
*/
public void updateRealmResults(RealmResults<T> queryResults, int maxDepth) {
if (realmResultsListener != null) {
if (realmResults != null) {
realmResults.removeChangeListener(realmResultsListener);
}
}
realmResults = queryResults;
if (realmResults != null && realmResultsListener !=null) {
realmResults.addChangeListener(realmResultsListener);
}
this.maxDepth = maxDepth;
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,this.maxDepth);
notifyDataSetChanged();
}
public T getItem(int position) {
return realmResults.get(position);
}
public int getRealmResultsSize(){
return realmResults.size();
}
private RealmChangeListener getRealmResultsChangeListener() {
return new RealmChangeListener<RealmResults<T>>() {
#Override
public void onChange(RealmResults<T> element) {
if (lastCopyOfRealmResults != null && !lastCopyOfRealmResults.isEmpty()) {
if (realmResults.isEmpty()) {
// If the list is now empty, just notify the recyclerView of the change.
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
notifyDataSetChanged();
return;
}
Patch patch = DiffUtils.diff(lastCopyOfRealmResults, realmResults);
List<Delta> deltas = patch.getDeltas();
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
if (!deltas.isEmpty()) {
List<Delta> deleteDeltas = new ArrayList<>();
List<Delta> insertDeltas = new ArrayList<>();
for (final Delta delta : deltas) {
switch (delta.getType()){
case DELETE:
deleteDeltas.add(delta);
break;
case INSERT:
insertDeltas.add(delta);
break;
case CHANGE:
notifyItemRangeChanged(
delta.getRevised().getPosition(),
delta.getRevised().size());
break;
}
}
for (final Delta delta : deleteDeltas) {
notifyItemRangeRemoved(
delta.getOriginal().getPosition(),
delta.getOriginal().size());
}
//item's should be removed before insertions are performed
for (final Delta delta : insertDeltas) {
notifyItemRangeInserted(
delta.getRevised().getPosition(),
delta.getRevised().size());
}
}
} else {
notifyDataSetChanged();
lastCopyOfRealmResults = realm.copyFromRealm(realmResults,maxDepth);
}
}
};
}
}
Your post does not even contain a real question.
Have you checked out this post: http://gradlewhy.ghost.io/realm-results-with-recyclerview/ ?
Not sure why you wouldn't just use an ArrayList in your adapter and add all elements from the RealmResult to that list though. Could anyone explain why the solution in the blog post would be better?
Implementing the Realm Add-on from Thorben Primke is a very convenient method
for handling Recycler View applications with Realm databases. His github has good examples of the ways that it can be implemented.
I'll include mine here so you have an example. First modify your project build gradle for jitpack.io:
allprojects {
repositories {
jcenter()
maven { url "https://jitpack.io" }
}
Then your module gradle to point to the library: (note , check for latest version)
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.github.thorbenprimke:realm-recyclerview:0.9.20'
Create the xml layout for a recycler view using the RealmRecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<co.moonmonkeylabs.realmrecyclerview.RealmRecyclerView
android:id="#+id/realm_recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rrvIsRefreshable="true"
app:rrvEmptyLayoutId="#layout/empty_view"
app:rrvLayoutType="LinearLayout"
app:rrvSwipeToDelete="true"
/>
</RelativeLayout>
Now in your RealmRecycler Fragment obtain a Realm query result of RealmObjects, inflate and define a Primke RealmAdapter:
Log.i(TAG, " Obtain Filtered List");
final RealmResults <Session> realmResults = queryD.findAllSorted(
"sessionId", Sort.DESCENDING);
Log.i(TAG, " Inflate realm List");
View view = inflater.inflate(R.layout.realm_card_recycler2, null);
Log.i(TAG, " Define and configure SessionRealmAdapter");
SessionRealmAdapter sessionRealmAdapter =
new SessionRealmAdapter(getActivity(), realmResults, true, true);`enter code here`
RealmRecyclerView realmRecyclerView =
(RealmRecyclerView) view.findViewById(R.id.realm_recycle_view);
realmRecyclerView.setAdapter(sessionRealmAdapter);
Finally configure the Realm Adapter for whatever you want for actions. I've got a couple for clicks and turned on the swipe to delete for deleting realm records.
public class SessionRealmAdapter
extends RealmBasedRecyclerViewAdapter<Session, SessionRealmAdapter.ViewHolder> {
public class ViewHolder extends RealmViewHolder {
public TextView sessionTextView;
public ViewHolder(FrameLayout container) {
super(container);
this.sessionTextView = (TextView) container.findViewById(R.id.session_text_view);
}
}
public SessionRealmAdapter(
Context context,
RealmResults<Session> realmResults,
boolean automaticUpdate,
boolean animateResults) {
super(context, realmResults, automaticUpdate, animateResults);
}
#Override
public ViewHolder onCreateRealmViewHolder(ViewGroup viewGroup, int viewType) {
View v = inflater.inflate(R.layout.session_simple_view, viewGroup, false);
return new ViewHolder((FrameLayout) v);
}
#Override
public void onBindRealmViewHolder(ViewHolder viewHolder, int position) {
final Session singleSession = realmResults.get(position);
viewHolder.sessionTextView.setText(singleSession.gettMethod());
viewHolder.sessionTextView.setOnClickListener(
new View.OnClickListener(){
#Override
public void onClick(View v){
selectSession(singleSession);
showMessage(" Selected "+singleSession.gettMethod());
}
}
);
viewHolder.sessionTextView.setOnLongClickListener(
new View.OnLongClickListener(){
#Override
public boolean onLongClick(View v){
showInformationDialog(singleSession);
showMessage("Long click selected for "
+singleSession.getSessionTitle());
return true;
}
}
);
}
}
Related
The problem is that in my tablayout when im switching between tabs my list duplicating. So i need to remove list on onStop() to recreate it then. Or might be other better solution.
I have tried the following solutions
https://code-examples.net/en/q/1c97047
How to reset recyclerView position item views to original state after refreshing adapter
Remove all items from RecyclerView
My code of adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(Context context) {
this.onlineUsers = new ArrayList<>();
this.context = context;
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
Log.d("testList", "rating " + user.getRating() + " uid " + user.getUid());
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
}
}
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
int initSize = userList.size();
onlineUsers.addAll(userList);
// notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public String getLastItemId() {
return onlineUsers.get(onlineUsers.size() - 1).getUid();
}
public void clearData() {
List<OnlineUser> data = new ArrayList<>();
addUsers(data);
notifyDataSetChanged();
}
My code in fragment
#Override
public void onStop() {
super.onStop();
firstUid = "";
stopDownloadList = false;
List<OnlineUser> list = new ArrayList<>();
mAdapter.addUsers(list);
mAdapter.notifyDataSetChanged();
}
`users are added after callback
#Override
public void addUsers(List<OnlineUser> onlineUsers) {
if (firstUid.equals("")){
firstUid = onlineUsers.get(0).getUid();
}
if (!firstUid.equals("") && onlineUsers.contains(firstUid)){
stopDownloadList = true;
}
if (!stopDownloadList){
mAdapter.addUsers(onlineUsers);
}
setRefreshProgress(false);
isLoading = false;
isMaxData = true;
}
The line mAdapter.addUsers(onlineUsers); from addUsers method gets called twice. Looks like your asynchronous operation gets triggered twice (e. g. from repeating lifecycle methods like onCreate/onCreateView/onViewCreated).
Solution #1: request users a single time
Move your user requesting machinery to onCreate or onAttach. This will save network traffic but could lead to showing outdated data.
Solution #2: replaceUsers
Your clearData calls mAdapter.addUsers(new ArrayList<>()); (btw, take a look at Collections.emptyList()). Looks like you're trying to replace adapter data but appending instead. Replacement method could look like
public void replaceUsers(List<OnlineUser> userList) {
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size);
}
This version still requeses users every time your fragment gets focused but shows fresher data.
So I've got an adapter, which is holding a list of objects that I'm displaying. I'm using the MVVP approach with LiveData and ViewModel that come with the android architecture framework.
In my fragment, I connect the livedata to the adapter:
viewModel.getAlarms().observe(this, alarms -> {
Timber.d("Updating alarm list");
alarmAdapter.updateAlarms(alarms);
});
And in my adapter, I update the list...
void updateAlarms(List<Alarm> alarms){
this.alarms = alarms;
notifyDataSetChanged();
}
So even if I make a small change on a single item from the list (item update, item create, item delete..), the whole list will update. That messes up all of my animations. Is there a way to prevent that?
I don't want to copy everything, but as requested here's the bigger picture:
Fragment:
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewModel = ViewModelProviders.of(this, factory).get(HomeViewModel.class);
// Get everything..
viewModel.getAlarms().observe(this, alarms -> {
Timber.d("Updating alarm list");
alarmAdapter.updateAlarms(alarms);
});
}
#OnClick(R.id.home_fbtn_add_alarm)
void addAlarm(){
viewModel.createAlarm(new Alarm(13,39));
}
private void onAlarmStatusChanged(int alarmId, boolean isActive){
// TODO Make it so it doesn't update the whole list...
viewModel.setAlarmStatus(alarmId, isActive);
}
private void onAlarmDeleted(int alarmId){
this.showSnackbar(String.format("Alarm %s deleted", alarmId), clContainer);
viewModel.deleteAlarm(alarmId);
}
Adapter:
class AlarmsAdapter extends RecyclerView.Adapter<AlarmsAdapter.AlarmHolder> {
private List<Alarm> alarms;
private BiConsumer<Integer, Boolean> onStatusChange;
private Consumer<Integer> onDelete;
AlarmsAdapter(BiConsumer<Integer, Boolean> onStatusChange, Consumer<Integer> onDelete) {
this.alarms = new ArrayList<>();
this.onStatusChange = onStatusChange;
this.onDelete = onDelete;
}
void updateAlarms(List<Alarm> alarms){
this.alarms = alarms;
notifyDataSetChanged();
}
#NonNull
#Override
public AlarmHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
Context parentContext = parent.getContext();
int alarmLayoutId = R.layout.item_alarm;
View view = LayoutInflater.from(parentContext).inflate(alarmLayoutId, parent, false);
return new AlarmHolder(view);
}
#Override
public void onBindViewHolder(#NonNull AlarmHolder alarmViewHolder, int position) {
Alarm alarm = alarms.get(position);
alarmViewHolder.setAlarm(alarm);
}
#Override
public int getItemCount() {
return alarms == null ? 0 : alarms.size();
}
class AlarmHolder extends RecyclerView.ViewHolder {
#BindView(R.id.item_alarm_tv_time)
TextView tvTime;
#BindView(R.id.item_alarm_tv_repeat)
TextView tvRepeat;
#BindView(R.id.item_alarm_tv_punishments)
TextView tvPunishment;
#BindView(R.id.item_alarm_swt_active)
Switch swtActive;
#BindView(R.id.item_alarm_img_delete)
ImageView imgDelete;
#BindView(R.id.item_alarm_foreground)
ConstraintLayout foreground;
#BindView(R.id.item_alarm_background)
RelativeLayout background;
AlarmHolder(#NonNull View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void setAlarm(Alarm alarm){
Timber.i("Setting alarm: %s", this.getAdapterPosition());
boolean isActive = alarm.getActive();
tvTime.setText(alarm.getTime());
tvRepeat.setText(alarm.getRepetitionDays());
tvPunishment.setText(alarm.getPunishments());
swtActive.setChecked(isActive);
}
private void setStatus(boolean isActive) {
AlphaAnimation animation;
if(!isActive){
animation = new AlphaAnimation(1.0f, 0.3f);
} else {
animation = new AlphaAnimation(0.3f, 1f);
}
animation.setDuration(300);
animation.setFillAfter(true);
this.itemView.startAnimation(animation);
// TODO Make it so it doesn't update the whole list...
}
#OnCheckedChanged(R.id.item_alarm_swt_active)
void onStatusClick(boolean checked) {
onStatusChange.accept(getAdapterPosition(), checked);
setStatus(checked);
}
#OnClick(R.id.item_alarm_img_delete)
void onDeleteClick() {
onDelete.accept(getAdapterPosition());
}
}}
And the LiveData:
public class HomeViewModel extends ViewModel {
private final AlarmRepository alarmRepository;
private LiveData<List<Alarm>> alarms;
public HomeViewModel(AlarmRepository alarmRepository) {
this.alarmRepository = alarmRepository;
}
/**
* Gets the Alarms' Observable...
* #return Alarms' observable
*/
LiveData<List<Alarm>> getAlarms() {
Timber.d("Fetching alarms..");
if(alarms == null) {
Timber.i("No alarms are cached. Going to DB!");
alarms = alarmRepository.getAllAlarms();
}
return alarms;
}
/**
* Deletes the selected
* #param alarmPosition alarm to be deleted
*/
void deleteAlarm(int alarmPosition) {
Timber.d("Deleting alarm %d", alarmPosition);
getAlarmAtPosition(alarmPosition)
.ifPresent(alarmRepository::deleteAlarm);
}
/**
* Changes the status of the selected alarm
* #param alarmPosition The selected alarm
* #param status The new status
*/
void setAlarmStatus(int alarmPosition, boolean status){
Timber.d("Alarm: %d is changing active status to %s", alarmPosition, status);
getAlarmAtPosition(alarmPosition)
.ifPresent(alarm -> alarmRepository.updateStatus(alarm, status));
}
/**
* Gets the alarm at the selected position.
* #param position The position of the alarm
* #return The alarm of the selected position. Else returns empty.
*/
private Optional<Alarm> getAlarmAtPosition(int position){
Optional<List<Alarm>> alarms =
Optional.ofNullable(this.alarms)
.map(LiveData::getValue);
if(!alarms.isPresent()) {
return Optional.empty();
}
try {
return Optional.of(alarms.get().get(position));
} catch (Exception e){
Timber.e(e, "Could not get alarm at position: %d", position);
return Optional.empty();
}
}
/**
* Creates a new alarm. If null, does nothing.
* #param alarm The alarm to be saved in the DB
*/
void createAlarm(Alarm alarm) {
Timber.d("Adding new alarm.");
if(alarm == null) {
Timber.w("Cannot save null alarm");
return;
}
alarmRepository.createAlarm(alarm);
}
}
I would suggest using ListAdapter (part of recyclerView library)
class AlarmsAdapter extends ListAdapter<Alarm , AlarmsAdapter.AlarmHolder> {
public AlarmsAdapter(
#NonNull ItemCallback<Feed> diffCallback) {
super(diffCallback);
}.....
}
pass this to AlarmsAdapter
private final ItemCallback<Alarm > diffCallback = new ItemCallback<Feed>() {
#Override
public boolean areItemsTheSame(#NonNull Alarm oldItem, #NonNull Alarm newItem) {
return oldItem==newItem;
}
#Override
public boolean areContentsTheSame(#NonNull Alarm oldItem, #NonNull Alarm newItem) {
return oldItem==newItem;
}
};
I'm using a ViewModel that populates a RecyclerView.Adapter, and loads data from LiveData from my Room database. The problem is that my display is always blank, and checking with the Dao (for debug, on the main thread) shows me that the data is retreived just fine. (ergo there is data in the DB).
The problem is that the Observer on my LiveData always returns a null (or no data) and I end up having to refresh the fragment at least once (by moving away and moving back) to see anything - even the meagre one record I put in the Database for testing.
Restarting the app or fragment means a blank screen and a few refreshes before I see anything which is strange because, well, the data is already there.
I'm out of ideas on how to get this to show me data in more or less real time. Can anyone help?
Sharing the DAO, ViewModel and Fragment code here.
Fragment
... import libs and set up variables ...
private HouseCallAdapter houseCallAdapter;
private RecyclerView recyclerView;
private TextView emptyView;
RevivDatabase revivDatabase;
private LiveData<List<HouseCall>> liveHousecalls;
private List<HouseCall> houseCalls;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_reviv_housecall_request_list, container, false);
Bundle arguments = getArguments();
String action = arguments.getString("data");
revivDatabase = RevivDatabase.getDatabase(getActivity().getApplicationContext());
emptyView = view.findViewById(R.id.txtNoData);
recyclerView = view.findViewById(R.id.hcrecyclerView);
viewModel = ((Reviv) getActivity()).getViewModel();
if(liveHousecalls == null) {
liveHousecalls = new MutableLiveData<List<HouseCall>>();
}
houseCallAdapter = new HouseCallAdapter(getContext(), apikey, false, false);
liveHousecalls = viewModel.getOpenHousecalls();
// this is to test if there is actually any data retreived
// calling on main thread. Lose this code later.
houseCalls = revivDatabase.revivDao().getHousecallsByStatus(action);
break;
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
houseCallAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onChanged() {
super.onChanged();
checkEmpty();
}
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
checkEmpty();
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
super.onItemRangeRemoved(positionStart, itemCount);
checkEmpty();
}
void checkEmpty() {
//emptyView.setText (R.string.no_data_available);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
recyclerView.setVisibility (houseCallAdapter.getItemCount() == 0 ? View.GONE : View.VISIBLE);
}
});
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
liveHousecalls.observe(getActivity(), new Observer<List<HouseCall>>() {
#Override
public void onChanged(#Nullable List<HouseCall> houseCalls) {
if(houseCalls != null) {
houseCallAdapter.setData(houseCalls);
houseCallAdapter.notifyDataSetChanged();
}
}
});
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallAdapter);
emptyView.setVisibility(houseCallAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE);
return view;
}
ViewModel
private LiveData<List<HouseCall>> housecallList;
private LiveData<List<HouseCall>> openHousecalls, confirmedHousecalls, closedHousecalls, missedHousecalls, userCancelledHousecalls, respCancelledHousecalls;
private LiveData<List<Incident>> incidentList, openIncidents;
private LiveData<List<Incident>> closedIncidents, usercancelIncidents, respcancelIncidents;
private LiveData<Incident> liveIncident;
private RevivDatabase database;
Context context;
/////////////////////////////////////////////////////////
// CONSTRUCTOR
/////////////////////////////////////////////////////////
public RevivViewModel(Application application) {
super(application);
//////////////////////////////////////////////////////////////////////////
// //
// DANGER WILL ROBINSON //
// Storing context in ViewModel is Not A Good Idea (TM) //
context = application.getApplicationContext(); //
// //
//////////////////////////////////////////////////////////////////////////
database = RevivDatabase.getDatabase(application);
}
/////////////////////////////////////////////////////////
// GETTERS AND SETTERS
/////////////////////////////////////////////////////////
// Housecalls
public LiveData<List<HouseCall>> getHousecallList() {
if (housecallList == null) {
housecallList = new MutableLiveData<>();
loadHousecalls();
}
return housecallList;
}
public LiveData<List<HouseCall>> getOpenHousecalls() {
if (openHousecalls == null) {
openHousecalls = new MutableLiveData<>();
loadOpenHousecalls();
}
return openHousecalls;
}
/////////////////////////////////////////////////////////
// TRIGGER REFRESH FROM VIEWMODEL
/////////////////////////////////////////////////////////
// TBD
/////////////////////////////////////////////////////////
// EXTERNAL CALLS - REFRESH FROM DB
/////////////////////////////////////////////////////////
// Methods to accept/cancel incidents and housecalls
public void loadHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
housecallList = database.revivDao().getAllLiveHousecalls();
//housecallList.postValue(hc);
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
public void loadOpenHousecalls(){
class OneShotTask implements Runnable {
OneShotTask() {
}
public void run() {
openHousecalls = database.revivDao().getLiveHousecallsByStatus("open");
}
}
Thread t = new Thread(new OneShotTask());
t.start();
}
}
DAOInterface
public interface RevivDaoInterface {
// Housecalls
... numerous insert, delete and update calls ...
#Query("SELECT * FROM housecalls WHERE housecallid = :housecallid")
public HouseCall getHousecallById(String housecallid);
#Query("SELECT * FROM housecalls WHERE status = :status")
public List<HouseCall> getHousecallsByStatus(String status);
#Update(onConflict = OnConflictStrategy.IGNORE)
void updateHousecall(HouseCall houseCall);
#Query("SELECT * FROM housecalls WHERE status = \'open\'")
public LiveData<List<HouseCall>> getOpenHousecalls();
#Query("SELECT * FROM housecalls WHERE status = :status")
public LiveData<List<HouseCall>> getLiveHousecallsByStatus(String status);
#Query("SELECT * FROM housecalls")
public List<HouseCall> getAllHousecalls();
}
DAO
imports
#Dao
public abstract class RevivDao implements RevivDaoInterface {
#Transaction
public void upsert(HouseCall houseCall){
try {
this.insert(houseCall);
} catch (SQLiteConstraintException exception) {
this.update(houseCall);
Log.e(TAG, "upsert: ", exception);
}
}
#Transaction
public void upsert(List<HouseCall> houseCall){
for(HouseCall hc : houseCall) {
try {
this.insert(hc);
} catch (SQLiteConstraintException exception) {
this.update(hc);
Log.e(TAG, "upsert: ", exception);
}
}
}
}
Thanks to #pskink, I figured a way around the lag in updating the data into my ViewModel.
To solve this issue, I had to implement PagedListAdapter.
In the build.gradle (Module) file
implementation 'android.arch.paging:runtime:1.0.1'
In the DAO
#Query("SELECT * from housecalls where status = :status")
public abstract DataSource.Factory<Integer, HouseCall> getHousecallPagesByStatus(String status);
In the ViewModel
//declare a LiveData of PagedList
LiveData<PagedList<HouseCall>> openhousecallPages;
// Define Configuration for Paged List
Config pagedListConfig = (new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
// Function to access the data
public LiveData<PagedList<HouseCall>> getOpenhousecallPages(){
openhousecallPages = new LivePagedListBuilder<>(database.revivDao().getHousecallPagesByStatus("open"),
pagedListConfig).build();
return openhousecallPages;
}
Set up the PagedListAdapter
package packagename;
import static android.content.ContentValues.TAG;
public class HouseCallPagedAdapter extends PagedListAdapter<HouseCall, HouseCallViewHolder>{
protected HouseCallPagedAdapter(#NonNull DiffUtil.ItemCallback<HouseCall> diffCallback) {
super(diffCallback);
}
public HouseCallPagedAdapter(#NonNull DiffUtil.ItemCallback diffcallback){
super(diffcallback);
}
#NonNull
#Override
public HouseCallViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
return new HouseCallViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.card_housecall_request, parent, false),parent.getContext());
}
#Override
public void onBindViewHolder(#NonNull in.portmanteau.reviv.Adapters.HouseCallViewHolder holder, int i) {
holder.bindTo(getItem(i));
}
}
Define a ViewHolder with the following structure
public class HouseCallViewHolder extends RecyclerView.ViewHolder{
// declare values, elements, etc
public HouseCallViewHolder(View itemView, Context mContext) {
super(itemView);
// set up UI elements
}
void bindTo(final HouseCall houseCall){
this.houseCall = houseCall;
//populate values, set onClickListeners, etc.
}
}
Finally, use the Adapter in your Activity/Fragment!
// Implement an DiffUtil.ItemCallback
private DiffUtil.ItemCallback<HouseCall> diffCallback = new DiffUtil.ItemCallback<HouseCall>() {
#Override
public boolean areItemsTheSame(#NonNull HouseCall houseCall, #NonNull HouseCall newhouseCall) {
return houseCall.getHousecallid().equalsIgnoreCase(newhouseCall.getHousecallid()) ;
}
#Override
public boolean areContentsTheSame(#NonNull HouseCall houseCall, #NonNull HouseCall newhouseCall) {
return houseCall.isTheSame(newhouseCall);
}
};
HouseCallPagedAdapter houseCallPagedAdapter = new HouseCallPagedAdapter(diffCallback);
viewModel.getOpenhousecallPages().observe(this, houseCallPagedAdapter::submitList);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
recyclerView.setItemAnimator (new DefaultItemAnimator());
recyclerView.setAdapter(houseCallPagedAdapter);
Objective: I have a ListView with a BaseAdapter I want to populate with values from my Firebase Database.
This is my BaseAdapter I named MessagesAdapter.java:
public class MessagesAdapter extends BaseAdapter {
private Context context;
private List<Message> messages;
private LayoutInflater inflater;
boolean showMoreClicked = false;
public MessagesAdapter(Context context, List<Message> messages) {
this.context = context;
this.messages = messages;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return messages.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(final int position, final View convertView,final ViewGroup parent) {
View itemView = convertView;
MessagesAdapter.ViewHolder holder;
if (convertView == null) {
itemView = inflater.inflate(R.layout.item_message, null);
holder = new ViewHolder();
// set image based on selected text
holder.message = itemView.findViewById(R.id.tv_msg);
holder.showMoreBtn = itemView.findViewById(R.id.bt_show_more);
holder.datetime = itemView.findViewById(R.id.tv_ride_time);
itemView.setTag(holder);
} else
holder = (MessagesAdapter.ViewHolder) itemView.getTag();
final Message msg = messages.get(position);
/* editing item views */
holder.datetime.setText(msg.getDatetime());
holder.message.setText(msg.getContent());
holder.showMoreBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "All Message: "+msg.getContent(), Toast.LENGTH_SHORT).show();
showMoreClicked = !showMoreClicked;
}
});
DatabaseReference profileRef = FirebaseDatabase.getInstance().getReference("profile");
profileRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Profile sender = dataSnapshot.child(msg.getSender_id()+"").getValue(Profile.class);
ViewHolder h = new ViewHolder();
h.userImg = parent.getChildAt(position).findViewById(R.id.iv_profile_image);
h.userName = parent.getChildAt(position).findViewById(R.id.tv_sender_name);
h.userName.setText(sender.getFirstname()+" "+sender.getLastname()); /*** Error shows up here ***/
getImageFromDB(sender.getPicpath(), h.userImg);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return itemView;
}
private static class ViewHolder {
CircleImageView userImg;
TextView userName;
TextView datetime;
TextView message;
Button showMoreBtn;
}
private void getImageFromDB(String firebasePath, ImageView imageView) {
StorageReference mStorageRef = FirebaseStorage.getInstance()
.getReferenceFromUrl("gs://iveridemap.appspot.com")
.child(firebasePath);
// Load the image using Glide
Glide.with(context)
.using(new FirebaseImageLoader()) // cannot resolve method using!
.load(mStorageRef)
.into(imageView);
}
}
This adapter is used to fetch messages sent to the current user from firebase and show them in my ListView.
I instantiate this BaseAdapter and ListView inside a fragment I called MessagesFragment.java:
public class MessagesAdapter extends BaseAdapter {
private Context context;
private List<Message> messages;
private LayoutInflater inflater;
boolean showMoreClicked = false;
public MessagesAdapter(Context context, List<Message> messages) {
this.context = context;
this.messages = messages;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return messages.size();
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(final int position, final View convertView,final ViewGroup parent) {
View itemView = convertView;
MessagesAdapter.ViewHolder holder;
if (convertView == null) {
itemView = inflater.inflate(R.layout.item_message, null);
holder = new ViewHolder();
// set image based on selected text
holder.message = itemView.findViewById(R.id.tv_msg);
holder.showMoreBtn = itemView.findViewById(R.id.bt_show_more);
holder.datetime = itemView.findViewById(R.id.tv_ride_time);
itemView.setTag(holder);
} else
holder = (MessagesAdapter.ViewHolder) itemView.getTag();
final Message msg = messages.get(position);
/* editing item views */
holder.datetime.setText(msg.getDatetime());
holder.message.setText(msg.getContent());
holder.showMoreBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(context, "All Message: "+msg.getContent(), Toast.LENGTH_SHORT).show();
showMoreClicked = !showMoreClicked;
}
});
DatabaseReference profileRef = FirebaseDatabase.getInstance().getReference("profile");
profileRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Profile sender = dataSnapshot.child(msg.getSender_id()+"").getValue(Profile.class);
ViewHolder h = new ViewHolder();
h.userImg = parent.getChildAt(position).findViewById(R.id.iv_profile_image);
h.userName = parent.getChildAt(position).findViewById(R.id.tv_sender_name);
h.userName.setText(sender.getFirstname()+" "+sender.getLastname());
getImageFromDB(sender.getPicpath(), h.userImg);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return itemView;
}
private static class ViewHolder {
CircleImageView userImg;
TextView userName;
TextView datetime;
TextView message;
Button showMoreBtn;
}
private void getImageFromDB(String firebasePath, ImageView imageView) {
StorageReference mStorageRef = FirebaseStorage.getInstance()
.getReferenceFromUrl("gs://iveridemap.appspot.com")
.child(firebasePath);
// Load the image using Glide
Glide.with(context)
.using(new FirebaseImageLoader()) // cannot resolve method using!
.load(mStorageRef)
.into(imageView);
}
}
Problem: When I scroll down my ListView my code returns a NullPointerException at the line:
h.userName.setText(sender.getFirstname()+" "+sender.getLastname());
In the BaseAdapter, I pointed it out in the code above. Apprently something is wrong with initiating my profile instance I fetch from Firebase.
More Info: This error doesn't show up when there is fewer items in the list and I don't have to scroll down..
I am ready to post more code upon demand, if anyone needs to see my XML file or database structure just say so!
Thanks alot for your time.
I recommend using a RecyclerView, to use firebase items there is some great libraries to make everything more simple
Here is a great library to parcel your items
compile 'org.parceler:parceler-api:1.1.9'
With parceler you can make your activity like this example
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
import com.firebase.client.Firebase;
import com.firebase.client.Query;
import org.parceler.Parcels;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private final static String SAVED_ADAPTER_ITEMS = "SAVED_ADAPTER_ITEMS";
private final static String SAVED_ADAPTER_KEYS = "SAVED_ADAPTER_KEYS";
private Query mQuery;
private MyAdapter mMyAdapter;
private ArrayList<MyItem> mAdapterItems;
private ArrayList<String> mAdapterKeys;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handleInstanceState(savedInstanceState);
setupFirebase();
setupRecyclerview();
}
// Restoring the item list and the keys of the items: they will be passed to the adapter
private void handleInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null &&
savedInstanceState.containsKey(SAVED_ADAPTER_ITEMS) &&
savedInstanceState.containsKey(SAVED_ADAPTER_KEYS)) {
mAdapterItems = Parcels.unwrap(savedInstanceState.getParcelable(SAVED_ADAPTER_ITEMS));
mAdapterKeys = savedInstanceState.getStringArrayList(SAVED_ADAPTER_KEYS);
} else {
mAdapterItems = new ArrayList<MyItem>();
mAdapterKeys = new ArrayList<String>();
}
}
private void setupFirebase() {
Firebase.setAndroidContext(this);
String firebaseLocation = getResources().getString(R.string.firebase_location);
mQuery = new Firebase(firebaseLocation);
}
private void setupRecyclerview() {
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mMyAdapter = new MyAdapter(mQuery, MyItem.class, mAdapterItems, mAdapterKeys);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mMyAdapter);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
return item.getItemId() == R.id.action_settings || super.onOptionsItemSelected(item);
}
// Saving the list of items and keys of the items on rotation
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(SAVED_ADAPTER_ITEMS, Parcels.wrap(mMyAdapter.getItems()));
outState.putStringArrayList(SAVED_ADAPTER_KEYS, mMyAdapter.getKeys());
}
#Override
protected void onDestroy() {
super.onDestroy();
mMyAdapter.destroy();
}
}
Here is the item Model
import org.parceler.Parcel;
#Parcel
public class MyItem {
String name;
long age;
public MyItem() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getAge() {
return age;
}
public void setAge(long age) {
this.age = age;
}
}
Here is the adapter
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.firebase.client.Query;
import java.util.ArrayList;
import java.util.List;
public class MyAdapter extends FirebaseRecyclerAdapter<MyAdapter.ViewHolder, MyItem> {
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textViewName;
TextView textViewAge;
public ViewHolder(View view) {
super(view);
textViewName = (TextView) view.findViewById(R.id.textview_name);
textViewAge = (TextView) view.findViewById(R.id.textview_age);
}
}
public MyAdapter(Query query, #Nullable ArrayList<MyItem> items,
#Nullable ArrayList<String> keys) {
super(query, items, keys);
}
#Override public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item, parent, false);
return new ViewHolder(view);
}
#Override public void onBindViewHolder(MyAdapter.ViewHolder holder, int position) {
MyItem item = getItem(position);
holder.textViewName.setText(item.getName());
holder.textViewAge.setText(String.valueOf(item.getAge()));
}
#Override protected void itemAdded(MyItem item, String key, int position) {
Log.d("MyAdapter", "Added a new item to the adapter.");
}
#Override protected void itemChanged(MyItem oldItem, MyItem newItem, String key, int position) {
Log.d("MyAdapter", "Changed an item.");
}
#Override protected void itemRemoved(MyItem item, String key, int position) {
Log.d("MyAdapter", "Removed an item from the adapter.");
}
#Override protected void itemMoved(MyItem item, String key, int oldPosition, int newPosition) {
Log.d("MyAdapter", "Moved an item.");
}
}
And here is the adapter that is extending the main adapter so it can work with firebase
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.ViewGroup;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.Query;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
/**https://firebase.google.com/support/guides/firebase-android.
* <p>
* This class is a generic way of backing an Android RecyclerView with a Firebase location.
* It handles all of the child events at the given Firebase location.
* It marshals received data into the given class type.
* Extend this class and provide an implementation of the abstract methods, which will notify when
* the adapter list changes.
* <p>
* This class also simplifies the management of configuration change (e.g.: device rotation)
* allowing the restore of the list.
*
* #param <T> The class type to use as a model for the data contained in the children of the
* given Firebase location
*/
public abstract class FirebaseRecyclerAdapter<ViewHolder extends RecyclerView.ViewHolder, T> extends RecyclerView.Adapter<ViewHolder> {
private Query mQuery;
private ArrayList<T> mItems;
private ArrayList<String> mKeys;
/**
* #param query The Firebase location to watch for data changes.
* Can also be a slice of a location, using some combination of
* <code>limit()</code>, <code>startAt()</code>, and <code>endAt()</code>.
*/
public FirebaseRecyclerAdapter(Query query) {
this(query, null, null);
}
/**
* #param query The Firebase location to watch for data changes.
* Can also be a slice of a location, using some combination of
* <code>limit()</code>, <code>startAt()</code>, and <code>endAt()</code>.
* #param items List of items that will load the adapter before starting the listener.
* Generally null or empty, but this can be useful when dealing with a
* configuration change (e.g.: reloading the adapter after a device rotation).
* Be careful: keys must be coherent with this list.
* #param keys List of keys of items that will load the adapter before starting the listener.
* Generally null or empty, but this can be useful when dealing with a
* configuration change (e.g.: reloading the adapter after a device rotation).
* Be careful: items must be coherent with this list.
*/
public FirebaseRecyclerAdapter(Query query,
#Nullable ArrayList<T> items,
#Nullable ArrayList<String> keys) {
this.mQuery = query;
if (items != null && keys != null) {
this.mItems = items;
this.mKeys = keys;
} else {
mItems = new ArrayList<T>();
mKeys = new ArrayList<String>();
}
query.addChildEventListener(mListener);
}
private ChildEventListener mListener = new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
String key = dataSnapshot.getKey();
if (!mKeys.contains(key)) {
T item = getConvertedObject(dataSnapshot);
int insertedPosition;
if (previousChildName == null) {
mItems.add(0, item);
mKeys.add(0, key);
insertedPosition = 0;
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mItems.size()) {
mItems.add(item);
mKeys.add(key);
} else {
mItems.add(nextIndex, item);
mKeys.add(nextIndex, key);
}
insertedPosition = nextIndex;
}
notifyItemInserted(insertedPosition);
itemAdded(item, key, insertedPosition);
}
}
#Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {
String key = dataSnapshot.getKey();
if (mKeys.contains(key)) {
int index = mKeys.indexOf(key);
T oldItem = mItems.get(index);
T newItem = getConvertedObject(dataSnapshot);
mItems.set(index, newItem);
notifyItemChanged(index);
itemChanged(oldItem, newItem, key, index);
}
}
#Override
public void onChildRemoved(DataSnapshot dataSnapshot) {
String key = dataSnapshot.getKey();
if (mKeys.contains(key)) {
int index = mKeys.indexOf(key);
T item = mItems.get(index);
mKeys.remove(index);
mItems.remove(index);
notifyItemRemoved(index);
itemRemoved(item, key, index);
}
}
#Override
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
String key = dataSnapshot.getKey();
int index = mKeys.indexOf(key);
T item = getConvertedObject(dataSnapshot);
mItems.remove(index);
mKeys.remove(index);
int newPosition;
if (previousChildName == null) {
mItems.add(0, item);
mKeys.add(0, key);
newPosition = 0;
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mItems.size()) {
mItems.add(item);
mKeys.add(key);
} else {
mItems.add(nextIndex, item);
mKeys.add(nextIndex, key);
}
newPosition = nextIndex;
}
notifyItemMoved(index, newPosition);
itemMoved(item, key, index, newPosition);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e("FirebaseListAdapter", "Listen was cancelled, no more updates will occur.");
}
};
#Override
public abstract ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
#Override
public abstract void onBindViewHolder(ViewHolder holder, final int position);
#Override
public int getItemCount() {
return (mItems != null) ? mItems.size() : 0;
}
/**
* Clean the adapter.
* ALWAYS call this method before destroying the adapter to remove the listener.
*/
public void destroy() {
mQuery.removeEventListener(mListener);
}
/**
* Returns the list of items of the adapter: can be useful when dealing with a configuration
* change (e.g.: a device rotation).
* Just save this list before destroying the adapter and pass it to the new adapter (in the
* constructor).
*
* #return the list of items of the adapter
*/
public ArrayList<T> getItems() {
return mItems;
}
/**
* Returns the list of keys of the items of the adapter: can be useful when dealing with a
* configuration change (e.g.: a device rotation).
* Just save this list before destroying the adapter and pass it to the new adapter (in the
* constructor).
*
* #return the list of keys of the items of the adapter
*/
public ArrayList<String> getKeys() {
return mKeys;
}
/**
* Returns the item in the specified position
*
* #param position Position of the item in the adapter
* #return the item
*/
public T getItem(int position) {
return mItems.get(position);
}
/**
* Returns the position of the item in the adapter
*
* #param item Item to be searched
* #return the position in the adapter if found, -1 otherwise
*/
public int getPositionForItem(T item) {
return mItems != null && mItems.size() > 0 ? mItems.indexOf(item) : -1;
}
/**
* Check if the searched item is in the adapter
*
* #param item Item to be searched
* #return true if the item is in the adapter, false otherwise
*/
public boolean contains(T item) {
return mItems != null && mItems.contains(item);
}
/**
* ABSTRACT METHODS THAT MUST BE IMPLEMENTED BY THE EXTENDING ADAPTER.
*/
/**
* Called after an item has been added to the adapter
*
* #param item Added item
* #param key Key of the added item
* #param position Position of the added item in the adapter
*/
protected void itemAdded(T item, String key, int position) {
}
/**
* Called after an item changed
*
* #param oldItem Old version of the changed item
* #param newItem Current version of the changed item
* #param key Key of the changed item
* #param position Position of the changed item in the adapter
*/
protected void itemChanged(T oldItem, T newItem, String key, int position) {
}
/**
* Called after an item has been removed from the adapter
*
* #param item Removed item
* #param key Key of the removed item
* #param position Position of the removed item in the adapter
*/
protected void itemRemoved(T item, String key, int position) {
}
/**
* Called after an item changed position
*
* #param item Moved item
* #param key Key of the moved item
* #param oldPosition Old position of the changed item in the adapter
* #param newPosition New position of the changed item in the adapter
*/
protected void itemMoved(T item, String key, int oldPosition, int newPosition) {
}
/**
* Converts the data snapshot to generic object
*
* #param snapshot Result
* #return Data converted
*/
protected T getConvertedObject(DataSnapshot snapshot) {
return snapshot.getValue(getGenericClass());
}
/**
* Returns a class reference from generic T.
*/
#SuppressWarnings("unchecked")
private Class<T> getGenericClass() {
return (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1];
}
}
Useful links:
Generic Firebase recyclerview list for Android
Hope it helps
I am using parse-server to develop the app which uses RecyclerView to display image items.
but the problem is that the items displayed in the view changed every time I scrolled up and down.
I want to know what is the problem on my code.
if you see below images, you can find the items are changing their position.
I tried to make holder image become null before call the holder again. but it's not working. I guess that the item's position number is changed when I call the item again.but I can't find the cause of the situation
enter image description here
enter image description here
RecyclerParseAdapter.java
public class MyTimelineAdapter extends RecyclerParseAdapter {
private interface OnQueryLoadListener<ParseObject> {
public void onLoading();
public void onLoaded(List<ParseObject> objects, Exception e);
}
private static ParseQueryAdapter.QueryFactory<ParseObject> queryFactory;
private static List<OnQueryLoadListener<ParseObject>> onQueryLoadListeners;
private static List<List<ParseObject>> objectPages;
private static ArrayList<ParseObject> items;
private static int currentPage;
private static RequestManager requestManager;
public MyTimelineAdapter(Context context, RequestManager requestManager) {
super(context);
this.requestManager = requestManager;
this.onQueryLoadListeners = new ArrayList<>();
this.currentPage = 0;
this.objectPages = new ArrayList<>();
this.items = new ArrayList<>();
this.queryFactory = new ParseQueryAdapter.QueryFactory<ParseObject>() {
#Override
public ParseQuery<ParseObject> create() {
ParseQuery<ParseObject> query = ParseQuery.getQuery("ImageClassName");
query.setCachePolicy(ParseQuery.CachePolicy.CACHE_THEN_NETWORK);
query.whereEqualTo("status", true);
query.orderByDescending("createdAt");
return query;
}
};
loadObjects(currentPage);
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View timelineView;
timelineView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_timeline_item2, parent, false);
TimelineItemViewHolder timelineItemViewHolder = new TimelineItemViewHolder(timelineView);
return timelineItemViewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
final ParseObject timelineOb = getItem(position);
FunctionPost functionPost = new FunctionPost(context);
functionPost.TimelineArtistPostAdapterBuilder( timelineOb, holder, requestManager);
//기능 추가
}
#Override
public int getItemCount() {
return items.size();
}
#Override
public ParseObject getItem(int position) {
return items.get(position);
}
#Override
public void loadObjects(final int page) {
final ParseQuery<ParseObject> query = this.queryFactory.create();
if (this.objectsPerPage > 0 && this.paginationEnabled) {
this.setPageOnQuery(page, query);
}
this.notifyOnLoadingListeners();
if (page >= objectPages.size()) {
objectPages.add(page, new ArrayList<ParseObject>());
}
query.findInBackground(new FindCallback<ParseObject>() {
#Override
public void done(List<ParseObject> foundObjects, ParseException e) {
if ((e != null) && ((e.getCode() == ParseException.CONNECTION_FAILED) || (e.getCode() != ParseException.CACHE_MISS))) {
hasNextPage = true;
} else if (foundObjects != null) {
// Only advance the page, this prevents second call back from CACHE_THEN_NETWORK to
// reset the page.
if (page >= currentPage) {
currentPage = page;
// since we set limit == objectsPerPage + 1
hasNextPage = (foundObjects.size() > objectsPerPage);
}
if (paginationEnabled && foundObjects.size() > objectsPerPage) {
// Remove the last object, fetched in order to tell us whether there was a "next page"
foundObjects.remove(objectsPerPage);
}
List<ParseObject> currentPage = objectPages.get(page);
currentPage.clear();
currentPage.addAll(foundObjects);
syncObjectsWithPages(items, objectPages);
// executes on the UI thread
notifyDataSetChanged();
}
notifyOnLoadedListeners(foundObjects, e);
}
});
}
public void loadNextPage() {
if (items.size() == 0) {
loadObjects(0);
} else {
loadObjects(currentPage + 1);
}
}
public void syncObjectsWithPages(ArrayList<ParseObject> items, List<List<ParseObject>> objectPages) {
items.clear();
for (List<ParseObject> pageOfObjects : objectPages) {
items.addAll(pageOfObjects);
}
}
protected void setPageOnQuery(int page, ParseQuery<ParseObject> query) {
query.setLimit(this.objectsPerPage + 1);
query.setSkip(page * this.objectsPerPage);
}
public void addOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.add(listener);
}
public void removeOnQueryLoadListener(OnQueryLoadListener<ParseObject> listener) {
this.onQueryLoadListeners.remove(listener);
}
public void notifyOnLoadingListeners() {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoading();
}
}
public void notifyOnLoadedListeners(List<ParseObject> objects, Exception e) {
for (OnQueryLoadListener<ParseObject> listener : this.onQueryLoadListeners) {
listener.onLoaded(objects, e);
}
}
}
I did find the problem
I add overide method in the adapter then It works find.
#Override
public int getItemViewType(int position) {
return position;
}
I am not sure why it happens now. any one help me to know the cause of problem?
I has a similar problem the other day see this post. onBindViewHolder needs to know how to display the row when it's called. I returned two different view types depending on the need in getItemViewType, inflated the view type conditionally in onCreateViewHolder, then I was able to set the data on the ViewHolder as needed.