Can't read data from firestore database (android application) - java

I can't read data from the firestore database. I can write to it although sometimes the data appears on firestore console (very) late.
And i can Delete a record although it is only delete (from firestore console) when i do a write.
It is some stupid simple error that i make, but i can't find it. Buffers? Setting? i don't know :-(
It looks like firestore is delayed responding, it never reach the line with:
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful())
....
Below the logcat lines...
I am using java/android app
Thanks in advance for any help
,, E/PlayMissionActivity: sleep 2 <==
,, E/PlayMissionActivity: sleep 2 <==
,, W/Firestore: (0.6.6-dev) [OnlineStateTracker]: Could not reach Firestore backend.
,, E/PlayMissionActivity: sleep 2 <==
My Code (just a copy of the samples)
public class MyPlay extends AppCompatActivity {
FirebaseFirestore dbRef = null;
protected void onCreate(Bundle savedInstanceState) {
{
initDB();
getDocument();
int q=0;
while (q<10) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
q += 1;
Log.e(LOG_TAG, "sleep 2 " + "<==");
}
}
public void initDB() {
dbRef = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setTimestampsInSnapshotsEnabled(true)
.build();
dbRef.setFirestoreSettings(settings);
}
public void getDocument() {
DocumentReference docRef = dbRef.collection("users").document("123");
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(TAG, "----------------------------> DocumentSnapshot data: " + document.getData());
} else {
Log.d(TAG, "----------------------------> No such document");
}
} else {
Log.d(TAG, "----------------------------> get failed with ", task.getException());
}
}
});
}
}
app/build.gradle:
compile 'com.google.firebase:firebase-core:16.0.1'
compile 'com.google.firebase:firebase-firestore:17.0.2'
compile 'com.google.code.gson:gson:2.8.4'
Firestore rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
Firestore data:
users/123/born=1815
users/123/first="Ada"
users/123/last="Lovelace"

Solved it, it was indeed fundamental.
Basically i didn't understand the concept of threads, events etc.
When a thread (event) is running, the thread that is reading firestore db is waiting for finishing the main thread. (Pseudo code, when the onCreate is finished)
Probably someone can explain it better i am just an old amature
So what isn't working (pseudo code android activity):
onCreat{
object = readFirestore
print object.id <== this line will give you an error
}
readFirestore() {
read id
onSucces
object = document.toObject()
}
So what's working fine:
onCreat{
object = readFirestore
}
readFirestore() {
read id
onSucces
object = document.toObject()
print object.id <== this line will print your result
}

Related

Android - Transaction - Task is not yet complete

I found a few examples of "task not yet complete," but have not found any examples for transactions. I am using a transaction because in my application I need the operation to be able to fail if there is no internet connection. I can detect this with a transaction.
I have a Collection with Documents. I am trying to obtain the names of the documents. Sometimes the code works fine, but majority of the time I get the "task not yet complete" error. The frustrating thing is that I have a callback for "onComplete" so it's weird that the transaction isn't complete when the callback is... called.
I get the "task not yet complete exception in the onCompleteListener(). What's frustrating is that I even check to ensure if (task.isSuccessful() && task.isComplete()). Do I need to use a continuation? If so, please provide an example - I just don't quite understand it yet.
// Note: states is an ArrayList<String>
// snapshot is a QuerySnapshot
public void getStatesList(){
states.clear();
states.add("Select A State");
db.runTransaction(new Transaction.Function<Void>() {
#Nullable
#Override
public Void apply(#NonNull Transaction transaction) {
// Collect Snapshot data
snapshot = db.collection("DATA").get();
return null;
}
}).addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if(task.isSuccessful() && task.isComplete()){
try{
for(QueryDocumentSnapshot document : snapshot.getResult()){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
} catch (Exception e){
e.printStackTrace(); // Transaction is not yet complete
sendResponseToActivity("Fail", RESULT_OK);
}
}
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
if(e.getMessage().contains("UNAVAILABLE"))
sendResponseToActivity("NoInternet", RESULT_OK);
else
sendResponseToActivity("Fail", RESULT_OK);
}
});
} // End getStatesList()
In your code, you call db.collection("DATA").get() but this doesn't operate inside of the transaction (that is done using transaction.get(docRef), transaction.update(docRef, newData) and so on). Just because the Task of the "transaction" has completed, it doesn't mean that this rogue database call has.
If the purpose is to get the server's copy of /DATA and only the server's copy, use Query#get(Source source) with Source.SERVER. (Note: pending server writes may be merged into the data to reflect the most up-to-date copy of the server's data)
public void getStatesList(){
states.clear();
states.add("Select A State");
db.collection("DATA").get(Source.SERVER)
.addOnSuccessListener(querySnapshot -> {
for(QueryDocumentSnapshot document : querySnapshot){
states.add(document.getId());
}
sendResponseToActivity("Success", RESULT_OK);
})
.addOnFailureListener(ex -> {
if (ex.getMessage().contains("UNAVAILABLE")) {
sendResponseToActivity("NoInternet", RESULT_OK); // RESULT_OK?
} else {
sendResponseToActivity("Fail", RESULT_OK); // RESULT_OK?
}
});
}
However, because the above version uses and modifies global variables, I would implement it using:
/** Returns server's list of states */
public Task<ArrayList<String>> getStatesList() {
return db.collection("DATA").get(Source.SERVER)
.onSuccessTask(querySnapshot -> {
ArrayList<String> states = new ArrayList<>();
// NOTE: states.add("Select A State"); was removed here
for (QueryDocumentSnapshot document : querySnapshot) {
states.add(document.getId());
}
return Tasks.forResult(states);
});
}
Then use it like so:
getStatesList().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
ArrayList<String> states = task.getResult();
// do something with states, like update UI
// don't forget that "Select A State" isn't in the array
} else {
Exception ex = task.getException();
if (ex.getMessage().contains("UNAVAILABLE")) {
// offline, do something
} else {
// unexpected error, do something
}
}
})

Getting Data from Firestore Never Completes Successfully

I'm new to NoSQL databases, but I'm attempting to use Firestore with an Android mobile application I'm developing.
I can write to the DB without any issues, but I can't read data. See code below:
DocumentReference docRef = db.collection("users").document("abc#gmail.com");
docRef.get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
#Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
User userFromDB = documentSnapshot.toObject(User.class);
}
});
When I was debugging, program execution didn't enter the 'onSuccess' function.
The code I'm using is based off the documentation (Get Data with Cloud Firestore - Custom Objects). I made sure that the 'User' fields in my code match the ones in the DB, and they all have 'get' methods.
Also, these are my rules:
match /{document=**} {
allow read, write: if true
}
I've been stuck on this for a while, any help would be highly appreciated.
The onSuccessListener is for write actions to the database. For getting data you need to use the onCompletedListener as shown in the official documentation:
DocumentReference docRef = db.collection("users").document("abc#gmail.com");
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(TAG, "DocumentSnapshot data: " + document.getData());
} else {
Log.d(TAG, "No such document");
}
} else {
Log.d(TAG, "get failed with ", task.getException());
}
}
});

Wait until Firestore data is retrieved

I need after inner foreach finish and add all data in list then send to interface
but when but this line " view.setOrders(orderList); " below " orderList.add(order); "
my code run okye but not that is not performance , I need best way to collection list then send to interface ..
public ListenerRegistration getOrders() {
view.showLoading();
ListenerRegistration listenerRegistration = refOrders.addSnapshotListener((queryDocumentSnapshots, e) -> {
view.hideLoading();
if (e != null) {
view.onErrorMessage(e.getMessage());
} else {
List<Order> orderList = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots) {
Order order = snapshot.toObject(Order.class);
order.setOrderId(snapshot.getId());
refUsers.document(order.getPhone()).get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
}
}
});
}
//Here Back List size = 0
view.setOrders(orderList);
}
});
return listenerRegistration;
}
Since get() method is asynchronous which means that the code after the CompletionListener will be executed first and then after the data is retrieved the listener will get executed.
Therefore to solve the issue add the following line view.setOrders(orderList); inside the CompletionListener
if (task.isSuccessful()) {
User user = task.getResult().toObject(User.class);
order.setName(user.getName());
order.setAddress(user.getAddress());
orderList.add(order);
view.setOrders(orderList);

Firestore not querying second collection

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 ).

Cloud Firestore - Access Field for Realtime Updates

I am trying to access a field labeled vote_count via Cloud Firestore realtime updates.
Below is my data structure:
Previously, under Firebase Realtime Database, I would .addValueEventListener() and drill down to the "answer." However, with Cloud Firestore, it is a bit more complex.
mStoreSelectedPollRef.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(final DocumentSnapshot documentSnapshot, FirebaseFirestoreException e) {
if (e != null){
Log.v("LISTEN", "LISTEN_FAILED");
return;
}
if (documentSnapshot != null){
Log.v("Current Data", String.valueOf(documentSnapshot.getData()));
mStoreSelectedPollRef.collection(ANSWERS_LABEL).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
updatePollResultAnswersDynamically(task.getResult().size(), documentSnapshot);
}
});
} else {
Log.v("Current Data", "Current Data Nulll");
}
}
});
Right now, I am adding a call to .get() within my Snapshot Listener, which seems to be inefficient. I was curious how I would access the:
Total number of answer Documents.
Each individual answer
Given this snippet:
mStoreSelectedPollRef.collection(ANSWERS_LABEL).get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
updatePollResultAnswersDynamically(task.getResult().size(), documentSnapshot);
}
});
You can get the total number of answer documents with:
task.getResult().size()
Since you already have this code, I'm not really sure what you're asking. If you're asking if there is a way to get the count without getting the documents, look here: https://stackoverflow.com/a/46555026
To access the individual answer documents, you loop over the query snapshot:
for (DocumentSnapshot document : task.getResult()) {
Log.d(TAG, document.getId() + " => " + document.getData());
}

Categories