I am trying to retrive a collection named jobs in which each job contains 3 different fields. I then want to display each job with all 3 field on an array list. My app runs but it returns one item which says: java.lang.field#2234456
This is my code:
ListView jobList;
ArrayList<String> jobInfo = new ArrayList<String>();
ArrayAdapter arrayAdapter;
Task hello;
String hi;
FieldPath ha;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_see_jobs2);
jobList = (ListView) findViewById(R.id.jobList);
setTitle("Hello");
arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, jobInfo);
jobList.setAdapter(arrayAdapter);
jobList.setAdapter(arrayAdapter);
hello = FirebaseFirestore.getInstance().collectionGroup("jobs").get().addOnCompleteListener(new
OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
hi = QuerySnapshot.class.getFields().toString();
hi.toString();
}
});
jobInfo.add(QuerySnapshot.class.getDeclaredFields().toString());
}
This is not the way to get data from a Firestore document:
QuerySnapshot.class.getFields()
Instead this looks up the fields that are declared on the QuerySnapshot class, which is not at all what you're interested in.
If we check the Firebase documentation on getting data from multiple documents, you'll see that we can get the data with:
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());
}
}
So this:
Loops over the documents that you get back from the database.
Then gets the data from each document with document.getData(), which returns a Map.
If you look at the reference documentation for the DocumentSnapshot class, you'll see that we can also get the value for a specific field with:
document.get("nameOfThefield")
You'll then want to add the field values to jobInfo.add(...) inside the onComplete method.
Related
In our app we are trying to fetch all the products and by iterating each product trying to get specificationId which is equal to another documentId in seperate collection Specifications.
Basically the structure is like below:
ProductDetail
productId
productTitle
productDescription
specificationId
Specification
documentId — this is autogenerated id for collection which is being used as specificationId in ProductDetail collection.
MainActivity.java
Query productResponseQuery = FirebaseFirestore.getInstance()
.collection("productdetails")
.limit(50);
productResponseQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#RequiresApi(api = Build.VERSION_CODES.N)
#Override
public void onEvent(#Nullable QuerySnapshot querySnapshot, #Nullable FirebaseFirestoreException error) {
if (error != null) {
Log.d(TAG, "Error getting documents: " + error.getMessage());
return;
}
querySnapshot.forEach(doc -> {
ProductDetailResponse productDetailResponse = new ProductDetailResponse();
productDetailResponse.setProductImage(doc.getData().get("productImageUrl").toString());
productDetailResponse.setProductTitle(doc.getData().get("productTitle").toString());
productDetailResponse.setProductShortDesc(doc.getData().get("productShortDesc").toString());
productDetailResponse.setProductDesc(doc.getData().get("productDescription").toString());
populateSpecification(doc.getData().get("specId").toString());
});
}
});
private void populateSpecification(String specId){
DocumentReference specDocumentRef = FirebaseFirestore.getInstance()
.collection("specifications")
.document(specId);
specDocumentRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if(task.getResult().exists()){
task.getResult();
}else{
task.getException();
}
}
});
}
I understand that the Firestore database calls are asynchronous in nature. Hence if I call the specificationQuery inside the for loop, without waiting for the result it is jumping to the next iteration.
Is there any way without changing the structure I can achieve the result, which will basically show all the product details along with their respective specification collection?
I'm fetching data from Firestore. I want to set the string data to a TextView. I'm able to get the data successfully . i.e I'm able to log it in the logcat. But when I try to set the text,it shows null in place of the data
Here is my code :
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
yourSector=view.findViewById(R.id.Sector_tv);
yourPincode=view.findViewById(R.id.Pincode_tv);
DocumentReference docRef = db.collection("customerUsers").document(userID);
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
pincode = document.getString("pincode");
sector = document.getString("sector");
Log.d("pincodetest", "onComplete: "+pincode);
} else {
Log.d("docref", "No such document");
}
} else {
Log.d("docref", "get failed with ", task.getException());
}
}
});
String sectorText="Sector : " + sector;
String pincodeText="Pincode : "+pincode;
yourSector.setText(sectorText);
yourPincode.setText(pincodeText);
My logcat (shows the correct data):
2020-06-14 00:41:43.779 14633-14633/? D/pincodetest: onComplete: 110001
When I set the text, on my screen I get:
Sector : null
PS: Strings pincode,sector have already been declared outside onViewCreated
The OnCompleteListener completes asynchronously so you need to place your setTexts within the onComplete method of it. In other words, the sector and pincode local variables are not populated with data when they are accessed for the concatenation to form the sectorText and pincodeText Strings.
In simpler terms, the onComplete method runs after the string concatenation. Therefore, during the string concatenation the value of the variables sector and pincode are still null.
I've done a little fix in the code below:
if (document.exists()) {
pincode = document.getString("pincode");
sector = document.getString("sector");
Log.d("pincodetest", "onComplete: "+pincode);
String sectorText="Sector : " + sector;
String pincodeText="Pincode : "+pincode;
yourSector.setText(sectorText);
yourPincode.setText(pincodeText);
}
This question already has answers here:
How to return a DocumentSnapShot as a result of a method?
(2 answers)
Closed 3 years ago.
Currently Running into an issue where I'm fetching data from firebase. I know it is because Firebase is asynchronous, so when I make my firebase call, it is executed on its own thread, and the current thread that it was called on continues to execute. I'm populating a list of objects with the data from firebase and I return that list of objects. The thing is, the list is always returning null because the execution of the firebase code isn't completed in time.
I created some asynchronous code that fetches from SQLite db that works fine, but this approach does not seem to work with firebase (I believe its due to firebases API being asynchronous) Here is my method to return a list of objects from firebase.
/** Method to get activity data from firebase.
* #param userInput the user query to select the data
* #return a list of activity models based on the query
* */
public List<ActivityModel> retrieveActivityData(String userInput) {
Log.d(TAG, "retrieveActivityData: starts");
List<ActivityModel> models = new ArrayList<ActivityModel>();
// execute the query in firebase
CollectionReference activfitCollection = db.collection("activity");
activfitCollection.orderBy("isoTimestamp")
.startAt(userInput)
.endAt(DateHelper.getDayEndingDate(userInput))
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Log.d(TAG, "onComplete: Getting data successful!");
// check to see if it exists
if (!task.getResult().isEmpty()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
// cast the document to the activity model
Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
ActivityModel model = mapToActivityModel(documentSnapshot);
models.add(model);
Log.d(TAG, "retrieveActivityData: array size" + models.size());
}
}
} else {
Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
}
});
Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
return models;
}
Option - 1: You can use LiveData to achieve this. Post value to LiveData when operation complete and observe that inside your activity or fragment
MutableLiveData<List<ActivityModel>> listMutableLiveData = new MutableLiveData<>();
public MutableLiveData<List<ActivityModel>> retrieveActivityData(String userInput) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
....
if (task.isSuccessful()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
....
models.add(model);
}
//Post value to live data from here
listMutableLiveData.postValue(models);
}
....
return listMutableLiveData;
}
And then observe like this
retrieveActivityData(userInput).observe(this, new Observer<List<ActivityModel>>() {
#Override
public void onChanged(List<ActivityModel> activityModels) {
//you can use list here
}
});
Option - 2: You can use callback function to get result when firebase operation complete.
Create an interface for callback
interface FirebaseResultListener {
void onComplete(List<ActivityModel> activityModels);
}
Configure your retrieveActivityData to handle this callback
public void retrieveActivityData(String userInput, FirebaseResultListener callback) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
....
if (task.isSuccessful()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
....
models.add(model);
}
//Invoke callback with result from here
callback.onComplete(models);
}
....
}
Implement this interface in your activity or fragment
retrieveActivityData(userInput, new FirebaseResultListener() {
#Override
public void onComplete(List<ActivityModel> activityModels) {
//you can use list here
}
});
Since the data is loaded async you cannot return the data, you should pass callback(Interface) in retrieveActivityData method, and use callback of interface to load the data, check the code bellow
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
MyFirebaseCallback myFirebaseCallback = new MyFirebaseCallback() {
#Override
public void dataLoaded(List<ActivityModel> activityModels) {
//set the data in recycler view
}
};
retrieveActivityData("myInput",myFirebaseCallback);
}
public void retrieveActivityData(String userInput, final MyFirebaseCallback callback) {
Log.d(TAG, "retrieveActivityData: starts");
// execute the query in firebase
CollectionReference activfitCollection = db.collection("activity");
activfitCollection.orderBy("isoTimestamp")
.startAt(userInput)
.endAt(DateHelper.getDayEndingDate(userInput))
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
List<ActivityModel> models = new ArrayList<ActivityModel>();
Log.d(TAG, "onComplete: Getting data successful!");
// check to see if it exists
if (!task.getResult().isEmpty()) {
for (DocumentSnapshot documentSnapshot : task.getResult().getDocuments()) {
Log.d(TAG, "retrieveActivityData: document = " + documentSnapshot.getId());
// cast the document to the activity model
Log.d(TAG, "retrieveActivityData: document data " + documentSnapshot.getData());
ActivityModel model = mapToActivityModel(documentSnapshot);
models.add(model);
Log.d(TAG, "retrieveActivityData: array size" + models.size());
}
}
callback.dataLoaded(models);
} else {
Log.e(TAG, "onComplete: Error getting documents: ", task.getException());
}
});
Log.d(TAG, "retrieveActivityData: array size outside " + models.size());
}
interface MyFirebaseCallback{
void dataLoaded(List<ActivityModel> activityModels);
}
You have guessed it right! The query is asynchronous so retrieveActivityData() shouldn't return List<ActivityModel> or it would always be null. You would have to use an Event Bus to fire an event as soon as your List is compiled inside onComplete() or use LiveData and observe it.
LiveData
ViewModel
EventBus
I have a Firestore database like this:
I want to access each of the different symptom data's i.e. "Anxiety_data" and its children which consists of timestamps, and then a dictionary, and place them into a RecyclerView using FirebaseUI FirebaseRecyclerViewAdapter
I have this model class:
public class EntryDataModel {
private String timestamp, symptom, severity, comment;
public EntryDataModel() {}
public EntryDataModel(String timestamp, String symptom, String severity, String comment) {
this.timestamp = timestamp;
this.symptom = symptom;
this.severity = severity;
this.comment = comment;
}
public String getTimestamp() {return timestamp;}
public String getSymptom() {return symptom;}
public String getSeverity() {return severity;}
public String getComment() {return comment;}
}
Here is my Query:
Query query = db.collection("users").document(user_id).collection("symptom_data");
Here is the Firebase RecyclerView Adapter:
void fireStoreRecyclerAdapterSetup() {
FirestoreRecyclerOptions<EntryDataModel> options = new FirestoreRecyclerOptions.Builder<EntryDataModel>()
.setQuery(query, EntryDataModel.class)
.build();
FirestoreRecyclerAdapter adapter = new FirestoreRecyclerAdapter<EntryDataModel, EntryDataHolder>(options) {
#Override
public void onBindViewHolder(EntryDataHolder holder, int position, EntryDataModel model) {
// Bind the Chat object to the ChatHolder
// ...
System.out.println("Query: " + query.toString());
}
#Override
public EntryDataHolder onCreateViewHolder(ViewGroup group, int i) {
// Create a new instance of the ViewHolder, in this case we are using a custom
// layout called R.layout.message for each item
View view = LayoutInflater.from(group.getContext())
.inflate(R.layout.entry_data_recyclerview_item, group, false);
return new EntryDataHolder(view);
}
};
recyclerView.setAdapter(adapter);
adapter.startListening();
}
}
I am not sure how I should set this up so that I take all the Timestamp arrays from each of the symptom data fields, and have them all together in a list that I can use for the recyclerView.
Maybe I cannot use FirebaseUI Recycler adapter, or I need to iterate through each different symptom field first and build + append a list? Hopefully I am clear in what I would like to do Thank you.
EDIT: I have done it on iOS, This is the result that I want:
EDIT: I have added this, getting the names of each individual document, and then once I have that in a for loop I am now trying to get the Array values and add that to an EntryDataModel list, then I can use my own adapter:
EDIT: This works, I get the data I need, from each Document. Now I just need to be able to Iterate over the fields and timestamps, and use my model to create a list. How can I do that? Log.d("example", "DocumentSnapshot data: " + document.getData()); this prints: D/example: DocumentSnapshot data: {1558879769={severity=Mild, symptom=Anxiety, comment=Mild anxiety at work., timestamp=1558879769}, 1558879745={severity=Mild, symptom=Anxiety, comment=Feeling relaxed watching TV., timestamp=1558879745}, 1558879710={severity=Moderate, symptom=Anxiety, comment=Walking the cat., timestamp=1558879710}, 1558879827={severity=Moderate, symptom=Anxiety, comment=Taking the cat for a walk., timestamp=1558879827}, 1558888729={severity=Mild, symptom=Anxiety, comment=The cat is fed up with walking., timestamp=1558888729}} Now I just need to get each timestamp array and add them to a seperate list, and i can then do this for each document and have a full list of all the timestamp arrays.
void getSymptomData() {
final CollectionReference colRef = db.collection("users").document(user_id).collection("symptom_data");
colRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<String> list = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
list.add(document.getId());
DocumentReference docRef = colRef.document(document.getId());
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Log.d("Iteration", "DocumentSnapshot data: " + document.getData());
} else {
Log.d("NoDoc", "No such document");
}
} else {
Log.d("Failed", "get failed with ", task.getException());
}
}
});
}
Log.d("listylist", list.toString());
} else {
Log.d("tag", "Error getting documents: ", task.getException());
}
}
});
}
The automatic mapping that the Firebase SDK can do from a Firestore document to a Java object, requires that each field name from the document matches a property name in the Java class. It has no mechanism to deal with dynamic fields, such as the timestamps in your example.
So you will have to do your own conversion. For that you can use the public T get (FieldPath fieldPath, Class<T> valueType) or public T get (String field, Class<T> valueType) method, which allow you to get an object from a specific field that you specify. So you will have to loop over the timestamp fields, but after that the Firebase SDK can map the severity, symptom, and timestamp properties to the object.
I'm kind of new to android studio and firestore database and
I'm having some trouble with querying my second firestore collection. As the title says, i am querying two collections, first one is:
with the code :
firestore = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.build();
firestore.setFirestoreSettings(settings);
firestore.collection("Obiective").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
//<--------- Check if firestore entry is already downloaded into file --------->
SingletonObjectivesId.getInstance().getIds().clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Log.d(TAG, task.getResult().size() + " number of documents");
SingletonObjectivesId.getInstance().setSize(task.getResult().size());
if(document.exists() && document != null) { ...
and the second collection have the following format:
with the code:
firestore.collection("Routes")
.get()
.addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
Log.d(TAG, task.getResult().size() + " = task.getResult().size()");
for (QueryDocumentSnapshot document : task.getResult()) {
objectives_id.clear();
id_route = document.getId();
if(document.exists() && document != null) {
Map<String, Object> map = document.getData();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String field_name = entry.getKey() + "";
String id = document.getString(field_name) + "";
objectives_id.add(id);
}
}
routes.add(new Route(objectives, objectives_id, id_route));
}
} else {
Log.d(TAG, "Error getting documents: ", task.getException());
}
}
});
As you can see in the second code i added a Log.d ( after if (task.isSuccessful()) ) who will display the number of documents. In my case, the first query Log.d returns 3 and the second returns 0 despite the fact that i have 2 documents in there. How can i access this 2 documents ?
Thank you.
Firebase APIs are asynchronous, meaning 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 that method returns immediately, the number of documents that you try to log, is not 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 to move the code that queries the second collection inside the first callback (inside the onComplete() method) so-called nested queries, 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.
After i followed the steps from the video, i updated the code like this:
I have a global variable firestore created at the beginning of my class
private FirebaseFirestore firestore;
I have two methods readDataObjective and readDataRoute and two interfaces FirestoreCallback and FirestoreCallbackRoutes
readDataRoutes
private void readDataRoute(FirestoreCallbackRoute firestoreCallbackRoute){
firestore.collection("Trasee").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
for (QueryDocumentSnapshot document : task.getResult()) { ...
readDataObjective
private void readDataObjective(FirestoreCallback firestoreCallback){
firestore.collection("Obiective").get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
SingletonObjectivesId.getInstance().getIds().clear();
for (QueryDocumentSnapshot document : task.getResult()) { ...
Interfaces
private interface FirestoreCallback{
void onCallback(ArrayList<Objective> list);
}
private interface FirestoreCallbackRoute{
void onCallback(ArrayList<Route> list);
}
And in onCreate method i call readDataObjective and readDataRoute like this
firestore = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder().build();
firestore.setFirestoreSettings(settings);
readDataObjective(new FirestoreCallback() {
#Override
public void onCallback(ArrayList<Objective> list) {
for(Objective item : list){
//Create plainText Object - delimiter "/~/"
String data = "Title:" + item.getTitle() + "/~/" +
............................
} else if(str.contains("Longitude:")){
obj.setLongitude(str.substring(10,str.length()));
}
start = crt + 2;
}
}
SingletonObjectivesArray.getInstance().getObjectives().add(obj);
}
readDataRoute(new FirestoreCallbackRoute() {
#Override
public void onCallback(ArrayList<Route> list) {
Log.d(TAG, 2 + " ");
ArrayList<Objective> routeObjectives = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
routeObjectives.clear();
for (int j = 0; j < SingletonObjectivesArray.getInstance().getObjectives().size(); j++){ ...
With the mention that readDataRoute is called inside readDataObjective, at the end of it.
I noticed that the problem is not only with the second query, but with the first one too. I added a new document into the first collection and after running the code, the first query return the old data ( without my new entry ).