无法使用 android 分页库加载下一个数据

Failing to load next data with android Paging library

我正在尝试使用 Room-Paging-LiveData-ViewModel 显示通话记录列表。 没有分页我的代码工作完美。我也想使用分页。

在我的数据库中我总共有25条通话记录。前 9 条通话记录显示在列表中。

通过调试,我发现在通过 Dao 读取视图模型中的数据时,它返回大小为 25 的列表。但是其中只有前 9 个是非空的。列表中的所有其他条目均为空。

I am expecting the null data will refresh soon as this is a paged list. But the problem is the null are never getting refreshed with valid data.

而且视图模型的观察方法只被调用一次,只有第一次。

我觉得我做错了什么。

下面是代码

The fragment

public class CallLogListFragment extends Fragment {
    private static final String TAG = "RecentCallsFragment";

    public static String getTAG() {
        return TAG;
    }

    public static Fragment newInstance() {
        return new CallLogListFragment();
    }

    public CallLogListFragment() {
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        FragmentCallLogListBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_call_log_list, container, false);
        CallLogListAdapter adapter = new CallLogListAdapter();
        binding.list.setAdapter(adapter);
        CallLogListViewModel model = ViewModelProviders.of(this).get(CallLogListViewModel.class);
        model.getCallLogList().observe(this, adapter::refreshData);
        return binding.getRoot();
    }

}

The Adapter

public class CallLogListAdapter extends PagedListAdapter<CallLogItem, CallLogListAdapter.ViewHolder> {
    CallLogListAdapter() {
        super(DIFF_CALLBACK);
    }

    void refreshData(List<CallLogItem> data) {
        DiffUtil.DiffResult calculatedDiff = DiffUtil.calculateDiff(new CallLogListDiffUtilCallBack(this.data, data));
        this.data.clear();
        this.data.addAll(data);
        calculatedDiff.dispatchUpdatesTo(this);
    }

    private List<CallLogItem> data = new ArrayList<>();

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()),
                R.layout.call_log_list_single_item,
                parent, false
        ));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        CallLogItem item = data.get(position);
        holder.binding.setCallLog(item);
    }

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

    class ViewHolder extends RecyclerView.ViewHolder {
        public CallLogListSingleItemBinding binding;
        public ViewHolder(@NonNull CallLogListSingleItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

     private static DiffUtil.ItemCallback<CallLogItem> DIFF_CALLBACK =
        new DiffUtil.ItemCallback<CallLogItem>() {
            @Override
            public boolean areItemsTheSame(CallLogItem oldItem, CallLogItem newItem) {
                return oldItem.getHeaderDateVisibility() == newItem.getHeaderDateVisibility()
                        && oldItem.getCallId().equals(newItem.getCallId());
            }

            @Override
            public boolean areContentsTheSame(@NonNull CallLogItem oldItem, @NonNull CallLogItem newItem) {
                return areItemsTheSame(oldItem, newItem);
            }
        };
}

The Dao

@Dao
public interface CallLogDao extends BaseDao<CallLog>{
    @Query("SELECT * FROM log")
    List<CallLog> getAll();

    @Query("SELECT * FROM log WHERE number=:number")
    CallLog findByName(String number);

    @Query("SELECT * FROM log order by date desc")
    LiveData<List<CallLog>> getAllLive();

    @Query("SELECT * FROM log order by date desc")
    DataSource.Factory<Integer, CallLog> getAllLivePaged();
}

The ViewModel

public class CallLogListViewModel extends ViewModel {
    private LiveData<List<CallLogItem>> callLogList;

    public CallLogListViewModel() {
        callLogList = Transformations.map(new LivePagedListBuilder<>(AppDatabase.get().callLogDao().getAllLivePaged(), 3).build(), input -> {
            List<CallLogItem> list = new ArrayList<>();
            for (int i = 0; i < input.size(); i++) {
                boolean isHeader = true;
                CallLog callLog = input.get(i);
                if(callLog!=null) {
                    if (i > 0) {
                        CallLog previousCallLog = input.get(i - 1);
                        if(previousCallLog!=null) {
                            isHeader = TimeFormat.isDifferentDate(callLog.date, previousCallLog.date);
                        }
                    }
                    list.add(CallLogItem.Companion.from(callLog, isHeader));
                }
            }
            return list;
        });
    }

    LiveData<List<CallLogItem>> getCallLogList() {
        return callLogList;
    }
}

后来我试着做了

private LiveData<List<CallLogItem>> callLogList; 

之类的分页列表
private LiveData<PagedList<CallLogItem>> callLogList; 

但是我没有找到合适的方法来转换成那个。

为了能够 return 映射 PagedList 你应该知道 DataSourceDataSource.Factorymap()mapByPage() . 您可以使用 mapByPage() 映射 DataSource Factory 项,而不是使用 Transformation,如下所示:

DataSource.Factory<Integer, CallLog> dataSourceFactoryCallLog = AppDatabase.get().callLogDao().getAllLivePaged();
                
DataSource.Factory<Integer, CallLogItem> dataSourceFactoryCallLogItem = dataSourceFactoryCallLog.mapByPage(input -> {
   List<CallLogItem> list = new ArrayList<>();
   for (int i = 0; i < input.size(); i++) {
       boolean isHeader = true;
       CallLog callLog = input.get(i);
       if(callLog!=null) {
           if (i > 0) {
               CallLog previousCallLog = input.get(i - 1);
               if(previousCallLog!=null) {
                   isHeader = TimeFormat.isDifferentDate(callLog.date, previousCallLog.date);
               }
           }
           list.add(CallLogItem.Companion.from(callLog, isHeader));
       }
   }
   return list;
 });
        
LiveData<PagedList<CallLogItem>> callLogItems = new LivePagedListBuilder<>(dataSourceFactoryCallLogItem, 3).build()

编辑

根据PagedList documentation

With placeholders, the PagedList is always the full size of the data set. get(N) returns the Nth item in the data set, or null if its not yet loaded.

Without null placeholders, the PagedList is the sublist of data that has already been loaded. The size of the PagedList is the number of currently loaded items, and get(N) returns the Nth loaded item. This is not necessarily the Nth item in the data set.

Placeholders are enabled by default, but can be disabled in two ways. They are disabled if the DataSource does not count its data set in its initial load, or if false is passed to setEnablePlaceholders(boolean) when building a PagedList.Config.

您只需创建一个 PagedList.Config 并将其添加到 LivePagedListBuilder 实例化。

PagedList.Config pagedListConfig =
                (new PagedList.Config.Builder())
                        .setEnablePlaceholders(false)
                        .setPageSize(3).build();

LiveData<PagedList<CallLogItem>> callLogItems = new LivePagedListBuilder<>(dataSourceFactoryCallLogItem, pagedListConfig).build()

对于分页列表适配器,有两点需要注意。 1.数据将在内部处理,无需声明任何数据结构来手动处理数据。 2. PagedListAdapter中有一个默认方法叫submitList。有必要通过该方法将分页列表提交给适配器。

Modified adapter

public class CallLogListAdapter extends PagedListAdapter<CallLogItem, CallLogListAdapter.ViewHolder> {
    private Context context;
    CallLogListAdapter(Context context) {
        super(DIFF_CALLBACK);
        this.context = context;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new ViewHolder(DataBindingUtil.inflate(
                LayoutInflater.from(parent.getContext()),
                R.layout.call_log_list_single_item,
                parent, false
        ));
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        CallLogItem item = getItem(position);
        if (item != null) {
            holder.binding.setCallLog(item);
            ImageUtil.setImage(holder.binding.ivProfileImage, item.getImageUrl(), item.getName());
        } else {
            holder.binding.invalidateAll();
        }
    }


    class ViewHolder extends RecyclerView.ViewHolder {
        public CallLogListSingleItemBinding binding;

        public ViewHolder(@NonNull CallLogListSingleItemBinding binding) {
            super(binding.getRoot());
            this.binding = binding;
        }
    }

    private static DiffUtil.ItemCallback<CallLogItem> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<CallLogItem>() {
                @Override
                public boolean areItemsTheSame(CallLogItem oldItem, CallLogItem newItem) {
                    return oldItem.getHeaderDateVisibility() == newItem.getHeaderDateVisibility()
                            && oldItem.getCallId()!=null &&  oldItem.getCallId().equals(newItem.getCallId());
                }

                @Override
                public boolean areContentsTheSame(@NonNull CallLogItem oldItem, @NonNull CallLogItem newItem) {
                    return areItemsTheSame(oldItem, newItem);
                }
            };
}

Modified Data passing to adapter

CallLogListViewModel model = ViewModelProviders.of(this).get(CallLogListViewModel.class);
model.getCallLogList().observe(this, adapter::submitList);