Android PagedList updates
Asked Answered
L

4

14

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(only) and shows it in recycler view using paged adapter. When user is pressing on some post, I need to get post details and show it at the DetailsActivity. I am making another request to the server and it returns me post details. After that call, server increases viewsCount value of that post and when user returns to the posts list, I need to update that counter at the list item.

The question is, how to update single item (post), in that PagedList, cause I don't need to reload all list from the beginning just to update one single item.

Lucre answered 14/1, 2018 at 23:12 Comment(3)
OP - did you ever get an answer to this?Scopula
You are already incrementing view counts on the server. Why don't you pack the new count with the post details response and retrieve it to the client? I don't think if you need another request just to retrieve a single view count value.Gratin
@MeanCoder but if we've paginated far down the list, it's better to update that one item because we don't want pagination to start at square one again.Scopula
J
24

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.

Jocular answered 30/10, 2018 at 5:24 Comment(2)
Wow, thanks for the thorough explanation. This is way more complex than it needed to be. :/ I wish pagedlists could just be mutated.Scopula
Is there a way to cache this data in memory and update it there?Gnash
P
2

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.

Pansie answered 23/10, 2018 at 16:4 Comment(1)
Once I get the original list, though, how do I update the item I want? Once we have data, the PagedList becomes immutable doesn't it?Scopula
M
1

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));
        }
    }
}
Moneylender answered 29/10, 2018 at 3:44 Comment(0)
A
0

Can you do this on the PagedListAdapter?

    //first is the id
    //second is the viewcount
    var viewCountPair: Pair<Int,Int>? = null
        set(value) {
            field = value
            for (i: Int in 0..itemCount) {
                if (getItem(i)?.id == pair.first) {
                    getItem(i)?.viewCount = pair.second
                    notifyItemChanged(i)
                    break
                }
            }
        }
Abaft answered 1/4, 2021 at 13:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.