Run Firebase Cloud Function before page load Flutter - firebase

I have a Firebase Cloud Function that creates a document when a new user signs up. The document that gets created by the function is where the user data will be stored. The process is as such:
User signs up
User document created in Firestore
Firebase Function triggered to create 'other' document
User sees homepage
Homepage uses data from 'other' document
The problem I have is the user is going straight to the homepage before the Firebase Function is executed and the 'other' document is not created yet.
This means the user is just seeing a CircularProgressIndicator because the page is loading before the 'other' document exists.
It works fine if the user clicks away from that page and returns to it, because by that time the 'other' document exists. Likewise, when I add a 5 second delay on initially loading the homepage, it works because the Firebase Function has time to execute - but this is not a nice solution.
I am wondering how I can ensure the Firebase Function has executed and the 'other' document created before loading the homepage?
initState
void initState() {
super.initState();
final user = Provider.of<UserClass>(
context,
listen: false);
final uid = user.uid;
_houseID = getHouseID(uid);
}
Future returning ID of document created by Firebase Function
Future<String> getHouseID(uid) async {
String houseID;
await Future.delayed(Duration(milliseconds: 5000)); // with this delay it works fine
await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.collection('userHouses') // this collection is being created by a Cloud Function
.get()
.then(
(value) {
houseID = value.docs.single.id;
},
);
return houseID;
}
FutureBuilder
return FutureBuilder(
future: _houseID,
builder: (BuildContext context, AsyncSnapshot snapshot) {
hhid = snapshot.data;
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator()); // this runs forever when the user first signs up
} else {
return // homepage using hhid to retrieve user data

You can open a stream which listens to that specific document after the user signs up. The stream initially may be empty, so you can check if the document exists. Once the document is written, the stream will be updated and then you can close it if you're done.
here's a simple code that explains the idea:
final subscription = FirebaseFirestore.instance.doc('path-to-document').snapshots().listen((event) {
if (event.exists) {
// do something with the data
final data = event.data();
// update your state
// .... some code
// call a function to close the subscription if you don't need it
closeSubscription();
}
});
closeSubscription() {
subscription.cancel();
}

Related

Download data before build - method

I want to build a contactScreen for my flutter app with an array downloaded from firebase. The array is correctly downloaded, but while building the app the array stays empty. With some tests (the print-statements) I figured out, that the app builds the screen and at the same time download the data (with getUserData()). So the download is not fast enough. (After a reload everything works fine).
#override
void initState() {
super.initState();
getUserData();
print(contacts);
}
getUserData() async {
var userData = await FirebaseFirestore.instance
.collection('users')
.doc(currentUser)
.get();
print(userData.data()!['contacts']);
contacts = userData.data()!['contacts'];
}
Is it possible to download the data before the build method every time? I can't use the straembuilder because it's already in use for another download.
create a variable bool isLoading = true
getUserData() {
//await retrieve data
//after data is retrieved
setState(() {
isLoading = false;
});
}
In your build method:
isLoading ? Container(child: Text('Loading...')) : YourScreen()

Does Firestore perform a read every time a page is built?

I'm designing an app with Flutter and using Firestore as my database.
I have this page which is basically taking everything from a specific collection in Firestore and listing it for the user.
I'm not yet so familiar with how things work in the Firestore side and would like to make this is optimized as possible, to avoid unnecessary database reads. My question in this situation is: will Firestore perform a read in this collection every time the user opens this screen? Or only when something in this collection changes?
Let's say the user opened this list, but then went to his profile, and after that went back to this list. If the collection didn't have any changes in the meanwhile, will there be 1 or 2 reads in the database?
Would I need to use a Stream for that to happen?
Implementation of this List in Flutter
FutureBuilder<List<Store>>(
future: DatabaseService().storeList,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Loading();
} else {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) => StoreTile(store: snapshot.data[index]),
itemCount: snapshot.data.length,
);
}
},
);
Implementation of Database stuff
class DatabaseService {
// Collection reference
final CollectionReference storeCollection = FirebaseFirestore.instance.collection('stores');
// Make a store list from snapshot object
List<Store> storeListfromSnapshot(QuerySnapshot snapshot) {
return snapshot.docs.map((doc){
return Store(
name: doc.data()['name'] ?? '',
description: doc.data()['description'] ?? '',
image: doc.data()['image'] ?? ''
);
}).toList();
}
// Get instantaneous stores list
Future<List<Store>> get storeList async {
return storeListfromSnapshot(await storeCollection.get());
}
}
Every get() call will fetch the data from the server. So here since you are using get() on a collection and let's say you have 5 documents, then it will be 5 document read. If you enter the page again, then it will also count 5 document reads.
If you want to get data from the cache then use snapshots() which returns data from the cache and if there are any modification then it will return data from the server.
You can also add the parameter GetOptions to the get() method for example:
get(GetOptions(source : Source.cache))
This will always get the data from cache, ignoring the server completely.
You can also monitor document read here:
https://firebase.google.com/docs/firestore/monitor-usage
Also note if you keep the firebase console open then you will get unexpected reads.

How to get sub-collections of every collection in Firestore using Flutter

I'm building a task management app where every user has multiple projects (collections), inside every project there are tasks (collections), and inside every task there are sub-tasks.
I was able to retrieve all the projects using this code:
User user = Provider.of<User>(context);
CollectionReference projects = Firestore.instance.collection('users').document(user.uid).collection("projects");
But my question is how can I get all the tasks and sub-tasks (if they exist) from every project and list them in a StreamBuilder?
Here's my current code:
Flexible(
child: StreamBuilder<QuerySnapshot>(
stream: projects.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return new ListView(
children:
snapshot.data.documents.map((DocumentSnapshot document) {
return new ListTile(
title: new Text(document.data['project_name']),
subtitle: new Text(document.data['project_description']),
);
}).toList(),
);
},
),
),
If the project has tasks I want to display them below the project description, and if the task has sub-tasks I want to show them below its parent task.
It sounds like you're essentially asking how to list nested subcollections, which is not possible for web and mobile clients. See also:
How to list subcollections in a Cloud Firestore document
How to list subcollection on firestore?
Also, all Firestore queries are shallow, meaning they only consider documents in one collection at a time. There are no deep or recursive queries.
Consider instead maintaining the list of projects in a document that you can get() directly. You can use that list to then query each subcollection directly.
There are different kind of implementations for this scenario, basically subcollections are located under one single document, the query identifies a group of documents, when you know that the query only identifies one document, the Firestore client doesn't know that, and even if it did, it doesn,t know the full path for that document.
You need to execute a query for getting individual documents, and then you can get the subcollection of each one.
You can find a good example using flutter here.
I had a similar issue, and used bloc provider alongside streams,
I first created a model for Json parsing (In my case I had an app whereby users can create campaigns, these campaigns were saved as a sub-collection in the UserData collection, the document IDs were the userID from FirebaseAuth(now User in flutter migration). The objective was to loop through UserData collection for each user's subcollection (UserCampaigns) and retrieve all of them and display in the UI with bloc. userCampaignRepository:
List<UserCampaignModel> UserCampaignList = [];
CreateListofAllUserCampaigns(QuerySnapshot snapshot) async {
var docs = snapshot.docs;
for (var Doc in docs) {
UserCampaignList.add(
UserCampaignModel.fromEntity(UserCampaignEntity.fromSnapshot(Doc)));
}
return UserCampaignList;
}
Stream<List<UserCampaignModel>> getListUserCampaigns() async* {
final collection = FirebaseFirestore.instance.collection('UserData');
final List list_of_users = await collection.get().then((value) => value.docs);
for (int i = 0; i < list_of_users.length; i++) {
FirebaseFirestore.instance.collection("UserData")
.doc(list_of_users[i].id.toString())
.collection("UserCampaigns")
.snapshots()
.listen(CreateListofAllUserCampaigns);
}
yield UserCampaignList;
}
In the bloc: I have 2 events, one to initiate the Load of Events by listening to the stream and calling the loader event.
if (event is InitiateLoadOfAllUserCampaignsEvent) {
yield AllUserCampaignsState.initial();
try {
_userCampaignSubscription?.cancel();
_userCampaignSubscription = _userCampaignRepository
.getListUserCampaigns()
.listen((allCampaigns) {
print("Load event is called");
add(
LoadAllUserCampaignsEvent(allUserCampaigns: allCampaigns),
);
});
} catch (e) {
print("error is ${e}");
yield AllUserCampaignsState.allUserCampaignLoadFailure();
}
} else if (event is LoadAllUserCampaignsEvent) {
yield* _mapLoadAllUserCampaignsEventToState(event);
}}}

Flutter & Cloud-Firestore: how to read Firestore Documents linked to Flutter FutureBuilder

Scenario explained:
Using: import 'package:cloud_firestore/cloud_firestore.dart';
Cloud Firestore:
a Collection 'sessionsLog', containing documents with the following fields:
'sessionId' a String
'isClosed' a bool
'locationId' a String and some other fields ...
I want to retrieve all documents with a false bool for 'isClosed', only once, and then perform some actions. Therefore I chose to use a FutureBuilder (not a streambuilder), what I did:
FutureBuilder(
future: Firestore.instance.collection('sessionsLog')
.where('isClosed', isEqualTo: false).getDocuments();
builder: (ctx, sessionSnapshot) {
return RaisedButton(
child: Text('Search'),
onPressed: () async {
//some other code here, that needs async I added on top
if (sessionSnapshot.connectionState !=
ConnectionState.done) {
return CircularProgressIndicator();
}
if (!sessionSnapshot.hasData) {i should
print('nothing to show here');
}else {
//the below line of code is wrong, it could be print / or Text / or any other widget
//I've just added it for the sake of an example.
print(sessionSnapshot.data.documents[0]['parkingLocationName']);}});}),
The above code is not working, I've just created it for a simple code result, to just be able to print values I am retrieving, so please don't fix something in my code.
Also noting that, in my code logic, there will always be once single document with 'isClosed' = false. So, I should always receive one single document.
But If it differs, please give me 2 examples, one if retrieved data contains 1 single document and another if there could be several documents and therefore how would I iterate in the data.
I just want the correct way to be able to READ/access the data I am receiving, whether to print the fields info, or display them in a text. anything, but how would I do that, in the exact explained above scenarios.
Would really appreciate a simple sample example based on my code scenario.
You could do it in a future like this:
Future<void> performActionsOnClosed() async {
await FirebaseFirestore.instance
.collection('sessionsLog')
.where('isClosed', isEqualTo: false)
.get()
.then(
(snapshot) {
if (snapshot.docs.isNotEmpty) {
// Perform actions
} else {
print('No Documents Found');
}
},
);
}

Flutter Futurebuilder not returning anything

I have a Futurebuilder in flutter, but it never returns anything.
As you can see below, I have a Futurebuilder that calls a method called getGamesLost. If there is no data, it will show a loading indicator.
FirestoreUserProfile firestoreUserProfile = new FirestoreUserProfile();
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: firestoreUserProfile.getGamesLost(),
builder: (BuildContext context, AsyncSnapshot<int> userProfileData) {
if(userProfileData.hasData) {
print(userProfileData);
}
else {
return Styling.loadingIndicator;
}
}
);
My problem is that there is never data. Below is the getGamesLost method.
Future<int> getGamesLost() async {
return await firestoreCollectionReference
.document(FirebaseUserData.currentFirebaseUser.email)
.snapshots().forEach((userData) {
return userData.data[describeEnum(fieldNames.profile)][describeEnum(fieldNames.gamesLost)];
});
}
For some reason the getGamesLost that is called by the futurebuilder never finishes. I can print the value of userData just before the last return, which means there actually is data returning from firebase, but it is as if the method never actually returns and the futurebuilder just keeps waiting.
It's because you are using a stream returned by snapshots() to get data and iterating it. This stream stays alive and listens to any realtime data updates. The forEach function here would give you a Future, but it completes only when the Stream returned by snapshots() completes. Hence, the Future never completes.
If you just want the value form the document, just use it like.
// The `get` method instead of `snapshots` just fetches the doc at this instance and would not listen for updates
Future<int> getGamesLost() async {
return await firestoreCollectionReference
.document(FirebaseUserData.currentFirebaseUser.email)
.get()
.then((DocumentSnapshot userData) {
return userData.data[describeEnum(fieldNames.profile)]
[describeEnum(fieldNames.gamesLost)];
});
}
Hope that helps!

Resources