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
Does Spring @Transactional Attribute Work on a Private Method
Failed to Load the Jni Shared Library (Jdk)
How to List Target Platforms. Please Make Sure the Android Sdk Path Is Correct
How Delete a Collection or Subcollection from Firestore
Android Studio 3.1.3 Gradle Sync Error. Could Not Download Gradle-Core.Jar
Difference Between Using Java.Library.Path and Ld_Library_Path
Using Singleton Design Pattern for SQLitedatabase
How to Open a Command Terminal in Linux
What Would Cause a Java Process to Greatly Exceed the Xmx or Xss Limit
How to Mock a Final Class With Mockito
Why Can't Static Methods Be Abstract in Java
When Exactly Is It Leak Safe to Use (Anonymous) Inner Classes
Convert JSON Array to Normal Java List
How to Programmatically Set Drawableleft on Android Button
Setting/Changing the Ctime or "Change Time" Attribute on a File
Which Operating Systems Support Native (Inotify-Like) File Watching in Java