Download data before build - method - firebase

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

Related

Firebase Flutter Does Not Recognize My String as Parameter

I am trying to get user email,save to shared preferences and use as collection name in another file.
my code to save
Future<void> saveEmail() async {
var sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("email", _emailKontroller.text);}
no problem here, I can save data to sharedPreferences and read data from another file.
my code to read
#override
void initState() {
// TODO: implement initState
void initilaizeEmail() async {
var sharedPreferences = await SharedPreferences.getInstance();
_email = sharedPreferences.getString("email");
print(_email);
}
initilaizeEmail();
setState(() {});
}
output
I/flutter ( 3274): a#a.com
where I use as parameter my sharedPreferences Data:
query: FirebaseFirestore.instance
.collection("test")
.doc("$_email")
.collection("class 0"),
// to fetch real-time data
isLive: false,
I can not see anything on screen but, if I delete
_email
and type "a#a.com" manually everything works.What is the problem?
The problem is that initilaizeEmail is an async method, and you're not waiting for its result. To fix this:
await initilaizeEmail();
I also recommend fixing the name of the method to be initializeEmail. While it won't change the behavior, spelling mistakes tend distract from other problems.
I solved my problem with using
Future Builder

Run Firebase Cloud Function before page load Flutter

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();
}

Not Loading the value in Real Time

I am using below code to get the total number of documents from the firestore collection and then store it to int countDocument, it does get the value accurately, but when I used to display the same value in the Flutter widget Text Widget which is nested in Scaffold, upon loading the screen it does not show the value, showing null only, only upon hot reload it shows the value on the screen.
To represent the value of countDocument in Text Widget, I did countDocument.toString()' but still it does not show the value upon initial loading of the screen
How should I resolve it?
void countDocuments() async {
StreamSubscription<QuerySnapshot> _myDoc = await Firestore.instance.collection('users').snapshots().listen((result) {
countDocument = result.documents.length;
print(countDocument);
});
You need to use a StatefulWidget, then using setState, you can change the data of the Text:
void countDocuments() async {
StreamSubscription<QuerySnapshot> _myDoc = await Firestore.instance
.collection('users')
.snapshots()
.listen((result) {
setState(() {
countDocument = result.documents.length;
});
print(countDocument);
});
}
setState will call the build method again with the new data. Inside the build method you can do the following:
Text(countDocument ?? "loading")

Values retrieved from firestore only after hot reload in flutter

I am new to flutter. This is what I have done so far(This is not the whole code).As you can see I am trying get some data from firestore in flutter. The problem is that the number value is always zero when I entering to the page. But if I hot reload the page it gives me the correct value. I Why is that? I think it has something to do with future and async. But I don't understand them yet properly.
Widget build(BuildContext context) {
someMethod() {
return Firestore.instance
.collection('comments')
.where("Post", isEqualTo: widget.snapshot.documentID)
.getDocuments();
}
someMethod().then((QuerySnapshot snapshot) {
if (snapshot.documents.isNotEmpty) {
db = snapshot.documents;
}
});
int number = db?.length ?? 0;
print(number);
}
You have to use setState() to Notify the framework that the internal state of this object has changed.
someMethod().then((QuerySnapshot snapshot) {
if (snapshot.documents.isNotEmpty) {
setState(() {
db = snapshot.documents;
});
}
});

How should I handle asynchronous calls when initializing my BLoC?

I'm using the Provider package to provide a BLoC object (hand-written, not using bloc or flutter_bloc packages) to my Flutter app. I have a few asynchronous calls that need to be made to initialize the BLoC properly (for example, settings and other saved info from SharedPreferences). So far, I've written those async calls into a few separate functions that are called inside my BLoC's constructor:
class MyBloc {
MySettings _settings;
List<MyOtherStuff> _otherStuff;
MyBloc() {
_loadSettings();
_loadOtherStuff();
}
Future<void> _loadSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// loads settings into _settings...
}
Future<void> _loadOtherStuff() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// loads other stuff into _otherStuff...
}
}
I want to guarantee that _loadSettings() and _loadOtherStuff() complete before we get too far into the app so that the code that depends on settings/other stuff has the right info loaded (for example, I want settings to be loaded before I go out to make some network calls, initialize notifications, etc.).
As far as I understand, constructors can't be asynchronous, so I can't await on the constructor. I've tried giving my BLoC an init() function (or something similar) that calls _loadSettings() and/or _loadOtherStuff(), but I'm having a hard time finding a good place to put it.
Where should I be putting these calls? Or am I just misunderstanding async/await?
You can use a stream to listen for completion.
class MyBloc {
MySettings _settings;
List<MyOtherStuff> _otherStuff;
final _completer = StreamController<Void>.broadcast();
Stream<void> get completer => _completer.stream;
MyBloc() {
allInit();
}
allInit()async{
await _loadSettings();
await _loadOtherStuff();
_completer.sink.add(null);
}
Future<void> _loadSettings() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// loads settings into _settings...
return;
}
Future<void> _loadOtherStuff() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
// loads other stuff into _otherStuff...
return;
}
}
And then you use a streamBuilder
return StreamBuilder(
stream: myBloc.completer,
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) return Text('loading...');
//stuff to run after init
},
I also ended up using some help from Future.wait():
Future.wait([_loadSettings(), _loadOtherStuff()]).then((_) => _doMoreStuff());
Which makes sure that the first two are done before we move on.

Resources