I want to put data from firebase database in a list, however I get the error : "java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0", I even tried to add to the list manually inside the valueEventListener as shown in the comment, but still it's empty
this is my code :
public class Playing extends AppCompatActivity implements View.OnClickListener {
public static List<Question> list_question=new ArrayList<>();
Question currentQuestion;
int index=0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playing);
final DatabaseReference myRef = FirebaseDatabase.getInstance().getReference().child("quiz");
myRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
/*
Question question=new Question();
question.setQuestion("eeee");
question.setAnswerA("zzzzz");
question.setAnswerB("aazss");
question.setAnswerC("ytyty");
question.setAnswerD("jkjkjkjk");
question.setCorrectAnswer("A");
list_question.add(question);
*/
for (DataSnapshot ds:dataSnapshot.child("questions").getChildren())
{
Question question=new Question();
question.setQuestion(ds.child("question").getValue().toString());
question.setAnswerA(ds.child("A").getValue().toString());
question.setAnswerB(ds.child("B").getValue().toString());
question.setAnswerC(ds.child("C").getValue().toString());
question.setAnswerD(ds.child("D").getValue().toString());
question.setCorrectAnswer(ds.child("sol").getValue().toString());
list_question.add(question);
}
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
}
});
currentQuestion = list_question.get(index);
What is missing in my code ?
addValueEventListener is asynchronous and returns immediately. Your code goes on to execute list_question.get(index), but list_question is still empty. The listener you provided will not get invoked until some time later, after the database query is complete. There is no guarantee how long it will take.
If you want to use the results of a query, you must wait until the asynchronous database operation is complete. This means that you can only use the results inside the listener callback itself.
This methods to retrieve data is asynchronous. Which means that this isn't executed in the chronogical order.
Try to use the debug mode and you will see by yourself that it executes the inside of the listener after executing the rest of the code.
Put the
currentQuestion= list_question.get(index)
Inside the listener, just after the line where you add your question and you will see that it works.
It is just that you are trying to access the list before it is getting filled.
You can check for the size of the list by putting an if.
if(list_question.size() > 0)
currentQuestion = list_question.get(index);
Also make sure that you are doing this logic once a data change event happened.
Hope this helps.
Happy coding :)
Related
This question already has answers here:
Should I actually remove the ValueEventListener?
(3 answers)
Closed 4 years ago.
Note: Please do not suggest FirebaseUI as an answer.
I am trying to design a chat list UI. For this, I am populating data to my recyclerview which displays the list of chat groups along with last message. I want last message to be updated in real time. Therefore, to do this, I have some listeners/subscription inside onBindViewHolder method which continuously listen for new data and update the view.
The problem what I am facing is if the user migrates to some other activity, the app crashes when chat list data changes in the database. This is because the listeners are still running in background and trying to update views of a destroyed activity.
I am looking for a way to close my listeners/subscriptions when the recyclerview is destroyed. I have tried using onViewDetachedFromWindow but it only works for views that get recycled when the recycler is on screen. If i was reading data only once, i would have cleaned up subscriptions as soon as they complete but my use-case is to continuously listen for changes in data.
Some sample code:
protected void onBindViewHolder(#NonNull ChatViewHolder holder,
int position, #NonNull FirebaseConversationRecord model) {
final CardView cardView = (CardView) holder.itemView;
...
final ValueEventListener listener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
Log.v("FIREBASEADAPTERLISTENER", dataSnapshot.getKey());
FirebaseUserRecord data = dataSnapshot.getValue(FirebaseUserRecord.class);
textViewName.setText(data.getName());
GlideApp.with(cardView.getContext())
.load(data.getProfilePicURL())
.into(imageView);
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
Log.v("FIREBASEADAPTERLISTENER", databaseError.getMessage());
}
};
...
}
EDIT
This question is in context of a RecyclerView and how to attach listeners during onBindView. It is not the same as adding/removing a single listener from an activity which is very straight forward to implement.
remove ValueEvenetListener from DatabaseReference onDestroy using this following method
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("");
ValueEventListener valueEventListener = new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot dataSnapshot) {
//here you gets the data
}
#Override
public void onCancelled(#NonNull DatabaseError databaseError) {
//if some error occurs
}
};
//add value event listener
databaseReference.addValueEventListener(valueEventListener);
//remove enet listener
databaseReference.removeEventListener(valueEventListener);
this is just a hint
ok do this way :
//here you require the instance of activity
//in your onDataCahnge method
if(activity.getApplicationContext() != null){
//implement all the ui change here
}else{
//here you remove the ValueEventListener
databaseReference.removeEventListener(valueEventListener);
}
Pass in or expose an instance of CompositeDisposable and clear your disposables in the onDestroy() of your Activity.
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 want to receive a string from addValueEventListener() method I use to resell the data from the database Firebase. The data arrive correctly.
But when certain to get the string out of that method to use it in another, it returns nothing.
You have tips?
I already tried putExtras and also create a method on purpose but it did not work.
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
a = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
//this return null... why?
String title = a;
The data is loaded from Firebase asynchronously. By the time you run title = a, the onDataChange method hasn't been called yet. Set some breakpoints in a debugger to verify this, it's key to understanding how asynchronous loading works.
The solution is to reframe your problem from "first get the object, then do blabla with the title" to "start getting the object; once the object is available, do blabla with the title".
In code this translates to:
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
// Do blabla with the title
String title = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
Many developers new to Firebase (and other modern web APIs, as they all work this way) struggle with this problem. So I recommend you also check out their questions and answers:
Cannot access firebaseObjectObservable outside of set
Android Firebase get value of child without DataChange
Value of a global variable is reset after it is initialised in ValueEventListener
can't get values out of ondatachange method
ArrayList not updating inside onChildAdded function
Setting Singleton property value in Firebase Listener
and most others in this list of search results
In order to retrieve the string from method addValueEventListener in viewmodel or any other network call, it is recommended to use the either MutableLiveData<T> or LiveData<T> and observe the same in your activity. Observer will observe the changes, and as soon as string got filled up, the observer method will automatically give you string which you are looking.
You need to create reference variable for the LiveData<T> reference_variable wherever your addValueEventLister is located and set its value in your addValueEventListener.
And then in your viewmodel create the returning value function like below...
Observe this function in your activity and you will have your string.
public MutableLiveData<TotalRunsWicketsAndData> getDisplayableDetails() {
return observableLiveData;
}
I am using MutableLiveData here.
This is a trick which does it. It would be easy to do so if you have less data to retrieve from ValueEventListener.
Inside the onDataChange(), use a setText to set the required value in it. Keep the visibility of this text view as "Gone". Then retrieve using getText outside the ValueEventListener.
You can retrieve the whole list by using GenericTypeIndicator. Follow the official guide on here
I'm very new to Java, so apologies upfront if this is super straight forward.
I'm tying to loop through my Firebase database and store key set of every child in an ArrayList.
Here is the short version of the code:
public class MainActivity extends AppCompatActivity {
private DatabaseReference mRefEvents;
private List<String> newArray;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRefEvents = FirebaseDatabase.getInstance().getReferenceFromUrl("https:***");
newArray = new ArrayList<String>();
mRefEvents.addValueEventListener(new ValueEventListener()
{
#Override
public void onDataChange(DataSnapshot dataSnapshot)
{
for (DataSnapshot child : dataSnapshot.getChildren())
{
for (DataSnapshot grandChild : child.getChildren())
{
newArray.add(grandChild.getKey());
}
}
Log.i("App info middle", newArray.toString());
}
#Override
public void onCancelled(DatabaseError databaseError)
{
}
});
Log.i("App info end", newArray.toString());
}
}
When I log ("App info middle") newArray within addValueEventListener method, I get exactly the values I need. However, the second log ("App info end") at the bottom, gives me an empty array and doesn't store keys into ArrayList newArray.
I'm sure I'm missing something simple, but any suggestions would be highly appreciated. Thanks!
This looks like you are running everything in one go. Since the listener runs asynchronously, I imagine that you are hitting the Log at the bottom "App Info End" before you are hitting the Log in the listener "App Info Middle". Try running "App info End" in a different method or after a button click and you should see the Array populated. Having said all this, can you post your output to the Console so we can confirm that the App Info End is being hit before App info Middle?
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.