I am using the paging library with Firebase, and set ValueEventListener on the repository to invalidate the data whenever a change happens. The problem is as soon as I trigger invalidate()
, the Observer trigger onChanged
and Adapter.submitList(items)
gets items with 0 size before the new date is loaded from Firebase via loadInitial
method on the ItemKeyedDataSource
.
The result is that the DIFF_CALLBACK
is never called on PagedListAdapter
because there is no comparison between the old list and the new items list (0 size) and when the data received it refresh the list like if I used notifyDataSetChanged()
.
The only solution so far is to delay Adapter.submitList(items)
by 5 seconds until all the data is received from Firebase, but it's not a practical solution.
public class UsersRepository {
private final static String TAG = UsersRepository.class.getSimpleName();
// [START declare_database_ref]
private DatabaseReference mDatabaseRef;
private DatabaseReference mUsersRef;
private Boolean isFirstLoaded = true;
public ValueEventListener usersChangesListener;
public UsersRepository() {
mDatabaseRef = FirebaseDatabase.getInstance().getReference();
mUsersRef = mDatabaseRef.child("users");
isFirstLoaded = true;
Log.d(TAG, "UsersRepository init. isFirstLoaded= " + isFirstLoaded);
}
public void getUsers(Long initialKey, final int size, @NonNull final ItemKeyedDataSource.LoadInitialCallback < User > callback) {
if (initialKey == null) {
Log.d(TAG, "getUsers initialKey= " + initialKey);
mUsersRef.orderByChild("created").limitToFirst(size).addListenerForSingleValueEvent(new ValueEventListener() {@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
if (dataSnapshot.exists()) {
// loop throw users value
List < User > usersList = new ArrayList < >();
for (DataSnapshot userSnapshot: dataSnapshot.getChildren()) {
usersList.add(userSnapshot.getValue(User.class));
Log.d(TAG, "getUsers dataSnapshot. getSnapshotKey= " + userSnapshot.getKey());
}
if (usersList.size() == 0) {
return;
}
Log.d(TAG, "getUsers usersList.size= " + usersList.size() + " lastkey= " + usersList.get(usersList.size() - 1).getCreatedLong());
if (callback instanceof ItemKeyedDataSource.LoadInitialCallback) {
//initial load
/*((ItemKeyedDataSource.LoadInitialCallback)callback)
.onResult(usersList, 0, 14);*/
callback.onResult(usersList);
}
} else {
Log.w(TAG, "getUsers no users exist");
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
} else {
Log.d(TAG, "getUsers initialKey= " + initialKey);
mUsersRef.orderByChild("created").startAt(initialKey).limitToFirst(size).addListenerForSingleValueEvent(new ValueEventListener() {@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
if (dataSnapshot.exists()) {
// loop throw users value
List < User > usersList = new ArrayList < >();
for (DataSnapshot userSnapshot: dataSnapshot.getChildren()) {
usersList.add(userSnapshot.getValue(User.class));
Log.d(TAG, "getUsers dataSnapshot. getSnapshotKey= " + userSnapshot.getKey());
}
if (usersList.size() == 0) {
return;
}
Log.d(TAG, "getUsers usersList.size= " + usersList.size() + " lastkey= " + usersList.get(usersList.size() - 1).getCreatedLong());
if (callback instanceof ItemKeyedDataSource.LoadInitialCallback) {
//initial load
/*((ItemKeyedDataSource.LoadInitialCallback)callback)
.onResult(usersList, 0, 14);*/
callback.onResult(usersList);
}
} else {
Log.w(TAG, "getUsers no users exist");
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
}
}
public void getUsersAfter(final Long key, final int size, @NonNull final ItemKeyedDataSource.LoadCallback < User > callback) {
/*if(key == entireUsersList.get(entireUsersList.size()-1).getCreatedLong()){
Log.d(TAG, "getUsersAfter init. afterKey= " + key+ "entireUsersList= "+entireUsersList.get(entireUsersList.size()-1).getCreatedLong());
return;
}*/
Log.d(TAG, "getUsersAfter. AfterKey= " + key);
mUsersRef.orderByChild("created").startAt(key).limitToFirst(size).addListenerForSingleValueEvent(new ValueEventListener() {@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
if (dataSnapshot.exists()) {
// loop throw users value
List < User > usersList = new ArrayList < >();
for (DataSnapshot userSnapshot: dataSnapshot.getChildren()) {
usersList.add(userSnapshot.getValue(User.class));
Log.d(TAG, "getUsersAfter dataSnapshot. getSnapshotKey= " + userSnapshot.getKey());
}
if (usersList.size() == 0) {
return;
}
Log.d(TAG, "getUsersAfter usersList.size= " + usersList.size() + "lastkey= " + usersList.get(usersList.size() - 1).getCreatedLong());
if (callback instanceof ItemKeyedDataSource.LoadCallback) {
//initial After
callback.onResult(usersList);
/*((ItemKeyedDataSource.LoadCallback)callback)
.onResult(usersList);*/
}
} else {
Log.w(TAG, "getUsersAfter no users exist");
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
//mUsersRef.addValueEventListener(usersListener);
}
public void getUsersBefore(final Long key, final int size, @NonNull final ItemKeyedDataSource.LoadCallback < User > callback) {
Log.d(TAG, "getUsersBefore. BeforeKey= " + key);
/*if(key == entireUsersList.get(0).getCreatedLong()){
return;
}*/
mUsersRef.orderByChild("created").endAt(key).limitToLast(size).addListenerForSingleValueEvent(new ValueEventListener() {@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
// [START_EXCLUDE]
if (dataSnapshot.exists()) {
// loop throw users value
List < User > usersList = new ArrayList < >();
for (DataSnapshot userSnapshot: dataSnapshot.getChildren()) {
usersList.add(userSnapshot.getValue(User.class));
Log.d(TAG, "getUsersBefore dataSnapshot. getSnapshotKeys= " + userSnapshot.getKey());
}
if (usersList.size() == 0) {
return;
}
Log.d(TAG, "getUsersBefore usersList.size= " + usersList.size() + "lastkey= " + usersList.get(usersList.size() - 1).getCreatedLong());
if (callback instanceof ItemKeyedDataSource.LoadCallback) {
//initial before
callback.onResult(usersList);
/*((ItemKeyedDataSource.LoadCallback)callback)
.onResult(usersList);*/
}
//initial load
/* ((ItemKeyedDataSource.LoadCallback)callback)
.onResult(usersList, 0, usersList.size());*/
} else {
Log.w(TAG, "getUsersBefore no users exist");
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
}
});
//mUsersRef.addValueEventListener(usersListener);
}
public void usersChanged(final DataSource.InvalidatedCallback InvalidatedCallback) {
final Query query = mUsersRef.orderByChild("created");
usersChangesListener = new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!isFirstLoaded) {
isFirstLoaded = true;
Log.d(TAG, "entireUsersList Invalidated:");
// Remove post value event listener
if (usersChangesListener != null) {
query.removeEventListener(usersChangesListener);
Log.d(TAG, "usersChanged Invalidated removeEventListener");
}
((ItemKeyedDataSource.InvalidatedCallback)InvalidatedCallback).onInvalidated();
}
isFirstLoaded = false;
/*if(entireUsersList.size() > 0){
entireUsersList.clear();
((ItemKeyedDataSource.InvalidatedCallback)onInvalidatedCallback).onInvalidated();
Log.d(TAG, "entireUsersList Invalidated:");
return;
}
if (dataSnapshot.exists()) {
// loop throw users value
for (DataSnapshot userSnapshot: dataSnapshot.getChildren()){
entireUsersList.add(userSnapshot.getValue(User.class));
}
Log.d(TAG, "entireUsersList size= "+entireUsersList.size()+"dataSnapshot count= "+dataSnapshot.getChildrenCount());
} else {
Log.w(TAG, "usersChanged no users exist");
}*/
}
@Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
// ...
}
};
query.addValueEventListener(usersChangesListener);
//mUsersRef.addValueEventListener(eventListener);
}
}
public class UsersDataSource extends ItemKeyedDataSource < Long,
User >
public class UsersDataSource extends ItemKeyedDataSource < Long,
User > {
private final static String TAG = UsersDataSource.class.getSimpleName();
private UsersRepository usersRepository;
public UsersDataSource() {
usersRepository = new UsersRepository();
}
@Override
public void addInvalidatedCallback(@NonNull InvalidatedCallback onInvalidatedCallback) {
//super.addInvalidatedCallback(onInvalidatedCallback);
Log.d(TAG, "Callback Invalidated ");
usersRepository.usersChanged(onInvalidatedCallback);
}
@Override
public void loadInitial(@NonNull LoadInitialParams < Long > params, @NonNull LoadInitialCallback < User > callback) {
/*List<User> items = usersRepository.getUsers(params.requestedInitialKey, params.requestedLoadSize);
callback.onResult(items);*/
Log.d(TAG, "loadInitial params key" + params.requestedInitialKey + " " + params.requestedLoadSize);
usersRepository.getUsers(params.requestedInitialKey, params.requestedLoadSize, callback);
//usersRepository.getUsers( 0L, params.requestedLoadSize, callback);
}
@Override
public void loadAfter(@NonNull LoadParams < Long > params, @NonNull LoadCallback < User > callback) {
/*List<User> items = usersRepository.getUsers(params.key, params.requestedLoadSize);
callback.onResult(items);*/
Log.d(TAG, "loadAfter params key " + (params.key + 1));
usersRepository.getUsersAfter(params.key + 1, params.requestedLoadSize, callback);
}
@Override
public void loadBefore(@NonNull LoadParams < Long > params, @NonNull LoadCallback < User > callback) {
/*List<User> items = fetchItemsBefore(params.key, params.requestedLoadSize);
callback.onResult(items);*/
Log.d(TAG, "loadBefore params " + (params.key - 1));
usersRepository.getUsersBefore(params.key - 1, params.requestedLoadSize, callback);
}
@NonNull@Override
public Long getKey(@NonNull User user) {
return user.getCreatedLong();
}
}
public MainFragment()
public MainFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*/
public static MainFragment newInstance(String param1, String param2) {
MainFragment fragment = new MainFragment();
Bundle args = new Bundle();
//fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
/*if(activity != null){
activity.setTitle(R.string.main_frag_title);
}*/
View fragView = inflater.inflate(R.layout.fragment_main, container, false);
// prepare the Adapter
mUserArrayList = new ArrayList < >();
mProfileAdapter = new UsersAdapter();
// Initiate the viewModel
viewModel = ViewModelProviders.of(this).get(UsersViewModel.class);
// Initiate the RecyclerView
mProfileRecycler = (RecyclerView) fragView.findViewById(R.id.users_recycler);
mProfileRecycler.setHasFixedSize(true);
mProfileRecycler.setLayoutManager(new LinearLayoutManager(mActivityContext));
//viewModel.usersList.observe(this, mProfileAdapter::submitList);
//viewModel.getPagedListObservable().observe(this, new Observer<PagedList<User>>() {
viewModel.usersList.observe(this, new Observer < PagedList < User >> () {@Override
public void onChanged(@Nullable PagedList < User > items) {
System.out.println("onChanged");
if (items != null) {
new java.util.Timer().schedule(
new java.util.TimerTask() {@Override
public void run() {
// your code here
Log.d(TAG, "submitList size" + items.size());
mProfileAdapter.submitList(items);
}
},
5000);
}
}
});
mProfileRecycler.setAdapter(mProfileAdapter);
return fragView;
}
Update:
Well, I found a better solution than delaying submitList for 5 second. I decided to use a while loop until the data is received from firebase database, once items.size() is greater than 0 I stops the loop and submit the list to the adapter.
public class MainFragment extends Fragment {
private final static String TAG = MainFragment.class.getSimpleName();
private RecyclerView mUsersRecycler;
private ArrayList<User> mUserArrayList;
private UsersAdapter mUsersAdapter;
private Context mActivityContext;
private Activity activity;
private UsersViewModel viewModel;
public MainFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
*/
public static MainFragment newInstance(String param1, String param2) {
MainFragment fragment = new MainFragment();
Bundle args = new Bundle();
//fragment.setArguments(args);
return fragment;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Initiate viewModel for this fragment instance
viewModel = ViewModelProviders.of(this).get(UsersViewModel.class);
//viewModel.getPagedListObservable().observe(this, new Observer<PagedList<User>>() {
viewModel.usersList.observe(this, new Observer<PagedList<User>>() {
@Override
public void onChanged(@Nullable final PagedList<User> items) {
System.out.println("mama onChanged");
if (items != null ){
// Create new Thread to loop until items.size() is greater than 0
new Thread(new Runnable() {
int sleepCounter = 0;
@Override
public void run() {
try {
while(items.size()==0) {
//Keep looping as long as items size is 0
Thread.sleep(20);
Log.d(TAG, "sleep 1000. size= "+items.size()+" sleepCounter="+sleepCounter++);
if(sleepCounter == 1000){
break;
}
//handler.post(this);
}
//Now items size is greater than 0, let's submit the List
Log.d(TAG, "after sleep finished. size= "+items.size());
if(items.size() == 0 && sleepCounter == 1000){
// If we submit List after loop is finish with 0 results
// we may erase another results submitted via newer thread
Log.d(TAG, "Loop finished with 0 items. Don't submitList");
}else{
Log.d(TAG, "submitList");
mUsersAdapter.submitList(items);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
});
}
}