How to Make App Wait Until Firebase Request Is Finished

How can I wait until Firebase Database data is retrieved in Flutter?

A listener is something that waits for changes (new documents added etc.) and notifies you whenever that happens. So it isn't something that starts/ends after you call it - you typically create it in the init of your Flutter class, and it changes state whenever something changes.

It looks like you want to grab all the users, once, and not listen like you're currently doing.

So instead of listening, perhaps just read the data. Something like:

// fetch all the documents
final allDocs =
await FirebaseFirestore.instance.collection('users').get();
// map them to something like your strings.
final thedetails = allDocs.docs.map((DocumentSnapshot e) => e.data()!.toString()).toList();
return thedetails;

Is there any way to make my app wait for data to be retrieved from Firebase before continuing the code?

you can simulate fetching user synchronous by making recursion (function which call it self until index becomes bigger then size of list of uids).

So first thing you want to define adapter and List of strings (which represent user names). When you do that, you can call recursion, which will populate your List and notifyDataSetChanged. Here is the example

// Define empty list of user names, which you will populate later with recursion
List<String> userNames = new ArrayList<String>();
// Connect adapter with empty list
ArrayAdapter<String> adapter = new ArrayAdapter<>(TutorsListActivity.this, R.layout.item_subtopic, userNames);
// Set adapter to ListView
sListView2.setAdapter(adapter);

// Call recursion with list of uids and starting index of 0
getUserSync(list, 0);

private void getUserSync(List<String> list, int i) {
if (i < 0 || i > list.length - 1) {
// If index i is out of bounds for list, we break the recursion
return;
}

String uid = list.get(i);

Toast.makeText(TutorsListActivity.this, uid, Toast.LENGTH_LONG).show();

subTopicsDatabase.collection("users").document(uid).get().addOnSuccessListener(new OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
// When we load document, we fetch name and add it to the list which is connected to adapter
// After that, we call adapter.notifyDataSetChanged which will update ui
// When all that is done, we call getUserSync, to fetch user name for next uid
if (documentSnapshot.exists()) {
String stName = documentSnapshot.getString("name");
if (stName != null) {
userNames.add(stName);
adapter.notifyDataSetChanged();
}
}
getUserSync(list, i++);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
// If enything goes wrong, we break the recursion
return;
}
});
}

If you have any troubles, feel free to comment..

Wait for Firebase to load before returning from a function

(Variations on this question come up constantly on SO. I can never find a good, comprehensive answer, so below is an attempt to provide such an answer)

You can't do that. Firebase is asynchronous. Its functions take a completion handler and return immediately. You need to rewrite your loadFromFirebase function to take a completion handler.

I have a sample project on Github called Async_demo (link) that is a working (Swift 3) app illustrating this technique.

The key part of that is the function downloadFileAtURL, which takes a completion handler and does an async download:

typealias DataClosure = (Data?, Error?) -> Void

/**
This class is a trivial example of a class that handles async processing. It offers a single function, `downloadFileAtURL()`
*/
class DownloadManager: NSObject {

static var downloadManager = DownloadManager()

private lazy var session: URLSession = {
return URLSession.shared
}()

/**
This function demonstrates handling an async task.
- Parameter url The url to download
- Parameter completion: A completion handler to execute once the download is finished
*/

func downloadFileAtURL(_ url: URL, completion: @escaping DataClosure) {

//We create a URLRequest that does not allow caching so you can see the download take place
let request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 30.0)
let dataTask = URLSession.shared.dataTask(with: request) {
//------------------------------------------
//This is the completion handler, which runs LATER,
//after downloadFileAtURL has returned.
data, response, error in

//Perform the completion handler on the main thread
DispatchQueue.main.async() {
//Call the copmletion handler that was passed to us
completion(data, error)
}
//------------------------------------------
}
dataTask.resume()

//When we get here the data task will NOT have completed yet!
}
}

The code above uses Apple's URLSession class to download data from a remote server asynchronously. When you create a dataTask, you pass in a completion handler that gets invoked when the data task has completed (or failed.) Beware, though: Your completion handler gets invoked on a background thread.

That's good, because if you need to do time-consuming processing like parsing large JSON or XML structures, you can do it in the completion handler without causing your app's UI to freeze. However, as a result you can't do UI calls in the data task completion handler without sending those UI calls to the main thread. The code above invokes the entire completion handler on the main thread, using a call to DispatchQueue.main.async() {}.

Back to the OP's code:

I find that a function with a closure as a parameter is hard to read, so I usually define the closure as a typealias.

Reworking the code from @Raghav7890's answer to use a typealias:

typealias SongArrayClosure = (Array<Song>?) -> Void

func loadFromFireBase(completionHandler: @escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data

if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
})
}

I haven't used Firebase in a long time (and then only modified somebody else's Firebase project), so I don't remember if it invokes it's completion handlers on the main thread or on a background thread. If it invokes completion handlers on a background thread then you may want to wrap the call to your completion handler in a GCD call to the main thread.


Edit:

Based on the answers to this SO question, it sounds like Firebase does it's networking calls on a background thread but invokes it's listeners on the main thread.

In that case you can ignore the code below for Firebase, but for those reading this thread for help with other sorts of async code, here's how you would rewrite the code to invoke the completion handler on the main thread:

typealias SongArrayClosure = (Array<Song>?) -> Void

func loadFromFireBase(completionHandler:@escaping SongArrayClosure) {
ref.observe(.value, with: { snapshot in
var songArray:Array<Song> = []
//Put code here to load songArray from the FireBase returned data

//Pass songArray to the completion handler on the main thread.
DispatchQueue.main.async() {
if songArray.isEmpty {
completionHandler(nil)
}else {
completionHandler(songArray)
}
}
})
}

Wait until firebase has finished loading data (vue) / 'await' not working

The onSnapshot() method is not an asynchronous method. As explained in the doc, it "attaches a listener for DocumentSnapshot events".

So, you should use it if you want to set a continuous listener to the Firestore document: if something changes in the document (e.g. a field gets a new value) the listener will be triggered. Note that "an initial call using the callback you provide creates a document snapshot immediately with the current contents of the single document" (see the doc).

You get the Firestore document data only in the callback function that you pass to onSnapshot(), as follows:

created() {
institutesCollection.doc('FbdYJ5FzQ0QVcQEk1KOU').onSnapshot(doc => {
this.settings = doc.data();
console.log(this.settings);
});
}

As Nilesh Patel mentioned, note that you need to use an arrow function in order to use this in this callback. Also note that the created function does NOT need to be async.

Finally, note that it is a good practice to call the unsubscribe function returned by onSnapshot() when you destroy the Vue.js component. Use the beforeDestroy hook as follows:

// ...
data() {
return {
settings: null,
firestoreListener: null
}
},
created() {
this.firestoreListener = institutesCollection.doc('FbdYJ5FzQ0QVcQEk1KOU').onSnapshot(doc => {
this.settings = doc.data();
console.log(this.settings);
});
},
beforeDestroy() {
if (this.firestoreListener) {
this.firestoreListener();
}
},

On the other hand, if you just want to read the document data ONLY ONCE in the created Vue.js hook, you should use the get() method, as follows:

async created() {
const doc = await institutesCollection.doc('FbdYJ5FzQ0QVcQEk1KOU').get();
if (doc.exists) {
this.settings = doc.data();
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}

Note that, in this case, the created function NEEDS to be async, since get() is asynchronous.


More details on the difference between get() and onSnapshot() in this SO answer.

Wait for firebase cloud trigger function to end before launching http request

What I am hearing here is that you have a classic race condition. I am understanding that your user click "Register" and that causes a new document to be added to the database. There is then a trigger on that insertion which updates other documents/fields. It is here that you have introduced parallelism and hence the race.

Your design needs to change such that there is a "signal" sent back from GCP when the updates that were performed asynchronously have completed. Since the browser doesn't receive unsolicited signals, you will have to design a solution where your browser calls back to GCP such that the call doesn't return until the asynchronous changes have been completed. One way might be to NOT have an onCreate trigger but instead have the browser explicitly call a Cloud Function to perform the updates after the initial insertion (or even a Cloud Function that performs the insertion itself). The Cloud Function should not return until the data is consistent and ready to be read back by the browser.

How to wait until firebase save all data?

Sorry I don't know Rx, but here is a way to insert all the data in one API call. The onComplete() method is the update complete callback.

Map<String, Object> values = new HashMap<>();
for (Tag tag : databaseTags){
if (user.getTags().containsKey(tag.getId())){
values.put(idTag + "/users/" + userUid, true);
}else{
tagRepository.removeUserOnTag(String.valueOf(tag.getId()), user.getUid());
}
}

reference.updateChildren(values, new DatabaseReference.CompletionListener() {
@Override
public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
// all data saved, do next action
}
});

Hope this helps :)



Related Topics



Leave a reply



Submit