My question is how to update item in PagedList?
In my case, there are ListActivity and DetailsActivity. List activity is using Paging component to get posts from network
PagedListAdapter
uses a DiffUtil.ItemCallback
that will only update an existing item if the it considers it a "new" item. It seems to me that you do need to go to the server in order to get the new view count, but also you do not want unaffected items to redraw. This can be prevented by adding the view count property to the DiffUtil.ItemCallback
to determine what a new item is. Items with a new view count will be redrawn, others won't.
I am assuming that the original request that retrieves the first list returns objects that have a viewsCount
field in them.
I have implemented the pagination in recyclerview you can check the example on git hub https://github.com/kunal-mahajan/GitRepositoryList You wont reload the data again and again by using following code.
Step 1: Create class DynamicLoadingAdapter
import android.content.Context;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public abstract class DynamicLoadingAdapter extends RecyclerView.Adapter<ViewHolder> {
private static final int ITEM = 3;
private static final int LOADING = 4;
private static final int NO_DATA = 5;
protected List records;
protected Context context;
private LoadingObj loadingObj = new LoadingObj();
private NoDataAvailableObj noDataAvailableObj = new NoDataAvailableObj();
public DynamicLoadingAdapter(Context context) {
this.context = context;
records = new ArrayList<>();
records.add(loadingObj);
}
private int getIntToDP(int px) {
return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, px, context.getResources().getDisplayMetrics()));
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = null;
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType) {
case ITEM:
viewHolder = getViewHolder(parent, inflater);
break;
case LOADING:
viewHolder = new DynamicLoadingAdapter.LoadingVH(getProgressBar());
break;
case NO_DATA:
viewHolder = new ViewHolder(getNoDataTextView()) {
};
}
return viewHolder;
}
protected abstract String getEmptyText();
@NonNull
protected abstract ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater);
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (getItemViewType(position) == ITEM)
setValuesOnBind(holder, position);
}
protected abstract void setValuesOnBind(ViewHolder holder, int position);
@Override
public int getItemViewType(int position) {
Object o = records.get(position);
if (o instanceof LoadingObj)
return LOADING;
if (o instanceof NoDataAvailableObj)
return NO_DATA;
return ITEM;
}
void setLoadingFinished() {
records.remove(records.size() - 1);
if (records.size() == 0) records.add(new NoDataAvailableObj());
notifyDataSetChanged();
}
@Override
public int getItemCount() {
return records.size();
}
int getItemLoadedCount() {
int l = records.size();
if (records.get(records.size() - 1).equals(loadingObj))
l--;
if (l > 0 && records.get(0).equals(noDataAvailableObj))
l--;
return l;
}
void add(List list) {
for (int i = 0; i < list.size(); i++) {
records.add(records.size() - 1, list.get(i));
}
notifyDataSetChanged();
}
private ProgressBar getProgressBar() {
ProgressBar progressBar = new ProgressBar(context);
int h = getIntToDP(30);
int w = ViewGroup.LayoutParams.MATCH_PARENT;
LinearLayout.LayoutParams progressParams = new LinearLayout.LayoutParams(w, h);
int margin = getIntToDP(5);
progressParams.setMargins(margin, margin, margin, margin);
progressParams.gravity = Gravity.CENTER;
progressBar.setLayoutParams(progressParams);
return progressBar;
}
public TextView getNoDataTextView() {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
TextView textView = new TextView(context);
textView.setGravity(Gravity.CENTER);
textView.setText(getEmptyText());
textView.setTypeface(null, Typeface.ITALIC);
params.setMargins(0, getIntToDP(7), 0, getIntToDP(7));
textView.setLayoutParams(params);
return textView;
}
private static class LoadingObj {
}
private class LoadingVH extends ViewHolder {
public LoadingVH(View itemView) {
super(itemView);
}
}
private class NoDataAvailableObj {
}
}
Step 2: Create class: DynamicLoadingListHelper
import android.content.Context;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import java.util.List;
/**
* Created by Kunal.Mahajan on 7/23/2018.
*/
public abstract class DynamicLoadingListHelper {
private final DynamicLoadingAdapter adapter;
private final Context context;
private LinearLayout containerLayout;
private RecyclerView rv;
private int totalRecords = -1;
private int pageCount = 0;
public DynamicLoadingListHelper(Context context, LinearLayout containerLayout, DynamicLoadingAdapter adapter) {
this.adapter = adapter;
this.context = context;
this.containerLayout = containerLayout;
init();
}
public void init() {
final LinearLayoutManager lm = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
rv = new RecyclerView(context);
rv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
containerLayout.addView(rv);
rv.setLayoutManager(lm);
rv.setItemAnimator(new DefaultItemAnimator());
rv.setAdapter(adapter);
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
int lastLoadCountReq = Integer.MIN_VALUE;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (lm.findLastVisibleItemPosition() == adapter.getItemCount() - 1 && newState == RecyclerView.SCROLL_STATE_IDLE && adapter.getItemLoadedCount() < totalRecords && adapter.getItemLoadedCount() > lastLoadCountReq) {
lastLoadCountReq = adapter.getItemLoadedCount();
loadNextPage();
}
}
});
rv.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
@Override
public void onChildViewAttachedToWindow(View view) {
boolean isScrolled = layoutManager.findViewByPosition(0) != layoutManager.findViewByPosition(layoutManager.findFirstCompletelyVisibleItemPosition());
if (isScrolled)
return;
if (!(view instanceof ProgressBar))
return;
if (isRecyclerScrollable()) {
loadNextPage();
}
}
public void onChildViewDetachedFromWindow(View view) {
}
});
loadNextPage();
}
private void loadNextPage() {
pageCount++;
loadData(pageCount);
}
private boolean isRecyclerScrollable() {
LinearLayoutManager layoutManager = (LinearLayoutManager) rv.getLayoutManager();
RecyclerView.Adapter adapter = rv.getAdapter();
if (layoutManager == null || adapter == null) return false;
return layoutManager.findLastCompletelyVisibleItemPosition() < totalRecords;
}
protected abstract void loadData(int offset);
public void dataLoaded(List list, int totalPage) {
if (totalPage <= 0) {
adapter.setLoadingFinished();
return;
}
this.totalRecords = totalPage;
adapter.add(list);
if (adapter.getItemLoadedCount() >= totalPage)
adapter.setLoadingFinished();
}
}
Step 3: Create your own PaginationAdaptor eg:
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.gitfeaturelisting.R;
import com.gitfeaturelisting.component.DynamicLoadingAdapter;
import com.gitfeaturelisting.pojo.Item;
import com.squareup.picasso.Picasso;
/**
* Created by Kunal.Mahajan on 7/23/2018.
*/
public class RepoPaginationAdaptor extends DynamicLoadingAdapter {
public RepoPaginationAdaptor(Context applicationContext) {
super(applicationContext);
}
@Override
protected String getEmptyText() {
return "No repo available";
}
@NonNull
@Override
protected RecyclerView.ViewHolder getViewHolder(ViewGroup parent, LayoutInflater inflater) {
View v = inflater.inflate(R.layout.layout_repo_info_short, parent, false);
final RecyclerView.ViewHolder viewHolder = new RepoInfoVH(v);
return viewHolder;
}
@Override
protected void setValuesOnBind(RecyclerView.ViewHolder holder, int position) {
Item r = (Item) records.get(position);
RepoInfoVH rvh = (RepoInfoVH) holder;
rvh.tvName.setText(r.getDescription());
rvh.tvTitle.setText(r.getName());
rvh.tvRate.setText(context.getString(R.string.updated_on) + " " + (r.getUpdatedAt()));
Picasso.get().load(Uri.parse(r.getOwner().getAvatarUrl())).into(rvh.ivPic);
rvh.llMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(context, ActivityRepoDetail.class);
Item r = (Item) records.get(rvh.getLayoutPosition());
i.putExtra(ActivityRepoDetail.KEY_REPO_DETAIL, r);
context.startActivity(i);
}
});
}
private class
RepoInfoVH extends RecyclerView.ViewHolder {
ImageView ivPic;
TextView tvName;
TextView tvTitle;
TextView tvRate;
LinearLayout llMain;
public RepoInfoVH(View jobView) {
super(jobView);
ivPic = jobView.findViewById(R.id.layout_repo_info_short_iv_repo_pic);
tvName = (jobView.findViewById(R.id.layout_repo_info_short_tv_name));
tvTitle = (jobView.findViewById(R.id.layout_repo_info_short_tv_title));
tvRate = (jobView.findViewById(R.id.layout_repo_info_short_tv_bottom));
llMain = (jobView.findViewById(R.id.layout_repo_info_short_ll));
}
}
}
PagedList is immutable
I would suggest you do not use the paging library because it assumes that your data is immutable which is almost impossible in a practical use-case.
You can check it in the official docs. It says:
If you have more granular update signals, such as a network API signaling an update to a single item in the list, it's recommended to load data from the network into memory. Then present that data to the PagedList via a DataSource that wraps an in-memory snapshot. Each time the in-memory copy changes, invalidate the previous DataSource, and a new one wrapping the new state of the snapshot can be created.
i.e.,
You have to fetch the data from the server and then store it into local DB using room library and then whenever you have to update any item, update the item into local DB and in turn room will reflect those changes into the UI(using LiveData).
But then you have to do the paging from using local DB.
In case of room, you'll have something like this.
@Dao
interface FeedDao {
@Query("SELECT * FROM feed ")
fun selectPaged(): DataSource.Factory<Int, Feed>
}
But then you have to take care of many more things like :
what if one entity gets deleted from the remote server. How are we going to notice that?
And we have to remove it from our local DB also.
You can read this article to solve this problem. It explains all the problems in paging library and a possible workaround if you really want to use the paging library.
My suggestion is to use this library and implement your own pagination.