Async await flutter firestore - asynchronous

I would like to ask whats going on with my code.
Assuming the 'Counter' field is 179 in this instance, how do I make my outside myData update before printing?
class Test {
Firestore _firestore = Firestore.instance;
var myData;
void getData() async {
DocumentSnapshot snapshot =
await _firestore.collection('Counter').document('Counter').get();
myData = await snapshot.data['Counter'];
print('inside $myData');
}
void checkMyData() {
myData = 5;
getData();
print('outside $myData');
}
}
Console:
flutter: outside 5
flutter: inside 179

You have to make getData() return a Future like this:
Future getData() async {
So you can do this:
getData().then((value) {
print('value: $value');
}).catchError((error) {
print('error: $error');
});
But you probably want to use a FutureBuilder to show the information when arrives, like this:
FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('value: ${snapshot.data}');
} else if (snapshot.hasError){
return Text('error: ${snapshot.error}');
}
return Text('loading...');
},
)

Related

Convert method into FutureBuilder : Flutter

I'm new to flutter. This is the method that I used to retrieve the data from firebase and I'm able to get the exact answer in the console. My question is how I can convert this code into future builder so I am able to read the data in my application.
void getUser() async {
firestoreInstance.collection("User Data").get().then((querysnapshot) {
querysnapshot.docs.forEach((result) {
firestoreInstance
.collection("User Data")
.doc(result.id)
.collection("bank")
.where('account_username', isEqualTo: ownerData?.name2)
.get()
.then((query Snapshot) {
querysnapshot.docs.forEach((result) {
print (result["bank_name"]);
});
});
});
});
}
You should return the value from the query, not print it
Your function should look like this
Future<String> getUser() async {
firestoreInstance.collection("User Data").get().then((querysnapshot) {
querysnapshot.docs.forEach((result) {
firestoreInstance
.collection("User Data")
.doc(result.id)
.collection("bank")
.where('account_username', isEqualTo: ownerData?.name2)
.get()
.then((query Snapshot) {
querysnapshot.docs.forEach((result) {
return result["bank_name"];
});
});
});
});
}
The Futurebuilder should look like this
FutureBuilder<String>(
future: getUser(), // async work
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}

Flutter How to handle Stream and Future return error

Im trying to stream my firebase firestore fields. This is my code from my Database. It works in button and I can print what I want. But actually I want to show data with Widgets in my HomePage with StreamBuilder.
getYukListFromDB() async {
_firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) {
for (var docA in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + docA.id);
_firebaseFirestore
.collection('customer')
.doc(docA.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) {
for (var docB in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + docB.id);
_firebaseFirestore
.collection('customer')
.doc(docA.id)
.collection('myYuks')
.doc(docB.id)
.get()
.then((DocumentSnapshot documentSnapshot) {
Map<String, dynamic> mapData =
documentSnapshot.data()! as Map<String, dynamic>;
if (documentSnapshot.exists) {
debugPrint("icerik = ${mapData['icerik']}");
debugPrint('doc var');
} else {
debugPrint('doc yok');
}
});
}
});
}
});
}
When I try this in stream it gives me an error.
Stream<List<YukModel>> yeniYukStream(String uid) { // ***error here***
_firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) {
for (var doc1 in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + doc1.id);
return _firebaseFirestore
.collection("customer")
.doc(doc1.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) {
for (var doc2 in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + doc2.id);
_firebaseFirestore
.collection('customer')
.doc(doc2.id)
.collection('myYuks')
.orderBy('createTime', descending: true)
.snapshots()
.map((QuerySnapshot querySnapshot) {
List<YukModel> retVal = <YukModel>[];
for (var lastData in querySnapshot.docs) {
retVal.add(YukModel.fromDocumentSnapshot(lastData));
}
return retVal;
});
}
});
}
});
}
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
And I know it should be Future. Let's try it.
Future<Stream<List<YukModel>>> yeniYukStream(String uid) async{
return _firebaseFirestore
.collection('customer')
.get()
.then((QuerySnapshot querySnapshot) { // ****error here****
for (var doc1 in querySnapshot.docs) {
debugPrint("shipment altındaki docs idsi = " + doc1.id);
return _firebaseFirestore
.collection("customer")
.doc(doc1.id)
.collection('myYuks')
.get()
.then((QuerySnapshot querySnapshot) { // ****error here****
for (var doc2 in querySnapshot.docs) {
debugPrint("myYuk altındaki docs idsi = " + doc2.id);
_firebaseFirestore
.collection('customer')
.doc(doc2.id)
.collection('myYuks')
.orderBy('createTime', descending: true)
.snapshots()
.map((QuerySnapshot querySnapshot) {
List<YukModel> retVal = <YukModel>[];
for (var lastData in querySnapshot.docs) {
retVal.add(YukModel.fromDocumentSnapshot(lastData));
}
return retVal;
});
}
});
}
});
}
**** error **** lines is this:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type.
Try adding either a return or a throw statement at the end.
And this is my YukModel.dart file;
class YukModel {
String? yukID;
String? yukBaslik;
String? icerik;
int? agirlik;
Timestamp? createTime;
String? aracTipi;
bool? onayDurumu;
YukModel(
{this.yukID,
this.yukBaslik,
this.icerik,
this.agirlik,
this.createTime,
this.aracTipi,
this.onayDurumu});
YukModel.fromDocumentSnapshot(DocumentSnapshot documentSnapshot) {
yukID = documentSnapshot.id;
yukBaslik = documentSnapshot.get('yukBaslik');
icerik = documentSnapshot.get('icerik');
agirlik = documentSnapshot.get('agirlik');
createTime = documentSnapshot.get('createTime');
aracTipi = documentSnapshot.get('aracTipi');
onayDurumu = documentSnapshot.get('onayDurumu');
}
}
What should I do? Also, I am using GetX package for state management.
MY SOLUTION FOR NOW
Here is my solution. It worked for me. Also, you can access your subcollections with this code.
In HomePage, I added a StreamBuilder:
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("customer")
.doc(authController.doneUser!.uid) // you can use your uid
.collection("myYuks")
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>> snapshot) {
if (snapshot.hasData && snapshot.data != null) {
if (snapshot.data!.docs.isNotEmpty) {
return ListView.builder(
itemBuilder: (context, int index) {
Map<String, dynamic> docData =
snapshot.data!.docs[index].data();
if (docData.isEmpty) {
return const Center(child: Text("Data empty"));
}
//these are my fields in subcollections.
// you can use like docData["yourfieldnameinsubcollection"];
String yukID = docData[FirestoreFields.yukID];
String yukBaslik = docData[FirestoreFields.yukBaslik];
String icerik = docData[FirestoreFields.icerik];
String agirlik = docData[FirestoreFields.agirlik];
return Card(
child: Container(
color: ColorConstants.birincilRenk,
height: 210,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
children: [
Text(yukID, style: const TextStyle(fontSize: 16)),
const Divider(thickness: 1),
Text(yukBaslik,
style: const TextStyle(fontSize: 20)),
const Divider(thickness: 1),
Text(icerik, style: const TextStyle(fontSize: 16)),
const Divider(thickness: 1),
Text(agirlik, style: const TextStyle(fontSize: 16)),
],
),
),
),
);
},
itemCount: snapshot.data!.docs.length,
);
} else {
return const Text("no data ");
}
} else {
return const Text("loading");
}
},
),
I didn't go through your entire code, because it was too much, but from the error message I can help you understand the issue.
Hers's a simplied version of what you might be trying to achive:
// This method returns the data from Firestore collection
Stream<T> getData(){
return collection.snapshots(); // This returns a Stream
// get() returns a Future
// snapshots() returns a Stream
}
// As the name says, it build from a STREAM
StreamBuilder(
future: getData(), // the source of stream
builder: (context,snapshot){
if(snapshot.hasData){ // checking if the stream's snapshot has data
return Text(snapshot.data!); // If there is data, we display widgets accordingly
}else{
return const CircularProgressIndicator(); // If there isn't data yet, we display a CircularProgressIndicator
}
}
)
Now for your issue, as the error message says:
The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. Try adding either a return or a throw statement at the end.
Here's the example to explain that:
StreamBuilder(
future: getData(), // the source of stream
builder: (context,snapshot){
if(snapshot.hasData){ // checking if the stream's snapshot has data
return Text(snapshot.data!); // If there is data, we display widgets accordingly
}
}
)
If you noticed, I don't use the else statement here, so what that means is that:
If snapshot has data then display a widget, but what if the snapshot doesn't hava data yet, then what will be displayed. I will receive the same error as you. Soo to solve that, I use an else statement. This way my
my body DOESN'T return null
The Streambuilder can be listened to by subscribers. The MyClassBloc contains a get method called MyClassListStream. The MyClassBloc contains a datastream of list type. MyClassBlock can be assigned data from an web api provider to the listMyClass variable. in the _loadMyClassItems method a call to initializeStream is called notifying all subscribers there is data on the stream. The _buildList method has a streambuilder which stream points to the MyClassBloc stream controller get MyClassListStream assessing _MyClassListSubject.stream and the initialData references widget.blocMyClass.listMyClass. When data arrives on the stream, the Streambuilder maps data from the stream to create a list of CardWidgets. The snapshot.data is of MyClass Type. Therefore _buildList returns a list of cardwidgets created from the stream. Everything starts when initState() calls the _loadMyClassItems and the web api returns a list of MyClass objects after the promise is completed and assigns the data to the MyClassBloc variable and assigns data and invokes the initializeTheStream. The Streambuilder is notified that data is on the stream from the MyClassBloc _MyClassListSubject.add(listMyClass) event. The Streambuilder then returns a list of CardWidgets.
class MyClassBloc {
List<MyClass> listMyClass;
Stream<List<MyClass>> get MyClassListStream =>
_MyClassListSubject.stream;
final _MyClassListSubject =
BehaviorSubject<List<MyClass>>();
initializeTheStream() {
if (listMyClass != null) {
_MyClassListSubject.add(listMyClass);
}
}
dispose() {
_MyClassListSubject.close();
}
}
class MyClass
{
int field1;
String field2;
MyClass(
this.field1,
this.field2,
);
Map<String,dynamic> toJson(){
var map={
'field1': field1,
'field2': field2,
};
return map;
}
factory MyClass.fromJson(Map<String,dynamic> json){
if(json==null){throw FormatException("null json");}
return MyClass(
json['field1'] as int,
json['field2'] as String,
);
}
}
_loadMyClassItems(BuildContext context) {
try {
Provider.of<ApiWidget>(context, listen: false)
.getMyClass()
.then((value) {
setState(() {
loadSummary = true;
if (value != null) {
widget.blocSummary.listMyClass = value;
widget.blocSummary.initializeTheStream();
} else {
widget.blocSummary.listMyClass =
[];
widget.blocSummary.initializeTheStream();
}
});
}).catchError((error) {
DialogCaller.showErrorDialog(context, error);
});
} catch (e) {
DialogCaller.showErrorDialog(context, e.toString());
}
}
Widget _buildList(BuildContext context) {
List<Widget> cardWidgets;
return StreamBuilder<List<MyClass>>(
stream: widget.blocMyClass.MyClassListStream,
initialData: widget.blocMyClass.listMyClass,
builder: (context, snapshot) {
//debugPrint(snapshot.connectionState.toString());
if (!snapshot.hasData) {
return PleaseWaitWidget();
} else if (snapshot.hasError) {
DialogCaller.showErrorDialog(
context, "future builder in main has an error")
.then((value) {});
} else if (snapshot.hasData) {
//debugPrint("reached processing");
cardWidgets =
snapshot.data.map((MyClass MyClass) {
return CardWidget(MyClass);
}).toList();
return CardWidgets.length == 0
? Container(
padding: EdgeInsets.all(20),
child: Text("No Records found", style: NoRecordFoundStyle))
: ListView(children: CardWidgets);
}
return (Container(child: Text("Failed to build list")));
});
}
myWidget.dart
void initState(){
super.initState();
_loadMyClassItems();
}
Widget build(BuildContext context) {
if (loading == false) {
return PleaseWaitWidget();
} else {
return new Scaffold(
key: _scaffoldKey,
....
body: Column(children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Expanded(child: _buildList(context)),
]

Wait for a future function to complete its execution

I am new to flutter and in my below code there is read data function is called at line 18(it performs Firebase Database operations) I want that the readData() function must complete its execution before going to the print statement(line 19) and further execution.
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:udharibook/Screens/SignInPage.dart';
import 'package:udharibook/Screens/UserProfile.dart';
import 'package:udharibook/Screens/dashboard.dart';
class AuthService {
bool flag = false;
final FirebaseAuth _auth = FirebaseAuth.instance;
final DBRef = FirebaseDatabase.instance.reference().child('Users');
handleAuth(){
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot) {
if(snapshot.hasData) {
readData();
print(flag);
if(flag ==true)
return DashboardPage();
else
return UserProfile();
}
else {
return SignIn();
}
},
);
}
Future<void> readData() async {
final FirebaseUser user = await _auth.currentUser();
final userid = user.uid;
DBRef.child(userid).once().then((DataSnapshot data){
print(userid);
if(data.value!=null)
{
flag = true;
print(data.key);
print(data.value);
}
else{
print('User not found');
flag = false;
}
});
}
signOut(){
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
signInWithOTP(smsCode,verId){
AuthCredential authCreds = PhoneAuthProvider.getCredential(
verificationId: verId,
smsCode: smsCode
);
signIn(authCreds);
}
}
If you want a future to complete inside a build/builder method then using FutureBuilder is a good way to go. This way you can set up a placeholder widget whilst the future is completing, and then once its done output the desired results. In the example below I've used a circular progress indicator as the placeholder but you could use any widget:
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot) {
if(snapshot.hasData) {
return FutureBuilder(
future: readData(),
builder: (context, futureSnapshot) {
if (!futureSnapshot.hasData) return CircularProgressIndicator();
print(flag);
if(flag ==true)
return DashboardPage();
else
return UserProfile();
}
);
}
else {
return SignIn();
}
},
);
Since readData() is An AsyncFunction ,
Adding the keyword await before readData() , will force readData() to finish,and after that it will print , However you can't do it since StreamBuilder is waiting for Widget return type.
So what you can do is .
maybe modify your readData to return a Future boolean readData() ;
Future<bool> readData() async {
final FirebaseUser user = await _auth.currentUser();
final userid = user.uid;
DBRef.child(userid).once().then((DataSnapshot data){
print(userid);
if(data.value!=null)
{
print(data.key);
print(data.value);
return true;
}
else{
print('User not found');
return false;
}
});
}
and you call it :
readData().then((value)
{
if (value ==true)
return Widget
else
return Widget;
}
);
Hope that helps.

flutter: check if the document exist

I want to check whether the document exist or not without creating the document if it does not exits
Checked() {
Future<DocumentSnapshot> check = linkref.
document(user.UID).
collection("Requests").
document(uuid).get();
return FutureBuilder(
future: check,
builder: (context, ccheck) {
if (check != null ) {
return Text("Available");
}
return Text("not available);
});
}
i tried this code but even if the document does not exists it says that it exists
You should use; if (ccheck.data.exists) instead of if (check != null ). Here is the code;
Checked() {
Future<DocumentSnapshot> check =
linkref.document(user.UID).collection("Requests").document(uuid).get();
return FutureBuilder<DocumentSnapshot>(
future: check,
builder: (context, ccheck) {
if (ccheck.data.exists) {
return Text("Available");
}
return Text("not available");
});
}
You can use the .where( field, isEqualTo: query). This might be useful to you.
final userRef = FirebaseFirestore.instance.collection('users');
checkExists(String query) async {
QuerySnapshot checker = await userRef
.where('uid', isEqualTo: query)
.get();
chkr.docs.forEach((doc) {
if (doc.exists) {
print('Exists');
print(doc.get('uid'));
}else {
print('false');
}
});
}
Then, if you are using a button, you can use onPressed: () => check(yourQuery).

Flutter StreamBuilder returns null from Firestore

The idea is to display a string from a random document within a collection in Firebase. A simple function getRandom() retrieves the total number of documents and generates a random integer r that is fed into the Firebase instance.
The output in the app is always null.
StreamBuilder(
initialData: Words(),
stream: getWords(),
builder: (context, snapshot){
if(!snapshot.hasData){
return Center(child: Text("NO DATA"));
}else {
var r = snapshot.data;
return Center(child: Text("${r.english}"));
}
})
Stream<Words> getWords() async* {
int r = await getRandom();
print("RANDOM NO: " + "$r");
Firestore.instance.document("vocabs/foods/words/$r")
.get()
.then((snapshot){
try {
return Words().english;
} catch(e){
print("ERROR");
return null;
}
});
}
class Words{
Words(): super();
String english;
Words.fromSnapshot(DocumentSnapshot snapshot)
: english = snapshot.data["english"];
}
I've constructed a this piece of sample code for you to give you some options to achieve what you'd like to do:
import 'dart:async';
class Word {
final String english;
const Word(this.english);
}
Future<Iterable<Word>> get firebaseSnapshot async => [ Word('aWord'), Word('bWord'), Word('cWord') ];
Stream<String> getEnglishWords() async* {
yield* await firebaseSnapshot.then((words) => Stream.fromIterable(words.map((w) => w.english)));
}
Stream<String> getEnglishWords2() async* {
final words = await firebaseSnapshot.then((words) => words.map((w) => w.english));
yield* Stream.fromIterable(words);
}
Stream<String> getEnglishWords3() async* {
final snapshot = await firebaseSnapshot;
for(final word in snapshot) {
yield word.english;
}
}
main() async {
await for(final englishWord in getEnglishWords()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords2()) {
print(englishWord);
}
await for(final englishWord in getEnglishWords3()) {
print(englishWord);
}
}
Option No. 2 is the one I'd use. There is some significant performance consideration around it. I am scraping the back of my mind for the lecture around it... Nope, can't recall... If I find it, I'll update ya.

Resources