I am trying to show call log list using Room-Paging-LiveData-ViewModel. Without paging my code works perfectly. And I want to use paging also.
In my database I have total 25 call log record. The first 9 call log is showing in the list.
By debugging I found that while reading data in view model via Dao
, it is returning list of size 25. But only first 9 of these are non null. All other entries in the list is null.
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.
And the observe method of view model is getting called only once, the first time only.
I think I am doing something wrong.
Here is the code below
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;
}
}
Later I tried to make
private LiveData<List<CallLogItem>> callLogList;
to Paged list like
private LiveData<PagedList<CallLogItem>> callLogList;
But I found no proper way to transform into that.
In order to be able to return a mapped PagedList
you should know that DataSource
and DataSource.Factory
has map()
and mapByPage()
.
You can map the DataSource Factory items with mapByPage()
instead using Transformation
, like this:
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()
EDIT
According 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 aPagedList.Config
.
You just need to create a PagedList.Config
and add this to LivePagedListBuilder
instantiation.
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(3).build();
LiveData<PagedList<CallLogItem>> callLogItems = new LivePagedListBuilder<>(dataSourceFactoryCallLogItem, pagedListConfig).build()
For paged list adapter there is 2 thing to note.
1. Data will be handled internally and no need to declare any data structure to handle data manually.
2. There is a default method called submitList
in PagedListAdapter
. It is necessary to submit paged list over that method to the adapter.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With