Обновления Android PagedList

Мой вопрос, как обновить элемент в PagedList?

В моем случае есть ListActivity и DetailsActivity. В списке действий используется компонент "Пейджинг" для получения сообщений из сети (только), который отображается в виде утилиты с помощью выгружаемого адаптера. Когда пользователь нажимает на какое-либо сообщение, мне нужно получить детали сообщения и показать его в DetailsActivity. Я делаю еще один запрос к серверу, и он возвращает мне сообщения. После этого вызова сервер увеличивает значение viewsCount этого сообщения, и когда пользователь возвращается в список сообщений, мне нужно обновить этот счетчик в элементе списка.

Вопрос в том, как обновить отдельный элемент (запись) в этом PagedList, потому что мне не нужно перезагружать весь список с самого начала, чтобы обновить только один элемент.

5 ответов

PagedList является неизменным

Я бы посоветовал вам не использовать библиотеку подкачки, потому что она предполагает, что ваши данные неизменны, что практически невозможно в практическом случае использования.

Вы можете проверить это в официальных документах. Это говорит:

Если у вас есть более детальные сигналы обновления, такие как сетевой API, сигнализирующий об обновлении одного элемента в списке, рекомендуется загружать данные из сети в память. Затем представьте эти данные в PagedList через DataSource, который оборачивает снимок в памяти. Каждый раз, когда изменяется копия в памяти, аннулируется предыдущий источник данных, и может быть создан новый источник, содержащий новое состояние моментального снимка.

т.е.

Вы должны получить данные с сервера и затем сохранить их в локальной БД с использованием библиотеки помещений, а затем всякий раз, когда вам нужно обновить какой-либо элемент, обновите элемент в локальной БД и, в свою очередь, комната отразит эти изменения в пользовательском интерфейсе (используя LiveData),

Но тогда вам придется делать пейджинг с использованием локальной БД.
В случае комнаты у вас будет что-то вроде этого.

@Dao
interface FeedDao {
@Query("SELECT * FROM feed ")
fun selectPaged(): DataSource.Factory<Int, Feed>
}

Но тогда вы должны позаботиться о многих других вещах, таких как:

Что делать, если один объект удаляется с удаленного сервера. Как мы собираемся это заметить?
И мы должны удалить его из нашей локальной БД также.

Вы можете прочитать эту статью, чтобы решить эту проблему. Он объясняет все проблемы в библиотеке подкачки и возможный обходной путь, если вы действительно хотите использовать библиотеку подкачки.

Я предлагаю использовать эту библиотеку и реализовать свою собственную нумерацию страниц.

PagedListAdapter использует DiffUtil.ItemCallback это обновит существующий элемент, только если он считает его "новым" элементом. Мне кажется, что вам нужно пойти на сервер, чтобы получить новый счетчик просмотров, но также вы не хотите, чтобы незатронутые элементы перерисовывались. Этого можно избежать, добавив свойство количества просмотров в DiffUtil.ItemCallback чтобы определить, что такое новый предмет. Элементы с новым количеством просмотров будут перерисованы, другие - нет.

Я предполагаю, что исходный запрос, который получает первый список, возвращает объекты, которые имеют viewsCount поле в них.

Я реализовал разбиение на страницы в программе повторного просмотра, вы можете проверить пример на git hub https://github.com/kunal-mahajan/GitRepositoryList Вы не будете перезагружать данные снова и снова, используя следующий код.

Шаг 1. Создайте класс 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 {
    }
}

Шаг 2. Создайте класс: 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();
    }
}

Шаг 3: Создайте свой собственный PaginationAdaptor, например:

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

После этого вызова сервер увеличивает значение viewsCount этого сообщения, и когда пользователь возвращается в список сообщений, мне нужно обновить этот счетчик в элементе списка.

Теперь у вас есть список и подробности, нажав на элемент списка, который вы вызываете веб- сервис, чтобы обновить количество просмотров поста.

@Override
public void onBackPressed() {
   Call broadcast/interface        
}

Так что теперь вы можете сделать это двумя способами

  1. Создайте широковещательный приемник в списке, переопределите метод onbackpress и отправьте широковещательную рассылку в список onBackPressed() удалить супер вызов. если ваш веб-сервис успешно вызван, тогда отправьте широковещательную рассылку в список при нажатии или обновлении, или при выполнении любой другой задачи.

  2. Создайте интерфейс и вызовите его из метода обратной печати.

Можете ли вы сделать это на 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
                }
            }
        }
Другие вопросы по тегам