Wait Until Firestore Data Is Retrieved to Launch an Activity

Wait until Firestore data is retrieved to launch an activity

You cannot use something now that hasn't been loaded yet. Firestore loads data asynchronously, since it may take some time for this. Depending on your connection speed and the state, it may take from a few hundred milliseconds to a few seconds before that data is available. If you want to pass a list of objects of type Attraction to another method, just call that method inside onSuccess() method and pass that list as an argument. So a quick fix would be this:

FirebaseFirestore rootRef = FirebaseFirestore.getInstance();
CollectionReference attractionsRef = rootRef.collection("attractions");

attractionsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Attraction> attractionsList = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Attraction attraction = document.toObject(Attraction.class);
attractionsList.add(attraction);
}
methodToProcessTheList(attractionsList);
}
}
});

So remember, onSuccess() method has an asynchronous behaviour, which means that is called even before you are getting the data from your database. If you want to use the attractionsList outside that method, you need to create your own callback. To achieve this, first you need to create an interface like this:

public interface MyCallback {
void onCallback(List<Attraction> attractionsList);
}

Then you need to create a method that is actually getting the data from the database. This method should look like this:

public void readData(MyCallback myCallback) {
attractionsRef.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComplete(@NonNull Task<QuerySnapshot> task) {
if (task.isSuccessful()) {
List<Attraction> attractionsList = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Attraction attraction = snapshot.toObject(Attraction.class);
attractionsList.add(attraction);
}
myCallback.onCallback(attractionsList);
}
}
});
}

In the end just simply call readData() method and pass an instance of the MyCallback interface as an argument wherever you need it like this:

readData(new MyCallback() {
@Override
public void onCallback(List<Attraction> attractionsList) {
//Do what you need to do with your list
}
});

This is the only way in which you can use that attractionsList object outside onSuccess() method. For more informations, you can take also a look at this video.

How to make a function which relies on a previous functions data wait until that data is set

Easiest solution is to just call your second function inside your first subscription:


ngOnInit() {
this.getActiveCities();
}

getActiveCities() {
this.fbService.getActiveCities().subscribe(
data => {
this.activeCities = data;
this.getAllFamilies();
},
error => {
console.log("Error retrieving active cities");
}
);
}

Although a better design is to keep everything as observables and subscribe with the async pipe in html.

export class HomeComponent implements OnInit {
constructor(private fbService: FirebaseService) { }

activeFamiliesMap = new Map<string, Observable<Family[]>>();

activeCities$: Observable<any[]> = this.fbService.getActiveCities().pipe(
tap((activeCities) => {
for (const city of activeCities) {
this.activeFamiliesMap.set(city.id, this.activeFamilies(city.id));
}
}),
catchError((err) => {
console.error('Error retrieving active cities', err);
return of([]);
})
);

activeFamilies(id: any): Observable<Family[]> {
return this.fbService.getFamiliesForCity(id).pipe(
catchError((err) => {
console.error('Error retrieving families for city id:', id, err);
return of([]);
})
);
}
}

Just an example of how to display the data:

<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>

<ng-container *ngFor="let city of activeCities$ | async">
<div>City Id: {{ city.id }}</div>
<div>Families:</div>
<pre>{{ activeFamiliesMap.get(city.id) | async | json }}</pre>
</ng-container>

stackblitz: https://stackblitz.com/edit/angular-ivy-trkuqx?file=src/app/app.component.ts


And an even better design may be for your service to return an observable of the map, although it's an ugly beast of an observable. At least it hides the logic from your components:

Service

  getFamilyMap(): Observable<Map<string, Family[]>> {
return this.getActiveCities().pipe(
map((activeCities) => {
return activeCities.map((city) => {
return this.getFamiliesForCity(city.id).pipe(
map((families) => {
return { id: city.id, families };
})
);
});
}),
switchMap((data) => forkJoin(data)),
map((data) => {
const res = new Map<string, Family[]>();
for (const entry of data) {
res.set(entry.id, entry.families);
}
return res;
}),
catchError((err) => {
console.error('Error retrieving family map', err);
return of(new Map<string, Family[]>());
})
);
}

Component

export class HomeComponent {
constructor(private fbService: FirebaseService) {}

activeCities$ = this.fbService.getActiveCities();
familyMapEntries$ = this.fbService
.getFamilyMap()
.pipe(map((map) => Array.from(map)));
}

I use Array.from() rather than map.entries() because iterators tend to throw changedAfterChecked errors.

Html

<div>Active Cities</div>
<pre>{{ activeCities$ | async | json }}</pre>

<ng-container *ngFor="let entry of (familyMapEntries$ | async)">
<div>City Id: {{ entry[0] }}</div>
<div>Families:</div>
<pre>{{ entry[1] | json }}</pre>
</ng-container>

stackblitz: https://stackblitz.com/edit/angular-ivy-ffzpya?file=src/app/firebase.service.ts

How to wait for Firebase data to be fetched before progressing?

So I managed to fix it somehow. Thanks to Julian for the help

What I did was create an array of promises which will be executed whenever the data changes. The code is:

import React, {useCallback, useEffect, useState} from 'react';
import {ActivityIndicator, Dimensions, Text, View} from 'react-native';
import firestore from '@react-native-firebase/firestore';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
import FolloweringScreens from './FolloweringScreens';
import {TouchableOpacity} from 'react-native-gesture-handler';

const {width, height} = Dimensions.get('screen');

function Following({urlname, navigation}) {
const [followingData, setfollowingData] = useState();
const [loading, setLoading] = useState(true);

// Following counts, displayname, image
const fetchData = useCallback(() => {
const dataRef = firestore().collection('usernames');

dataRef
.doc(urlname)
.collection('Following')
.limit(25)
.onSnapshot((snapshot) => {
let promises = [];
snapshot.forEach((doc) => {
const promise = dataRef
.doc(doc.id.toLowerCase())
.get()
.then((followerDoc) => {
const data = followerDoc.data();

return {
profileName: doc.id,
displayName: data.displayName
? data.displayName
: data.userName,
followerCount:
data.followers !== undefined ? data.followers : 0,
followingCount:
data.following !== undefined ? data.following : 0,
image: data.imageUrl ? data.imageUrl : null,
};
});
promises.push(promise);
});
Promise.all(promises)
.then((res) => setfollowingData(res))
.then(setLoading(false));
});
}, []);

useEffect(() => {
const dataRef = firestore().collection('usernames');

const cleanup = dataRef
.doc(urlname)
.collection('Following')
.limit(25)
.onSnapshot(fetchData);

return cleanup;

// fetchData();
}, [urlname, fetchData]);

return (
<>
<View
style={styles}>
<TouchableOpacity onPress={() => navigation.openDrawer()}>
<Icon name="menu" color="#222" size={30} />
</TouchableOpacity>
<Text style={{left: width * 0.05}}>Following</Text>
</View>

{loading ? (
<ActivityIndicator size="large" color="black" />
) : (
<>
<FolloweringScreens data={followingData} />
</>
)}
</>
);
}

export default Following;

How to wait for data to be loaded from Firestore before running functions Angular

You could just call formsInit() from your getEmployees function like so:

export class ManageEmployeesComponent implements OnInit {
newEmployee: FormGroup;
Employees: Employee[];

selectedOrganization: string;

@Input() set selectOrg(org: string) {
this.selectedOrganization = org;
this.getEmployees();
}

getEmployees() {
this.employeeService.getEmployeeList(this.selectedOrganization).subscribe(data => {
this.Employees = data.map(e => {
return {
id: e.payload.doc.id,
...e.payload.doc.data() as Employee
};
});
this.formsInit();
});
}

formsInit() {
//some reactive form stuff
}
}

Firebase Realtime Database wait until the data is retrieved

You can achieve it using this way by utilizing the Task. Tasks.whenall() will wait until all task are done.

fun functionA() {

val taskList = mutableListOf<Task<DataSnapshot>>()
val resultFileDataList = List<DataSnapshot>()

for ((key, value) in filesMap) {
val databaseReferenceTask: Task<DataSnapshot> = database.child("files").child(key).get()
taskList.add(databaseReferenceTask)

val resultTask = Tasks.whenAll(taskList)
resultTask.addOnCompleteListener {
for (task in taskList) {
val snapshotKey: String? = task.result.key
val snapShotValue = task.result
}

callFunctionB()
}
}
}


Related Topics



Leave a reply



Submit