notifyDataSetChanged not working with RecyclerView + Firebase Realtime Database - android

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

Related

RecyclerViewAdapter not updating after an observable change

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

How to retrieve child value from firebase?

Here is my problem, I want to show all phone numbers in RecyclerView, how?
databaseReference = firebaseDatabase.getReference("CallLog");
//in on data change method using for
CallLogList logList = snapshot.getValue(CallLogList.class);
list.add(logList);
ArrayList<HashMap<String, String>> arrayList = new ArrayList<HashMap<String,String>>();
HashMap<String, String> map = new HashMap<String, String>(); map.put("phone",phoneNumber);
map.put("name",callName);
map.put("callType",dir);
map.put("date",dateString);
map.put("duration",callDuration+" seconds");
arrayList.add(map);
myRef.push().setValue(arrayList);
After getting the whole list from Firebase iterate them one by one to add them in the list, after the loop set them in the adapter of RecyclerView, and set the adapter to your desired RecyclerView
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
list = new ArrayList<CallLogList>();
for(DataSnapshot dataSnapshot1 :dataSnapshot.getChildren()){
CallLogList logList = snapshot.getValue(CallLogList.class);
list.add(logList);
}
yourAdapter = YourAdapter(list);
yourRecyclerView.setAdapter(yourAdapter);
}
For more info, here is a full example
Note: If you are using LiveData or Paging then setting adapters is different, Let me know if you need more example
First you should change the tree to be like this:
Call Log
-Calls
--Lhe...
---CallType OUTGOING
--Lhe...
---CallType INCOMING
Next, create "Calls" class in your project with the same fields as in the firebase, so callType, date, duration and phone.
After that, with this code, you can get all instances from the database into a list:
public void refreshList(DataSnapshot dataSnapshot) {
List<Class> list = new ArrayList<>();
for (DataSnapshot dataSnapshot1 : dataSnapshot.child("Calls").getChildren())
{
Item value = dataSnapshot1.getValue(Calls.class);
list.add(value);
}
}
Now you have a list of objects of class Calls, from which you can easily access the phone numbers.

How to fix a List<> that become null inside OnCreateView()

I am trying to retrieve data from Firebase realtime-database and put it on a CardView inside a RecyclerView inside a Fragment.
But the Fragment shown is blank white with no error. I retrieve the data inside OnCreate method and add it into a List.
While debugging the application, found out that even after assigning the retrieved data inside the onCreate method, the list is still NULL inside the onCreateView method.
Fragment Dashboard List Class:
public class fragment_dashboard_list extends Fragment {
List<ibu> ibu_ibu;
FirebaseDatabase database;
DatabaseReference myRef ;
String a;
public fragment_dashboard_list() {}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ibu_ibu = new ArrayList<>();
database = FirebaseDatabase.getInstance();
myRef = database.getReference("Guardian");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method is called once with the initial value and again
// whenever data at this location is updated.
for(DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()){
ibu value = dataSnapshot1.getValue(ibu.class);
ibu ibu_val = new ibu();
String alamat = value.getAlamat();
String foto = value.getFoto();
String hp = value.getHp();
String ktp = value.getKtp();
String nama = value.getNama();
String privilege = value.getPrivilege();
String ttl = value.getTtl();
ibu_val.setAlamat(alamat);
ibu_val.setFoto(foto);
ibu_val.setHp(hp);
ibu_val.setKtp(ktp);
ibu_val.setNama(nama);
ibu_val.setPrivilege(privilege);
ibu_val.setTtl(ttl);
// Here the List ibu_ibu is not NULL
ibu_ibu.add(ibu_val);
}
}
#Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w("Hello", "Failed to read value.", error.toException());
}
});
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_dashboard_list, container, false);
RecyclerView myrv = (RecyclerView) view.findViewById(R.id.dashboard_recycler_view);
//Here the List ibu_ibu is null
adapter_list_ibu myAdapter = new adapter_list_ibu(ibu_ibu);
LinearLayoutManager LinearLayoutManager = new LinearLayoutManager(getContext());
myrv.setLayoutManager(LinearLayoutManager);
myrv.setAdapter(myAdapter);
return view;
}
}
I expected the List to be not NULL inside OnCreateView so the Fragment wont Blank
Firebase APIs are asynchronous, which means that onDataChange() method returns immediately after it's invoked, and the callback will be called some time later. There are no guarantees about how long it will take. So it may take from a few hundred milliseconds to a few seconds before that data is available.
Because that method returns immediately, your ibu_val list that you're trying to use it outside the onDataChange() method, will not have been populated from the callback yet and that's why is always empty.
Basically, you're trying to use a value of variable synchronously from an API that's asynchronous. That's not a good idea, you should handle the APIs asynchronously as intended.
A quick solve for this problem would be to notify the adapter once you got all elements from the database using:
myrv.notifyDatasetChanged();
So add this line of code right after where the for loop ends.
If you intent to use that list outside the callback, I recommend you see the last part of my anwser from this post in which I have explained how it can be done using a custom callback. You can also take a look at this video for a better understanding.

Loading values from Firebase database into drop down Textviews in Android

I require help in implementing data from a secondary Firebase database into autocomplete TextViews in my android application. I have a database containing data on cars (Make of car, model of car, engine size).
I have attached an image displaying how my data is set in the database.
Database image
I would like to have one TextView that will only display all the "make" attributes, another TextView to display only "model" attributes depending on which "make of car" the user picked. (eg. if the user picks Audi, models displayed would be A4, A6, A3 only. if the user picks Volkswagen, models displayed would be Golf, Jetta, Passat only) and then the final TextView to display only the "engine size" that corresponds to the chosen "make" and "model" as each model of car has different engine sizes.
Any help would be greatly appreciated as I have been stuck on this element of my app for several weeks now and have tried countless different approaches.
Below is my class and code. The application runs with no errors but will not retrieve the data from the database.
public class VehicleSpec extends AppCompatActivity
{
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//Sets the layout according to the XML file
setContentView(R.layout.activity_vehicle_spec);
//FirebaseDatabase database = FirebaseDatabase.getInstance();
//
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("com.example.user.vel")
.setApiKey("AIzaSyBvBtL81H7aiUK90c3QfVccoU1CowKrmAA")
.setDatabaseUrl("https://finalyearproject-vel1-aac42.firebaseio.com/")
.build();
//
FirebaseApp.initializeApp(this, options, "secondary");
//
FirebaseApp app = FirebaseApp.getInstance("secondary");
FirebaseDatabase database2 = FirebaseDatabase.getInstance(app);
DatabaseReference dbref = database2.getReference();
DatabaseReference dbref2 = database2.getReference();
DatabaseReference dbref3 = database2.getReference();
//
dbref.child("Cars").addValueEventListener(new ValueEventListener()
{
public void onDataChange(DataSnapshot dataSnapshot)
{
final List<String> Cars = new ArrayList<String>();
for ( DataSnapshot suggestionSnap : dataSnapshot.getChildren() )
{
String suggestion = suggestionSnap.child("Make").getValue(String.class);
Cars.add(suggestion);
}//End For()
//XML TextView variable
AutoCompleteTextView actv = (AutoCompleteTextView) findViewById(R.id.auto);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(VehicleSpec.this, android.R.layout.simple_list_item_1, Cars);
actv.setAdapter(adapter);
}//End onDataChange()
public void onCancelled(DatabaseError databaseError)
{
}//End onCancelled()
});//End dbref ValueEventListener()
//
dbref2.child("Cars").addValueEventListener(new ValueEventListener()
{
public void onDataChange(DataSnapshot dataSnapshot)
{
final List<String> Cars = new ArrayList<String>();
for ( DataSnapshot suggestionSnap : dataSnapshot.getChildren() )
{
String suggestion = suggestionSnap.child("Model").getValue(String.class);
Cars.add(suggestion);
}//End for()
//XML TextView variable
AutoCompleteTextView actv1 = (AutoCompleteTextView) findViewById(R.id.auto1);
ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(VehicleSpec.this, android.R.layout.simple_list_item_1, Cars);
actv1.setAdapter(adapter1);
}//End onDataChange()
public void onCancelled(DatabaseError databaseError)
{
}//End onCancelled()
});//End dbref2 ValueEventListener()
//
dbref3.child("Cars").addValueEventListener(new ValueEventListener()
{
public void onDataChange(DataSnapshot dataSnapshot)
{
final List<String> Cars = new ArrayList<String>();
for (DataSnapshot suggestionSnap : dataSnapshot.getChildren())
{
String suggestion = suggestionSnap.child("Engine Size").getValue(String.class);
Cars.add(suggestion);
}//End for()
//XML TextView variable
AutoCompleteTextView actv2 = (AutoCompleteTextView) findViewById(R.id.auto2);
ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(VehicleSpec.this, android.R.layout.simple_list_item_1, Cars);
actv2.setAdapter(adapter2);
}//End onDataChange()
public void onCancelled(DatabaseError databaseError)
{
}//End onCancelled()
});//End dbref3 ValueEventListener()
}//End onCreate()
I Don't really understand exactly what are you trying to achieve, but let's assume you want one of the followings:
Display a list of cars in a list with 3 sections , Make , Model and a drop down list for Engine Size:
i Suggest using RecyclerView with 2 TextViews (1 for Make, and the second for Model) and an AppCompatSpinner that will be populated from the Engine Size node. You can use these Libraries as your adapter :
FirebaseUI
Infinite-Fire i personaly like this one because it has a built in loadMore when you reach last the item.
Display a list of car Brands (Make in this case) and when a user selects an item, an other list with Models and Engine Sizes Appears : i Suggest using RecyclerView with A TextViews for Make and when the user clicks an item, you save the item name in your SharedPrefrances and open a new activity that displays only models from the selected Make using Firebase databaseRef.child("Cars").orderByChild("Make").equalesTo(getSelectedMake());
or just display them in an AlertDialog instead with a costume layout using the same methods...for displaying DATA You can use the above Libraries as well.
if This isn't what are you looking for , please explain your problem with details and i will update my answer.
Edit : using SharedPreferences to save a reference of the item is nonsense, it's a result of my low experience with android, instead you can use intent.putExtra()
Example
//Start your activity like this
intent.putExtra("itemId", itemId)
startService(intent)
//On your new activity's onCreate() get the itemId as the following
String itemId = intent.getStringExtra("itemId")
Item item = firebaseRef.child("cars")....getValue(Item:class)

How to list data from Firebase database in listview?

I am currently creating an app which logs a 6-digit int id's (values) and the time/date they were logged (keys) in a real-time database. I am trying to create a separate app to accompany this by listing the database key-value as items on a list, with a search query to search the id's.
My question is, how I should go about retrieving and listing both the keys and values as items in my listView? The items need to add themselves in real-time as well, so they can be viewed as they would in the Firebase console. Thanks!
public ArrayList<String> arr;
public ArrayAdapter adapter;
#Override
protected void onStart() {
super.onStart();
DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
arr = new ArrayList<>();
ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
map2list((Map) dataSnapshot.getValue());
//formats the datasnapshot entries to strings
adapter.notifyDataSetChanged();
//makes the ListView realtime
}
#Override
public void onCancelled(DatabaseError databaseError) {
// Getting Post failed, log a message
System.out.println(databaseError.toException());
// ...
}
};
mDatabase.addValueEventListener(listener);
adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, android.R.id.text1, arr);
ListView listView = (ListView) findViewById(R.id.yourOwnListView);
listView.setAdapter(adapter);
}
public void map2list(Map<String,Long> map){
arr.clear();
for (Map.Entry<String, Long> entry : map.entrySet()) {
Long key = Long.parseLong(entry.getKey());
String d = DateFormat.getDateTimeInstance().format(key);
Long value = entry.getValue();
arr.add(d + ": " + value);
}
}
I tested this on my own database and it works.
map2list() is my quick way of formatting your data for the listview. The best way to do this is with a custom arrayadapter or baseadapter, but that involves alot more code than you need for this example.
Be advised that keeping your keys as time is a bad idea since you could have a collision if two logs are made at the same time.
Lastly, firebase uses Longs over ints, so i used Longs for less code

Categories

Resources