1

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

}

}

Wael Adel
  • 51
  • 1
  • 7

0 Answers0