So I a fragment, which observes a list on a viewmodel, below is the code for that:
homeViewModel.getUrgentCharityList()
.observe(getViewLifecycleOwner(), new Observer<List<Charity>>() {
#Override
public void onChanged(List<Charity> charities) {
urgentCharityListRecyclerView.getAdapter().notifyDataSetChanged();
Log.d(TAG, String.format("Urgent charity list changed. %d items in list",
urgentCharityListRecyclerView.getAdapter().getItemCount()));
}
});
On observed change on the list, I just notify the RecyclerViewAdapter to refresh the RecyclerView.
Here is the code when I initialized the RecyclerView:
public void initializeCharityRecyclerView() {
// Urgent charity list
urgentCharityListRecyclerView = rootView.findViewById(R.id.rv_home_urgentCharityList);
LinearLayoutManager urgentLayoutManager = new LinearLayoutManager(requireContext(),
LinearLayoutManager.HORIZONTAL, false);
urgentCharityListRecyclerView.setLayoutManager(urgentLayoutManager);
urgentCharityListRecyclerView.setAdapter(new CharityRecyclerViewAdapter(requireContext(),
homeViewModel.getUrgentCharityList().getValue()));
}
The problem is that when the fragment is first created, the list data is fetched, but the RecyclerView doesn't refresh, weirdly when I change to another fragment then comes back the RecyclerView updates.
Below is my code for fetching the data from Firestore:
public void updateUrgentCharityList() {
isUpdating.setValue(Boolean.TRUE);
// Query all charities from Firestore
charityRepository.getCurrentCharity().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()) {
List<Charity> newUrgentCharityList = new ArrayList<Charity>();
// Loop through query result
for(QueryDocumentSnapshot document: task.getResult()) {
// Convert query result into Charity object
Charity charity = document.toObject(Charity.class);
// Add Charity object into charityList
newUrgentCharityList.add(charity);
Log.d("HomeFragment", "Charity list query successful");
}
urgentCharityList.setValue(newUrgentCharityList);
}
else {
Log.e(TAG, "Charity list query failed");
}
isUpdating.setValue(Boolean.FALSE);
}
});
}
I tried not notifying the adapter, but replacing the adapter with a new adapter it seems to work, but I think it's not a good way to replace the adapter for every change in the list.
The only problem that I could think of is because the data fetching is async, then that becomes a syncing issue? But why did replacing the adapter work then?
Can anybody explain the actual problem here?
Declare the adapter in class level
CharityRecyclerViewAdapter charityAdapter;
public void initializeCharityRecyclerView() {
// Urgent charity list
urgentCharityListRecyclerView = rootView.findViewById(R.id.rv_home_urgentCharityList);
LinearLayoutManager urgentLayoutManager = new LinearLayoutManager(requireContext(),
LinearLayoutManager.HORIZONTAL, false);
urgentCharityListRecyclerView.setLayoutManager(urgentLayoutManager);
charityAdapter=new CharityRecyclerViewAdapter(requireContext(),homeViewModel.getUrgentCharityList().getValue());
urgentCharityListRecyclerView.setAdapter(charityAdapter);
}
then change the list visibility from private to public in your adapter class and update the list in the observer before calling notifyDatasetChanged
homeViewModel.getUrgentCharityList()
.observe(getViewLifecycleOwner(), new Observer<List<Charity>>() {
#Override
public void onChanged(List<Charity> charities) {
charityAdapter.charities=charities;
charityAdapter.notifyDataSetChanged();
Log.d(TAG, String.format("Urgent charity list changed. %d items in list",
urgentCharityListRecyclerView.getAdapter().getItemCount()));
}
});
Related
I created a recyclerview that retrieves items from firebasefirestore when I delete a data from firebasefirestore recyclerview refresh itself normally but when I add an item to firebasefirestore it is refreshed but dublicates the datas and so many items appears in the recyclerview but it shouldnt, and when I enter other activity then exit to this activity a few times my recyclerview backs to normal.This is my method to fill the recyclerview:
private void fillArray() {
NoteArray = new ArrayList<>();
//SnapshotListener read and write data in real time
collectionReference.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
if(error !=null){
Toast.makeText(recyclerview_notes.this,"Error "+error.toString(),Toast.LENGTH_SHORT).show();
}
if (value!=null && !value.isEmpty()){
for(QueryDocumentSnapshot docs:value) {
Note note = docs.toObject(Note.class);
NoteArray.add(note);
}
recyclerViewAdapter = new NotesRecyclerViewAdapter(NoteArray,recyclerViewInterface);
recyclerView.setAdapter(recyclerViewAdapter);
recyclerViewAdapter.notifyDataSetChanged();
}
}
});
1.first I have 2 items in recyclerview:
2.then I added third item
3.it saves item but dublicates all items
4.when I enter other activity and exit a few times it is fixed
I want that when I add a data to firebase , my recyclerview updates normally.
I think it shows the same data so many times but stored properly on database. Then just clear your list before adding data to the list. Like this NoteArray.clear();
See the updated code below
private void fillArray() {
NoteArray = new ArrayList<>();
//SnapshotListener read and write data in real time
collectionReference.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot value, #Nullable FirebaseFirestoreException error) {
if(error !=null){
Toast.makeText(recyclerview_notes.this,"Error "+error.toString(),Toast.LENGTH_SHORT).show();
}
if (value!=null && !value.isEmpty()){
NoteArray.clear(); // adding list clear function. If data is fetched successfully then the list gets cleared old data and fills new data.
for(QueryDocumentSnapshot docs:value) {
Note note = docs.toObject(Note.class);
NoteArray.add(note);
}
recyclerViewAdapter = new NotesRecyclerViewAdapter(NoteArray,recyclerViewInterface);
recyclerView.setAdapter(recyclerViewAdapter);
recyclerViewAdapter.notifyDataSetChanged();
}
}
});
Hope it helps
I'm writing a todo app to learn more about android architecture.
I implemented RecyclerView with adapter , which receives data from ViewModel. Now i trying to implement swipe-to-delete with "undo" button in Snackbar.
All works fine, until I'm trying to delete 2 items from RecyclerView at the same time. Only one item is deleted, second appears again. Problem only exists while snackbar not dismissed. I use
Snackbar.Callback.DISMISS_EVENT_ACTION
to handle when user push cancel on snackbar
class FolderFragment
...
adapter = new FolderListAdapter(getContext(), folderViewModel);
folderViewModel.getFolders().observe(this, adapter::setFolders);
...
public void onSwipe() {
Snackbar.make(getView(),
R.string.folder_removed_message, Snackbar.LENGTH_SHORT)
.setAction(R.string.undo, v ->
adapter.undoDelete())
.addCallback(new Snackbar.Callback() {
#Override
public void onDismissed(Snackbar transientBottomBar, int event) {
if (event != Snackbar.Callback.DISMISS_EVENT_ACTION) {
folderViewModel.delete(adapter.getDeletedFolder());
}
}
})
.show();
public class FolderListAdapter extends RecyclerView.Adapter<FolderListAdapter.FolderViewHolder>
...
void setFolders(List<Folder> folders) {
this.folders = folders;
notifyDataSetChanged();
}
public void onItemDismiss(int position) {
mDeletedPosition = position;
mDeletedFolder = folders.get(position);
folders.remove(position);
notifyItemRemoved(position);
}
public void undoDelete() {
folders.add(mDeletedPosition, mDeletedFolder);
notifyItemInserted(mDeletedPosition);
}
...
public class FolderViewModel extends AndroidViewModel
...
public void delete(Folder folder) {
folderRepository.delete(folder);
}
...
See my RecyclerView adapter behavior on link below
behavior
trying to comment lines in adapter which deletes item from list in adapter -not work
logging setList() in adapter - viewmodel not updates LiveData because it work in background, but i dont know how to solve this
project on github
Usually on a RecyclerView I show an empty view when there are no items on the RecyclerView and since I control all updates to the RecyclerView via the notify methods then that is pretty simple but with PagedListAdapter updates just seem to happen on the background, so how can I hide or show my empty view?
For example, if I call deleteItem() on my Room DB, the PagedListAdapter will be updated on its own without me calling notifyItemDeleted but if it was the last item on the list, how does my code know to show the empty view? I could query the DB each time an action happens for the count but that seems wasteful. Is there a better way?
As mentioned in the comment, you can test emptiness of the list in the same LiveData observer you use for .submitList().
Java Example:
I am assuming you are following something similar to this snippet found in the PagedListAdapter document. I am simply adding emptiness check to that sample code.
class MyActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
UserAdapter<User> adapter = new UserAdapter();
viewModel.usersList.observe(this, pagedList -> {
// Check if the list is empty
updateView(pagedList.size());
adapter.submitList(pagedList));
pagedList.addWeakCallback(null, new PagedList.Callback() {
#Override
public void onChanged(int position, int count) {
updateView(pagedList.size())
// updateView(adapter.getItemCount())
}
...
}
}
recyclerView.setAdapter(adapter);
}
private void updateView(int itemCount) {
if (itemCount > 0) {
// The list is not empty. Show the recycler view.
recyclerView.setVisibility(View.VISIBLE);
emptyView.setVisibility(View.GONE);
} else {
// The list is empty. Show the empty list view
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.VISIBLE);
}
}
}
Kotlin Example:
The above Java example is actually just a Java translation of Kotlin example I found in this Android Paging codelab.
It's not the best solution, but you can give it a try
myViewModel.getMyPagedList().observe(MainActivity.this, items -> {
myPagedListAdapter.submitList(items);
Handler handler = new Handler();
Runnable runnable = new Runnable() {
#Override
public void run() {
if (myPagedListAdapter.getItemCount() == 0) {
runOnUiThread(() -> {
emptyTextView.setText("Empty");
emptyTextView.setVisibility(View.VISIBLE);
});
// The Runnable will be re-executed (repeated) as long as if condition is true
handler.postDelayed(this, 100);
} else
emptyTextView.setVisibility(View.GONE);
}
};
// trigger first time
handler.postDelayed(runnable, 1000);
});
I have this scenario where in my app i am trying to query the child nodes and pass it on the list to recyclerview adapter and here comes the problem when i am scrolling up the recycler view items and if some one has inserted a post, my recyclerview is again coming to first post item and also i am using the viewpager with three fragments and whatever fragment i am on I am rolling back to the first fragment if some one has inserted the post how to solve this.
I have implemented this in following way mentioned below.
one way im thinking is i thought i would not listen to the childevent changes instead i would query the results and populate recyclerview later not listening to child events so that way everything states as it is and i dont know in firebase how do you retrieve values without implementing listeners I tried the singleValueEventListener that way still the behavior is same rolling back to first item or first fragment
guide me through solution how to get rid of this behavior.
Query query= databasePostsReference.orderByChild("timestamp");
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<UserPostPOJO> listposts = new ArrayList<UserPostPOJO>();
for(DataSnapshot snapshot : dataSnapshot.getChildren()){
Log.d(TAG, "onDataChange: entered list adding");
UserPostPOJO post =
snapshot.getValue(UserPostPOJO.class);
listposts.add(0,post);
}
if(listposts.isEmpty()){
empty.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
else
{
empty.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
makelist(listposts);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
private void makelist(List<UserPostPOJO> listposts) {
list = listposts;
Log.d(TAG,"size is "+ list.size()+"");
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new
LinearLayoutManager(getActivity()));
CustomRecyclerViewAdapter adapter = new
CustomRecyclerViewAdapter(getActivity(), list,"recentfragment");
recyclerView.setAdapter(adapter);
}
There is a method called removeEventListener() that you can call to remove a specific event listener. You get data out from your database and than call this method. So in order to make this work, please use the following code:
databaseReference.removeEventListener(valueEventListener);
In which databaseReference is the reference where you intially put the listener.
For more details please read the offcial doc.
Hope it helps.
Firebase has a FirebaseRecyclerAdapter which can be set to a RecyclerView. It takes a DatabaseReference or Query object and handles all the data synchronization between your database and your view.
For a database reference object, the same way one can add an event listener, it can also be removed, using removeEventListener.
Instead of creating an anonymous object like this
Query query= databasePostsReference.orderByChild("timestamp");
query.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<UserPostPOJO> listposts = new ArrayList<UserPostPOJO>();
for(DataSnapshot snapshot : dataSnapshot.getChildren()){
Log.d(TAG, "onDataChange: entered list adding");
UserPostPOJO post =
snapshot.getValue(UserPostPOJO.class);
listposts.add(0,post);
}
if(listposts.isEmpty()){
empty.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
else
{
empty.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
makelist(listposts);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
you can create a named object of ValueEventListener and remove it from the database reference object using removeEventListener, at the end of the onDataChange method
Query query= databasePostsReference.orderByChild("timestamp");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<UserPostPOJO> listposts = new ArrayList<UserPostPOJO>();
for(DataSnapshot snapshot : dataSnapshot.getChildren()){
Log.d(TAG, "onDataChange: entered list adding");
UserPostPOJO post =
snapshot.getValue(UserPostPOJO.class);
listposts.add(0,post);
}
if(listposts.isEmpty()){
empty.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
}
else
{
empty.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
makelist(listposts);
}
query.removeEventListener(valueEventListener);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
query.addValueEventListener(valueEventListener);
The code inside onDataChange method gets executed only once as the ValueEventListener object is removed as soon as the last line of the method gets executed.
I'm trying to refresh my RecyclerView attached data after reading data from the firebase database. notifyDataSetChanged() does not seem to work.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// use a linear layout manager
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
adapter = new StationListAdapter(stations);
recyclerView.setAdapter(adapter);
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference stationsRef = database.getReference("stations");
stationsRef.equalTo("Berlin").orderByChild("city").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
stations = (ArrayList<Map<String, String>>) dataSnapshot.getValue();
Log.d(TAG, "Value is: " + stations.toString());
adapter.notifyDataSetChanged();
}
});
}
No items are shown in the list, but logging says there are items available. When adding items to stations before passing them to my adapter, they are displayed correctly:
HashMap<String, String> works = new HashMap<String, String>();
works.put("name", "berlin");
stations.add(works);
adapter = new StationListAdapter(stations);
I also tried to run notifyDataSetChanged() on the UIThread, same result. I don't want to use https://github.com/firebase/FirebaseUI-Android. Any way to fix this? I have used RecyclerView several times and notifyDataSetChanged() worked fine everytime.
You didnot updated the stations in the adapter class, just write a function in the adapter which sets the value of stations and then before notifydataset change call that function to set the stations, somewhat like
public void setStations(<Map<String, String>> stations){
this.stations=stations
}
this would work
The problem was this line:
stations = (ArrayList<Map<String, String>>) dataSnapshot.getValue();
It should have been:
stations.addAll((ArrayList<Map<String, String>>) dataSnapshot.getValue());