Doucment.getString not giving an output (Firestore) - java

I just wrote a basic code to get multiple documents from Firestore database. The task is getting successful and you could see the document.getId() & document.getData() value in Log, but after that when I use document.getString(), it gives a blank output. I have been trying to find my error but I am unable to and I have a project submission due tomorrow. Ignore all the textview declaration in the given code below:
private FirebaseFirestore db;
private int month,year;
private Calendar calendar;
private String email;
private String name,points;
private int i,j;
private String q,p,r;
public String[][] data= new String[10][10];
private TextView rankingTextView,a,b;
public TextView t01,t02,t03,t10,t11,t12,t20,t21,t22,t30,t31,t32,t40,t41,t42,t50,t51,t52,t60,t61,t62,t70,t71,t72,t80,t81,t82,t90,t91,t92;
private static final String TAG = "MyApp";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leaderboard);
calendar=Calendar.getInstance();
t01=(TextView)findViewById(R.id.t00);
t02=(TextView)findViewById(R.id.t01);
t03=(TextView)findViewById(R.id.t02);
t10=(TextView)findViewById(R.id.t10);
t11=(TextView)findViewById(R.id.t11);
t12=(TextView)findViewById(R.id.t12);
t20=(TextView)findViewById(R.id.t20);
t21=(TextView)findViewById(R.id.t21);
t22=(TextView)findViewById(R.id.t22);
t30=(TextView)findViewById(R.id.t30);
t31=(TextView)findViewById(R.id.t31);
t32=(TextView)findViewById(R.id.t32);
t40=(TextView)findViewById(R.id.t40);
t41=(TextView)findViewById(R.id.t41);
t42=(TextView)findViewById(R.id.t42);
t50=(TextView)findViewById(R.id.t50);
t51=(TextView)findViewById(R.id.t51);
t52=(TextView)findViewById(R.id.t52);
t60=(TextView)findViewById(R.id.t60);
t61=(TextView)findViewById(R.id.t61);
t62=(TextView)findViewById(R.id.t62);
t70=(TextView)findViewById(R.id.t70);
t71=(TextView)findViewById(R.id.t71);
t72=(TextView)findViewById(R.id.t72);
t80=(TextView)findViewById(R.id.t80);
t81=(TextView)findViewById(R.id.t81);
t82=(TextView)findViewById(R.id.t82);
db= FirebaseFirestore.getInstance();
email = getIntent().getStringExtra("email");
month = calendar.get(Calendar.MONTH);
month = month + 1;
year = calendar.get(Calendar.YEAR);
i=0;
j=0;
dothis();
Toast.makeText(this, "MO", Toast.LENGTH_SHORT).show();
t01.setText(data[0][0]);
t02.setText(data[0][1]);
t11.setText(data[1][0]);
t12.setText(data[1][1]);
t21.setText(data[2][0]);
t22.setText(data[2][1]);
t31.setText(data[3][0]);
t32.setText(data[3][1]);
t41.setText(data[4][0]);
t42.setText(data[4][1]);
t51.setText(data[5][0]);
t52.setText(data[5][1]);
}
private void dothis()
{i=0;
j=0;
db.collection("users")
// .whereGreaterThanOrEqualTo("points" + Integer.toString(month) + Integer.toString(year), "0")
.orderBy("points" + Integer.toString(month) + Integer.toString(year)).limit(10)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
if(document.exists()) {
Log.d(TAG, document.getId() + " => " + document.getData() + " ");
data[i][j] = document.getString("username");
data[i][j + 1] = document.getString("points" + Integer.toString(month) + Integer.toString(year));
i++;
Toast.makeText(Leaderboard.this, "OK", Toast.LENGTH_SHORT).show();
}
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
}
Please don't duplicate my question. I searched a lot before putting.

According to your comment, you say that when are you calling the following line of code:
t01.setText(data[0][0]);
Nothing is set to your t01 TextView and this is the normal behaviour since the data that is coming from the Firebase database is asynchronous. This means that the onComplete() method returns immediately after it's invoked, and the callback from the Task it returns, 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 this method returns immediately, the value of your data[0][0] variable you're trying to use outside the onComplete() method, will not have been populated from the callback yet.
Basically, you're trying to use a value 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 set all those texts to your TextViews only inside the onComplete() method. If you need to set them outside, I recommend dive into the asynchronous worlds and 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.

Related

Firestore asynchronous API getting null values outside readData

this is my logcat output
I am trying to fetch data from Firestore and work on them tried this but not working
getting null value for "emergencyNumber" outside "readData()"
I have tried this solution Text but still getting null
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_emergency_contact);
Log.d("TAG","Initial");
firebaseAuth = FirebaseAuth.getInstance();
fStore = FirebaseFirestore.getInstance();
userID = firebaseAuth.getCurrentUser().getUid();
readData(new FirebaseCallBack() {
#Override
public void onCallback(String str) {
emergencyNumber = str;
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
phone = (TextView) dialog.findViewById(R.id.EmergencyContactNumber) ;
//emergencyNumber = phone.getText().toString() + "Hello";
phone.setText(emergencyNumber);
Log.d("TAG",emergencyNumber+"inner read me");
}
});
Log.d("TAG",emergencyNumber+"middle");
Log.d("TAG",emergencyNumber+"end");
}
private void readData(FirebaseCallBack firebaseCallBack) {
documentReference.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()){
DocumentSnapshot document = task.getResult();
long l = document.getLong("EmergencyContact");
emergencyNumber = "0" + Long.toString(l);
firebaseCallBack.onCallback(emergencyNumber);
//Toast.makeText(EmergencyContact.this, emergencyNumber, Toast.LENGTH_SHORT).show();
Log.d("TAG",emergencyNumber+"exit read me");
}
}
});
}
private interface FirebaseCallBack {
void onCallback(String str);
}
}
Firestore asynchronous API getting null values outside readData.
Any code that needs data from Cloud Firestore needs to be inside the "onComplete()" method, or be called from there. It doesn't really matter if you create another callback, the same rules apply. This means that you cannot use the value of "emergencyNumber" outside the "onCallback()" method. Please note that this method fires, only when "onComplete()" method fires, hence that behavior. When the following Log statement is triggered:
Log.d("TAG",emergencyNumber+"middle");
The data isn't finished loading yet, that's why you have that order of execution in your logcat.
If you are not comfortable with callbacks, then I recommend you using the modern way of dealing with asynchronous programming when getting data from Firestore, which is using LiveData and ViewModel. Here you can find an example from one of my repositories where I have used the MVVM architecture pattern with LiveData and ViewModel to authenticate users in Firebase.
If you consider at some point in time to try using Kotlin, please check below an example:
https://github.com/alexmamo/FirestoreJetpackCompose
Where I have used Kotlin Coroutine for getting data from Firestore.

I can't add items to HashMap after querying them from firebase android

My goal is to display the author and date of the last post published to a specific topic.
I have two collections, forum_topics and forum_posts.
I iterate through all forum_topics and get the desired data put it into the HashMap topicData, then store the HashMap in ArrayList and display it through ListView, this works just fine.
But when I want to go through forum_posts(this query is nested inside forum_topics query) to get the latest post author and date I get the data but am unable to put it inside a HashMap topicData.
The query is okay because it gets the last author and post as logs show but they are not being put into the HashMap I don't know what seems to be the problem.
In the code, there is a comment pointing to the query that does get the data but doesn't add data to the hashmap.
Below is the code
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forum);
addTopic = findViewById(R.id.addNewTopicBtn);
fStore = FirebaseFirestore.getInstance();
empty = findViewById(R.id.empty);
final ArrayList<Map> forumTopics = new ArrayList<>();
final ListAdapter forumAdapter = new ForumAdapter(this, forumTopics);
ListView forumListView = findViewById(R.id.ForumListView);
forumListView.setEmptyView(empty);
forumListView.setAdapter(forumAdapter);
fStore.collection("forum_topics")
.orderBy("date_published", Query.Direction.ASCENDING)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
final Map<String, Object> topicData = new HashMap<>();
// getting topic name and putting it to the topicData HashMap
topicData.put("topic_name", document.getId());
// getting topic author and putting it to the topicData HashMap
topicData.put("author", document.getString("author"));
// getting the date_published timestamp
Date date_published = document.getTimestamp("date_published").toDate();
// formatting the date to the desired 24hr format and putting it into the HashMap
topicData.put("date_published", DateFormat.format("dd-MM-yyyy hh:mm:ss", date_published).toString());
// getting the post_num and putting it to the topicData HashMap
topicData.put("post_num", document.get("post_num").toString());
Log.d(TAG, "Got the topic with name: " + document.getId());
// below is the problematic query that doesn't add data to HashMap
fStore.collection("forum_posts")
.whereEqualTo("topic_name", document.getId())
.orderBy("date_published", Query.Direction.DESCENDING)
.limit(1)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot post : task.getResult()) {
Log.d(TAG, "got the last author " + post.getString("author"));
topicData.put("last_post_author", post.getString("author"));
Date date_published = post.getTimestamp("date_published").toDate();
String last_date_published = DateFormat.format("dd-MM-yyyy hh:mm:ss", date_published).toString();
topicData.put("last_post_published", last_date_published);
Log.d(TAG, "got the last post " + last_date_published);
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
forumTopics.add(topicData);
}
// notifying the forumAdapter with data change
((ArrayAdapter) forumAdapter).notifyDataSetChanged();
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
forumListView.setOnItemClickListener(
new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Map topicData = (Map) parent.getItemAtPosition(position);
String topic = (String) topicData.get("topic_name");
Intent intent = new Intent(Forum.this, ForumTopic.class);
intent.putExtra("topic_name", topic);
startActivity(intent);
finish();
}
}
);
addTopic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(Forum.this, ForumPostTopic.class));
}
});
}
I tried multiple things, doing the query outside the forum_topics query, iterating over ArrayList and then putting elements, I put the query into separate method, I even tried making another ArrayList iterating over the first one, getting the data from it, putting it back into another HashMap and adding it into the new ArrayList and updating adapter, but they all have failed.
Maybe I can get the desired data from forum_posts collection in another way, not by querying?
I would be grateful for any useful tips. I hope the question now is simplified as my previous one got closed.
Logs:
D/TAG: Got the topic with name: Some new topic in here
D/TAG: Got the topic with name: This is a new topic
D/TAG: Got the topic with name: 123
D/TAG: Got the topic with name: new topic
I/zygote: Do full code cache collection, code=124KB, data=79KB
I/zygote: After code cache collection, code=123KB, data=48KB
I/zygote: Do partial code cache collection, code=123KB, data=47KB
I/zygote: After code cache collection, code=123KB, data=47KB
Increasing code cache capacity to 512KB
D/TAG: got the last author Jo Do
D/TAG: got the last post 26-02-2020 10:42:46
got the last author Jo Do
D/TAG: got the last post 26-02-2020 02:29:16
got the last author Jo Do
D/TAG: got the last post 26-02-2020 11:01:25
D/TAG: got the last author Jo Do
D/TAG: got the last post 28-02-2020 11:19:34
Screenshot from firebase:
forum_topics:
forum_posts: from here I get the data in red but can't put it into HashMap
Screenshot from device:
I managed to solve this problem by notifying the forumAdapter after getting the callback from onComplete in second query.
As I debugged the app I saw that the code in the second query is executed after the first query finishes. It looked like the query was put on hold.
So the data was actually added to the HashMap, but adapter was unaware of new data added so it had to be notified twice, I thought it will be enough after the main query apparently it has to be notified each time the dataset change, that was a very valuable lesson and I new that solution was simple.
The code is bellow(I only post the code for the query that I thought wasn't working):
fStore.collection("forum_posts")
.whereEqualTo("topic_name", document.getId())
.orderBy("date_published", Query.Direction.DESCENDING)
.limit(1)
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot post : task.getResult()) {
Log.d(TAG, "got the last author " + post.getString("author"));
topicData.put("last_post_author", post.getString("author"));
Date date_published = post.getTimestamp("date_published").toDate();
String last_date_published = DateFormat.format("dd-MM-yyyy hh:mm:ss", date_published).toString();
topicData.put("last_post_published", last_date_published);
Log.d(TAG, "got the last post " + last_date_published);
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
// notifying the forumAdapter with data change
((ArrayAdapter) forumAdapter).notifyDataSetChanged();
}
});

Adding to Array-List doesn't work in For-Loop

I want to add data to an ArrayList inside a For-Loop and it doesn't work.
I've got a Cloud Firestore in Firebase with a collection filled with documents by another app. This one is meant to get the data and put it in a Recycler View. Adapter and all are set. Far as I can see, the problem is with filling the (Array)list inside the For-Loop? (As also just filling in any data inside this loop doesn't work, but I'm not sure.)
I noted some of my tries and errors in the code using comments. I'm sure, it must be just some minor mistake, but I really can't find it.
Adding data with the exact same code outside the For-Loop works, inside doesn't work (see "Blah")
private ArrayList<String> patients;
private String testString;
//-> String einzig zu Testzwecken
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
patients = new ArrayList<>();
textView = findViewById(R.id.textView);
db.collection("Test")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
//patients = new ArrayList<>();
//-> doesn't work
patients.add((document.getData().toString()));
//doesn't work
listData.add(new Data(R.drawable.hohlbrot, "ahjotest"));
//doesn't work (trying to feed the recyclerView more directly)
//Toast.makeText(MainActivity.this, document.getData().toString(), Toast.LENGTH_SHORT).show();
//textView.setText(document.getData().toString());
// -> both work
testString = testString + document.getData().toString();
textView.setText(testString);
// -> works
patients.add("Blah1");
//doesn't work
//Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
//Log.d(TAG, "Error getting documents: ", task.getException());
patients.add("BlahError");
}
}
});
patients.add("Blah2");
//works
Well, I tried several things, but with the setting, that I would expect to work, there were no error messages. It just didn't do anything. In the RecyclerView the "Blah2" occurs as do any Strings I add later. Anything I tried inside the For-Loop (Toast, String-to-TextView,...) actually worked, as long as it didn't use the ArrayList.
PS: Me am Noob, beg your patience -.-
try to run your for loop inside OnSuccessListener if you are using firebase.
the syntax is similar to this:
firebaseFirestore.collection("some collection").get()
.addOnSuccessListener(new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot documentSnapshots) {
//do your work here
})
I couldn't get it running with the solutions/approaches mentioned, but I rebuild it following this tutorial:
https://www.youtube.com/watch?time_continue=2&v=ub6mNHWGVHw&feature=emb_title
-> so using the Firestore RecyclerView (instead of the normal one) and the given setup (no For-Loop involved).

How Should I fetch the document fields and use them in another map for another collection?

How should I fetch the document fields from one collection and combine them to add a new document to another collection? I have attached picture of the database how does it looks, I want to fetch the fields from the collection show and want to update it to the new collection along with some other data:
private void savePost(String mPostTitle, String mPostContent, String mlistSpinnerC) {
final DocumentReference docRef = FirebaseFirestore.getInstance().collection("users").document(mauth.getCurrentUser().getUid());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document != null) {
String username = (String)
document.get("username");
String email= (String) document.get(email);
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});
postMap.put(Constants.POSTTTITLE, mPostTitle);
postMap.put(Constants.POSTCATEGORY, mlistSpinnerC);
postMap.put(Constants.POSTCONTENT, mPostContent);
postMap.put(Constants.TIMESTAMP, (System.currentTimeMillis()/1000));
postMap.put(Constants.USER_ID,mauth.getCurrentUser().getUid());
postMap.put("username", username);
PostsRef.document().set(postMap).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful()){
Intent toHomeActivity = new Intent(AddPostActivity.this, MainActivity.class);
startActivity(toHomeActivity);
}
}
});
I am just not able to map the fields from one collection to another collection, please guide me the correct method to that.
By the time you are trying to add the username to your postMap using the following line of code:
postMap.put("username", username);
The data has not finished loading yet from the database and this is because the listener you have added to your get() call is being invoked some unknown amount of time later after your query finishes. You don't know how long it's going to take, it may take from a few hundred milliseconds to a few seconds before that data is available. The onComplete() method has an asynchronous behavior, that's why you cannot get that username in such a way.
A quick solve for this problem would be to move all that block of code related to adding data to the postMap, inside the onComplete() method. In this you are waiting for the callback and username your will be available. Otherwise 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.

Accessing data within an inner function in Java with FireBase/FireStore

I am coding an android app using Google's FireStore backend. The code to grab all the documents in a firestore collection is as follows, from the official documentation:
db.collection("cities")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Where the above code outputs to Log.d(...), I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method. I'm not sure what is the best way to do this. Attempting to change the return the return type of the onComplete method yields errors. Is there a standard way of accessing elements in methods like this?
Declaring a variable and trying to mutate within the class also isn't possible, unless the variable is final, which defeats the point.
That is an asynchronous call (it launches a background process to do the Firebase query, and once that is done it executes your onComplete listener), so you can't expect to have the data in hand immediately after making the database call. For example, if your function looks like
void getData() {
final List<MyData> list = new ArrayList<>();
db.collection("cities")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
list.add(new MyData(document.getData()));
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
Log.d(TAG, "List size = " + list.size()); // will print 0
// list will be empty here, the firebase call will take hundreds
// to thousands of milliseconds to complete
}
You need to structure your program so that it can wait for the data to arrive. There are a couple ways you could do this. One would be to have list be a class member that gets filled by the onComplete listener (then you have to structure the program to handle data coming in at random times).
Another way would be to have a data handler routine that takes the ArrayList and does something with it. This method could be called from the onComplete listener once you've gotten all the data. For example:
void getData() {
db.collection("cities")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<MyData> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
list.add(new MyData(document.getData()));
}
processData(list);
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
}
void processData(List<MyData> data) {
// do stuff with the data, or make a copy, or otherwise share it
// with the rest of your program
}
I would like to have my program add the results of the document.getData() call to an ArrayList accessible outside the inner class/method.
In this case, if you are using a model class, you should use toObject() method and add objects of type YourModelClass in an ArrayList like this:
if (task.isSuccessful()) {
List<YourModelClass> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
YourModelClass yourModelClass = document.toObject(YourModelClass.class);
list.add(yourModelClass);
//Do what you need to do with your list
}
}
As you can see, I advised you to use the list of YourModelClass objects inside the callback. This is because onComplete() method has an asynchronous behavior and you cannot simply use that list outside the callback because it will always be empty.
Attempting to change the return the return type of the onComplete method yields errors.
Changing the return type of the method will not give you errors but the result will always be an empty list. You cannot return something now that hasn't been loaded yet. A quick solve for this problem would be to use that list of objects only inside the onComplete() method, as I already wrote above, or if you want to use it outside, 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.

Categories

Resources