I'm currently developing an Android app that deals with storing book translations. I'm using Firebase to hold all the data online, and I have no trouble pulling data from it and storing them as Book objects. I have a master list of books in the main class that stores all the books from the Firebase database, however the list doesn't seem to update properly.
public static List<Book> books = new ArrayList<Book>();
protected void onCreate(Bundle savedInstanceState) {
myFirebaseRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot bookSnapshhot : snapshot.getChildren()) {
Book book = bookSnapshhot.getValue(Book.class);
books.add(book);
System.out.println(book.getAuthor() + " - " + book.getTitle());
for (Chapter chapter : book.getChapters()) {
System.out.println(chapter.getTitle());
}
for (Genre genre : book.getGenres()) {
System.out.println(genre.getGenre());
}
System.out.println(books.size());
}
}
#Override public void onCancelled(FirebaseError error) { }
});
System.out.println(books.size());
}
In that body, the size of books is correct (right now it is only 1). However if I call the same print statement outside of that code body (the last line), it gives me 0. I don't know why, because it successfully adds it inside the onDataChange() method but it seems to not apply the changes on the outside.
I think that your problem is about the asyncronously of the ValueEventListener.
The onDataChange is called asynconously.
You have to use the books after the onDataChange is called.
The addValueEventListener() will fetch the data from Firebase asynchronously.
What your code depicts is that you are adding ValueEventListener and then printing System.out.println(books.size());. This means your logs gets displayed after you add ValueEventListener(which doesn't means your listener gets invoked). At this point your listener is not invoked yet and so isn't onDataChange(). Thus you won't ever get updated count outside the listener scope.
Related
I am designing a simple basic chat app using firebase real time database and i've designed everything well, however, i'm facing one sllight issue. My chats keep duplicating themselves on the inbox page (the page whrere the chats are laid out for a user to select which chat he wants to open and start talking).
I've attached an image of what i mean below.
Screenshot of the phone screen
The code i am using to get the chats and display them in the recycler view is given below. I have a directory called Conversations in my DB that saves a user's Id and under it, theres a child of each and every person he chats wit, under which is the last message and a seen boolean.
Database Structure
The code is given below
convoref = FirebaseDatabase.getInstance().getReference().child("Conversations").child(currentUid);
and then..
public void getConvoIds() {
convoref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
for(DataSnapshot convo : dataSnapshot.getChildren()){
boolean isMessageSeen = false;
String lastMessage = "";
if(convo.child("seen").getValue() != null) {
isMessageSeen = (boolean) convo.child("seen").getValue();
}else{
Log.i("nolastseen", "location is null");
}
if(convo.child("lastMessage").getValue() != null) {
lastMessage = convo.child("lastMessage").getValue().toString();
}else{
Log.i("nolastMessage", "location is null");
}
Log.i ("the_convo_partner_key", convo.getKey());
Log.i ("lastseenmessage", lastMessage);
Log.i ("seenstate", String.valueOf(isMessageSeen));
FetchConvoInfo(convo.getKey(), isMessageSeen, lastMessage );
}
}
}
the fetch convo information functuion is below
public void FetchConvoInfo(final String key, final boolean isMessageSeen, final String lastMessage){
FirebaseDatabase.getInstance().getReference().child("Users").child(key).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
boolean chatExists = false;
String username = "";
String thumbnail = "";
String chatPartner;
chatPartner = key;
if(dataSnapshot.child("username").exists()){
username = dataSnapshot.child("username").getValue().toString();
}
if(dataSnapshot.child("thumbnail").exists()){
thumbnail = dataSnapshot.child("thumbnail").getValue().toString();
}
ConvoClass obj= new ConvoClass(chatPartner, username, thumbnail, isMessageSeen, lastMessage);
resultConvos.add(obj);
mConvoAdapter.notifyDataSetChanged();
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
}
Any help would be greatly appreciated. i cant seem to figure out why the chat duplicates.
In your onDataChanged method, you are going through every child of the dataSnapshot. Each child of the data snapshot indicates a particular conversation of that particular currentUid guy. So when you are going through every child of the dataSnapshot you are adding all its children to the listview or recyclerview(I don't know what you are using. But you are adding it to the adapter). So you are adding the old data again and again whenever some new data must be added. Hence-duplicate data.
There are two common solutions.
The first is naive method. Do what you are doing right now. But while adding an item (chat, you will call it in your case, I think) to the adapter, check if it is already present in the container resultConvos. That will prevent you from adding duplicate chats. I am sure it is obvious to you also why this method is inefficient. You are unnecessarily having to go through every conversation of a person. It takes O(n) time for just adding one item.
The second method is the recommended method. Remove all the code of ValueEventListener. Instead use ChildEventListener. I don't know if you are aware of it. Check this.
ChildEventListener has mainly 4 methods instead of onDataChanged. Among that, what you require here is onChildAdded. Just like your onDataChanged, it has one argument- a data snapshot. But this data snapshot contains only the newly added child, whereas the data snapshot in onDataChanged contains the whole data of the conversations of that particular user (that means the whole list). So using the data snapshot provided by onChildAdded you can directly add only that chat to the adapter, which takes O(1) time.
For more about ChildEventListener, read that link I attached
i am building a app using firebase that requires an admin page and in it i want to make a list of all the usernames of users registered in the system and i am using this code:
Usernames = new ArrayList<>();
usersdb = FirebaseDatabase.getInstance().getReference().child("Users");
usersdb.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot user:dataSnapshot.getChildren()) {
Usernames.add(user.child("username").getValue().toString());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
adp = new ArrayAdapter<String>(context,android.R.layout.simple_list_item_1,Usernames);
lv.setAdapter(adp);
and for some reason nothing shows up in the list view in the end anyone knows why?
and my data structure looks like this:(the random letters are user uids)
Users
----sdfbsif
-----------email
-----------username
-----------password
----djgsvnv
-----------email
-----------username
-----------password
You're calling setAdapter() before the data is available from your listener. addValueEventListener is asynchronous and returns immediately, which means you're passing an empty list to the adapter. Put log messages throughout your code to see the order in which it's executing.
Instead, you could call setAdapter in the callback after all the data from the snapshot is available.
Yet now i am getting the all data from the FireBase at one time.What i want to do that getting data in LIMITS like 15 records at a time. Like in first time user get the 15 records from the Firebase and when user load more data at the bottom/TOP of the screen than 15 more records should come from Firebase and added to the bottom/TOP of the list.
I have implemented the logic to get the 15 records at a top OR bottom of the database from Firebase like below:-
public class ChatActivity extends AppCompatActivity implements FirebaseAuth.AuthStateListener {
private FirebaseAuth mAuth;
private DatabaseReference mChatRef;
private Query postQuery;
private String newestPostId;
private String oldestPostId;
private int startAt = 0;
private SwipeRefreshLayout swipeRefreshLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
mAuth = FirebaseAuth.getInstance();
mAuth.addAuthStateListener(this);
mChatRef = FirebaseDatabase.getInstance().getReference();
mChatRef = mChatRef.child("chats");
/////GETTING THE VIEW ID OF SWIPE LAYOUT
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipeRefreshLayout);
/////GETTING FIRST 10 RECORDS FROM THE FIREBASE HERE
mChatRef.limitToFirst(10).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
oldestPostId = child.getKey();
System.out.println("here si the data==>>" + child.getKey());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
//////FOR THE PULL TO REFRESH CODE IS HERE
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
// Refresh items
System.out.println("Here==>>> "+oldestPostId);
///HERE "oldestPostId" IS THE KEY WHICH I GET THE LAST RECORDS FROM THE FIREBASE
mChatRef.startAt(oldestPostId).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot child : dataSnapshot.getChildren()) {
System.out.println("here AFTER data added==>>" + child.getKey());
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
});
}
I have searched here on SO for it , but did not get the expected result, below link which i have searched for it
1. First Link
2. Second Link
3. Third Link
4. Forth Link
Please look at my firebase data structure in image.
I have implemented the logic for the getting 15 OR 10 records at first time and it works..and also implemented the logic for loading more records in limits but not getting proper solution (NOT WORKING) , please help and let me know where am i doing wrong..Thanks :)
EDIT SOLUTION
:- I have implemented the load more or pull to refresh functionality on this link:- Firebase infinite scroll list view Load 10 items on Scrolling
You are missing orderByKey(). For any filtering queries you must use the ordering functions. Refer to the documentation
In your onRefresh method you need to set the limit:
public void onRefresh() {
// Refresh items
///HERE "oldestPostId" IS THE KEY WHICH I GET THE LAST RECORDS FROM THE FIREBASE
mChatRef.orderByKey().startAt(oldestPostId).limitToFirst(10).addListenerForSingleValueEvent(new ValueEventListener() {
.....
So the data you retrieve is the only 10 new records after you got your first 10 records.
Make sure to save the oldest key of the newly retrieved data so that on next refresh new data from this key is only retrieved.
Suggestion: Instead of adding a child value listener to find the last key, you can just use the value listener and get the last data snapshot with the size to get the last record's key.
restructure your database, set a new child id which is incremental like 0,1,2,3... etc
"chats": {
"-KZLKDF": {
"id": 0,
"message": "Hi",
"name":"username"
},
then make a method for query
public void loadFromFirebase(int startValue,int endValue){
mChatRef.orderByChild(id).startAt(startValue).endAt(endValue).addListenerForSingleValueEvent(this);
}
make sure you have implemented addListenerForSingleValueEvent then do adapter related task.
Initially call from onCreate method:
loadFromFirebase(0,10);
NB: loading new content in the list you have to be aware with adapter.
I have the following data structure on firebase for the user MF0qeRA4p7djfjgXxqwFOck3m6p02. I want to get the value of item3 to populate a single field into the User interface on an Android App. I have been looking through samples on Stackoverflow, but all I have found are outdated and do not work with the current version of firebase. I'm new to firebase completely and this is my first app on android. I've got the oncreate user method to populate the users email address and add the 4 item fields, but retrieving the data I'm completely lost and I am not sure where to even begin.
-Users
---MF0qeRA4p7djfjgXxqwFOck3m6p02
------item1:"1"
------item2:"2"
------item3:"3"
------item4:"4"
According to what I can identify is, you are facing problem retrieving data from this reference. Here is the code:
final DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Users");
databaseReference.child("MF0qeRA4p7djfjgXxqwFOck3m6p02").addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Map<String, Object> map=(Map<String, Object>)dataSnapshot.getValue();
String item3=(String)map.get("item3");
display(item3);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Hope this helps.
You can create a custom model and inside you can insert elements. Something like this:
public class Item {
private List<Object> ojects;
}
There you can save instance of Item on database. In this case you have more controll. Other case is to use push() method, that will generate a new encoded key, something like this:
mDatabase.child("items").push().put(new Object());
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.