【问题标题】:Failing to load next data with android Paging library无法使用 android Paging 库加载下一个数据
【发布时间】:2019-04-17 06:33:30
【问题描述】:

我正在尝试使用 Room-Paging-LiveData-ViewModel 显示通话记录列表。 无需分页,我的代码就可以完美运行。我也想使用分页。

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

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

我预计 null 数据将在分页后立即刷新 列表。但问题是 null 永远不会被刷新 有效数据。

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

我认为我做错了什么。

下面是代码

片段

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

}

适配器

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

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

视图模型

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; 

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

【问题讨论】:

    标签: android android-room android-livedata androidx android-paging


    【解决方案1】:

    为了能够返回映射的PagedList,您应该知道DataSourceDataSource.Factory 具有map()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

    使用占位符时,PagedList 始终是数据集的完整大小。 get(N) 返回数据集中的第 N 项,如果尚未加载,则返回 null。

    没有空占位符,PagedList 是已加载数据的子列表。 PagedList 的大小是当前加载项的数量,get(N) 返回第 N 个加载项。这不一定是数据集中的第 N 项。

    占位符默认启用,但可以通过两种方式禁用。如果 DataSource 在其初始加载时未计算其数据集,或者在构建 PagedList.Config 时将 false 传递给 setEnablePlaceholders(boolean),则它们将被禁用。

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

    PagedList.Config pagedListConfig =
                    (new PagedList.Config.Builder())
                            .setEnablePlaceholders(false)
                            .setPageSize(3).build();
    
    LiveData<PagedList<CallLogItem>> callLogItems = new LivePagedListBuilder<>(dataSourceFactoryCallLogItem, pagedListConfig).build()
    

    【讨论】:

    • 我已经尝试过,我发现,在我的适配器中,refreshData 方法只被调用一次,具有完整的列表大小。列表大小为 25,但包含 9 个实际数据,其他为空。如何配置 PagedList 以加载数据列表中的下一页?
    • 尝试创建一个PagedList.Config 并将其添加到LivePagedListBuilder 实例化中。 new LivePagedListBuilder&lt;&gt;(dataSourceFactoryCallLogItem, pagedListConfig).build()
    • PagedList.Config 上将占位符更改为 false 就像这样 .setEnablePlaceholders(false)
    • 谢谢。这将有助于删除空元素,我知道。但问题是我的适配器类的错误实现。我已经编辑了您的答案以使其成为正确答案,并正在等待编辑通过同行评审。但现在我看到编辑不见了。
    • 我认为您可以编辑您的问题,让问题更清晰,然后为此创建正确的答案。
    【解决方案2】:

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

    修改后的适配器

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

    修改后的数据传递给适配器

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

    【讨论】:

    • 只加载第一组数据
    • 您从哪里调用此解决方案的 refreshData()??
    • refreshData() 不应被调用。我们必须致电submitList() 以使PagedListAdapter 正常工作。如果我们不使用submitList(),它只会加载第一组数据。为了确保加载下一组,我们必须调用适配器的submitList() 方法。
    • 是的,想通了。主要思想是避免将任何列表(数据源)显式发送到适配器。您仍然可以调用 refreshData() 并将任何其他项目(即视图模型或点击侦听器的实例)传递给适配器。但不是列表。感谢您的回复
    • 您好!我一直在玩新的 Paging 3 库。我已经在我的应用程序中成功实现了它,它只显示了一个联系人列表。现在我正在实现搜索联系人功能。我只是在 Flow 上应用地图和过滤器操作,这给了我精确的结果。但是,我必须滚动到底部才能更新所有联系人列表。否则,搜索仅适用于最先显示的联系人(因为底部的联系人尚未加载到流程中)。我想在后台强制加载数据(下一页)。有什么解决方法吗?
    猜你喜欢
    • 2022-09-27
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多