Querying data from firebase - android

I am trying to get a list of items from my firebase database...but I have a problem obtaining them.
My code looks more or less like this:
List<Items> itemsList;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_work_items_report);
itemsList = GetItems();
}
and the method that should return my items looks like:
private ArrayList<Items> GetItems(){
DatabaseReference database = FirebaseDatabase.getInstance().getReference();
DatabaseReference ref = database.child("items");
final ArrayList<Items> itemsRez = new ArrayList<Items>();
Query itemsQuery = ref.orderByChild("type");
itemsQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for (DataSnapshot singleSnapshot : dataSnapshot.getChildren()) {
Items item = singleSnapshot.getValue(Items.class);
itemsRez.add(item);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return itemsRez;
}
The GetItems() always returns me a null list, which kinda makes sense, as there is nothing to fire the onDataChange event before I return this list, so my question is...how can I make this method to return the list of items from DB?

When you call addListenerForSingleValueEvent() the Firebase client starts loading the data from the server, which may take some time. To prevent blocking your app (which would lead to an Application Not Responding dialog), it loads the data in a separate thread. So while the data is loading, your main thread goes on and returns the current state of itemsRez, which is an empty list.
It's easiest to see this if you add a few logging statements to your code:
private ArrayList<Items> GetItems(){
DatabaseReference database = FirebaseDatabase.getInstance().getReference();
DatabaseReference ref = database.child("items");
final ArrayList<Items> itemsRez = new ArrayList<Items>();
Query itemsQuery = ref.orderByChild("type");
System.out.println("Attaching listener");
itemsQuery.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for (DataSnapshot singleSnapshot : dataSnapshot.getChildren()) {
Items item = singleSnapshot.getValue(Items.class);
itemsRez.add(item);
}
}
System.out.println("Received "+itemsRez.size()+" items");
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
System.out.println("Returning "+itemsRez.size()+" items");
return itemsRez;
}
Contrary to what you likely expect, this will print the logging in this order:
Attaching listener
Returning 0 items
Received items
One common solution to your problem is to reframe the goal of you code. Instead of writing "first get the items, then do abc with them", write the code as "start getting the items. Then when they come in, do abc with them". In code that means you move the code that needs itemsRec into the onDataChange method, where it will be invoked at the right moment: when the items have loaded.
Also see:
Setting Singleton property value in Firebase Listener
Firebase/Android: Adding retrieved values from Firebase to arraylist returns null pointer exception
Wait Firebase async retrive data in android
this blog post from Doug Stevenson

Related

Android: How to use conditional DataSnapshot values in Firebase RecyclerView?

I am trying to query Firebase and populate a recycler adapter with conditional data from the query's DataSnapshot. I tried putting the populate function inside the if statement that correctly logs the data I want, however the recycler view instead just returns everything from the node I was searching in (the main query I started with). Any suggestions on how to just populate the items that apply to the "if" statement? Thank you!
rootRef = FirebaseDatabase.getInstance().getReference();
//below is the node i query
mAlbumQuery = rootRef.child(Constants.FIREBASE_CHILD_ALBUMS).orderByChild("genres");
mAlbumQuery.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot reco : dataSnapshot.getChildren()) {
if (reco.getValue().toString().contains(mRecommendation.getGenre())) {
//below returns the items i want
Log.d("is this correct", reco.getValue().toString());
//below returns everything in the original query
//how to populate only items that match the above?
mAdapter = new FirebaseRecyclerAdapter<Album, AlbumsViewHolder>(
Album.class,
R.layout.album_cards,
AlbumsViewHolder.class,
mAlbumQuery) {
#Override
public void populateViewHolder(AlbumsViewHolder holder, Album album, int position) {
holder.bindView(album.getImage(), album.getTitle());
if (!album.getGenres().contains(mRecommendation.getGenre())) {
//added as a hypothetical... should i have something in here?
}
}
};
mAlbumsRecycler.setAdapter(mAdapter);
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
return view;
}
if you want to extract any particular node u can use this:-
String notific = String.valueOf(dataSnapshot.getValue());
int key=dataSnapshot.getKey();
String title=String.valueOf(dataSnapshot.child("title").getValue());
String content=String.valueOf(dataSnapshot.child("content").getValue());
Well, if you send mAlbumQuery as param to your FirebaseRecyclerAdapter, I believe, it takes its size as number of items.
As an option (for quick fix) you can create new collection and inside this loop:
for (DataSnapshot reco : dataSnapshot.getChildren()) {
}
you can fill that new collection with needed items.
After loop you can create new adapter and pass filtered collection to it.
Here is how I see this:
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Collection<> myNewCollection = new Collection<>(); //HashMap, ArrayList - depends on what you are storing in Firebase
for (DataSnapshot reco : dataSnapshot.getChildren()) {
if (reco.getValue().toString().contains(mRecommendation.getGenre())) {
//below returns the items i want
Log.d("is this correct", reco.getValue().toString());
//below returns everything in the original query
//how to populate only items that match the above?
myNewCollection.add(reco.getValue);
}
}
recyclerView.setAdapter(new MyRecyclerViewAdapter(myNewCollection, ...));
}
Also pls take a look at Firebase docs and this SO question.
There are interesting methods - startAt, endAt and equalTo, which might help you. I didn't find method contains, unfortunately, but methods above might be enough for you.

how to retrieve the firebase database values only once without listening to childvalues even though they have changed

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.

Firebase how to return data?

I want to get data from Firebase and then return ArrayList. I don't want to listen constantly for changes in database. I just want to immediately query database once so i'm using addListenerForSingleValueEvent and i thought that this will allow me to just get data from database and simply return it but the list is unfortunately empty.
I tried to update list at the end of onDataChange method but it still doesn't work because the list is empty.
private List<ContactsDTO> contacts = new ArrayList<>();
public void updateContacts(List<ContactsDTO> contacts) {
this.contacts = contacts;
}
public void getContactsDB() {
refContacts.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
List<ContactsDTO> temp = new ArrayList<>();
for(DataSnapshot child : dataSnapshot.getChildren()) {
ContactsDTO contactDTO = child.getValue(ContactsDTO.class);
temp.add(contactDTO);
}
updateContacts(temp);
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("error");
}
});
}
public List<ContactsDTO> getContacts() {
getContactsDB();
System.out.println("contacts size: " + contacts.size()) // list will be empty
return contacts;
}
// edit:
I get data correctly in onDataChange method so the db structure is not the problem, but as you wish:
Do you have any idea how to achieve it?
This is happening because onDataChange is called asynchronously and the statement return contacts is executed before onDataChange has been called. So in order to use that temp List<ContactsDTO> you need to use it inside the onDataChange() method.
To solve this problem, please visit this post and this post.
Hope it helps.

Android function returns NULL while on firebase onDataChange shows it is not null

I try to get values from Firebase and add it into the list. I do it the following way:
ArrayList<Category> categoriesList;
public ArrayList<Category> getCategoryListFromFirebase() {
Firebase firebase = new Firebase(Constants.FIREBASE_URL).child("categories");
categoriesList = new ArrayList<>();
firebase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot categories : dataSnapshot.getChildren()) {
Category cat = categories.getValue(Category.class);
categoriesList.add(cat);
}
}
#Override
public void onCancelled(FirebaseError firebaseError) {
}
});
return categoriesList;
}
Function returns categoriesList null, but in a loop it shows it is adding object to categoriesList.
How this can be solved?
Thanks ))
The callbacks for listeners are asynchronous, they do not fire immediately, and may require a fetch of data from the Firebase server. In the code your posted, the statement executed after the ValueEventListener is return categoriesList, not the callback code that builds the list. The list value returned does not contain the results of the onDataChange() callback because it has not yet run.
Also note that a listener registered with addValueEventListener() remains active until it is removed. To get data one time, you should use addListenerForSingleValueEvent()

Android - Persist data retrieved from Firebase

I have an activity and a model called CourseDetails.
String getData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
getData = c.getCourseName();
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
textview1.setText(getData);
Using above code throws NullPointerException at last line above. But if I put textview1.setText(getData) into the ValueEventListener, under getData = c.getCourseName(), the data can be displayed correctly.
Methods I found working are using SharedPreferences or setting data from a method such as public void display(String data) { textview1.setText(data); }. But what are the other ways to keep the retrieved data even if the data is outside ValueEventListener?
For instance I want to persist the data added into an ArrayList.
ArrayList<String> listData;
DatabaseReference mRef = FirebaseDatabase.getInstance().getReference().child("courses").child("Business");
mRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot ds : dataSnapshot.getChildren()) {
CourseDetails c = dataSnapshot.getValue(CourseDetails.class);
String code = c.getCourseCode();
String name = c.getCourseName();
String CodeName = code + " " + name;
listData.add(CodeName);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
// data in ArrayList should be able to display here
StringBuilder builder = new StringBuilder();
for (String s : listData) {
builder.append(s + "\n");
}
textview1.setText(builder.toString());
How to achieve this kind of persistence?
As per my understanding, Firebase will notify all it's data listener attached to specific references (database references wherever the addValueEventListener is added) when those specific data gets modified. That is when
onDataChange will be called, when there is modification of the data at those database references,
(besides modification the method will always be called first time).
And this happens
asynchronously, so in the first case where null occurs because we don't know whether data is retreived from Firebase and
as far as I know, Android's main thread cannot be put on hold or pause until we retreive the data that's why we use Asynchronous tasks in Android.
So, I think the best way to do specific updates or task on data change is within onDataChange method. So, like you stated it could be
done by making those changes within onDataChange itself or by calling some other method from onDataChange.
Or, if you are using
adapter then, notifying adapter about the change within onDataChange. Also, you can take a look at other choice i.e. FirebaseRecyclerAdapter then,
it will handle the update automatically without any extra effort.

Categories

Resources