Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failing to load next data with android Paging library

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.

like image 371
Ifta Avatar asked Nov 14 '18 10:11

Ifta


2 Answers

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 a PagedList.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()
like image 51
haroldolivieri Avatar answered Oct 12 '22 23:10

haroldolivieri


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);
like image 42
Ifta Avatar answered Oct 13 '22 01:10

Ifta