Flutter, How handle state when awaiting for firebase data - flutter

checkBorrowerAlreadyExists() Function checks if a user input name already exists in firebase data and returns a bool. I am using this function in an if condition after onPressed of a button, I am providing user input as an argument to the function in if condition and display the snackbar based on the bool value. This works perfectly fine when firebase already has data, But initially when the user register and opens the app, firebase won't have any data to check if name already exists and nothing happens on the screen.
I used else condition to return false if no data, move on and add input in firebase, but didn't work. I also tried using the bool in different way but didn't work. How do I solve this.
checkBorrowerAlreadyExists(String textfieldname) async {
await for (var snapshots in _firestorem
.collection('lender')
.doc(auth.currentUser?.uid)
.collection('borrowers')
.snapshots()) {
for (var message in snapshots.docs) {
bool isThere;
if (message.data().containsValue(textfieldname)) {
return isThere = true;
} else {
return isThere = false;
}
}
}
}
onPressed: () async {
if (borrowerNameController.text.isEmpty) {
Get.snackbar(
'Error:', "Borrower Name can't be empty",
backgroundColor: Colors.red);
} else if (await checkBorrowerAlreadyExists(
borrowerNameController.text)) {
Get.snackbar('Error',
'Borrower Name alread exists, Enter new name',
backgroundColor: Colors.red);
} else{ upload data in firebase}

You can't use await in the build method, as there's no way to hold up rendering.
The solution is actually in the return type of your function, which you left implicit not. If you make the return type explicit, it is:
Future<bool> checkBorrowerAlreadyExists(String textfieldname) async {
...
Since this returns a Future, you need to use a FutureBuilder in your build method to render the boolean value (and to determine what to render until that value is available).
So something like:
onPressed: () async {
if (borrowerNameController.text.isEmpty) {
Get.snackbar('Error:', "Borrower Name can't be empty", backgroundColor: Colors.red);
}
else {
return FutureBuilder<String>(
future: checkBorrowerAlreadyExists(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
return Text(borrowerNameController.text)
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}'),
} else {
return Text('Awaiting result...'),
}
},
),
I recommend checking out the documentation on FutureBuilder and Flutter's codelab on asynchronous programming.

Related

Flutter null check operator used on a null value flutter

I am trying to check if user make a payment or not.If payment is done, user will see homepage.If payment is not done User will see the payment page. The problem is that I am getting null check operator used on a null value. What I am doing wrong?
class TwoPage extends StatelessWidget {
Package? offer;
PurchaserInfo? _purchaserInfo;
bool? payment;
Future<bool> ispaymentdone() async {
await Purchases.setDebugLogsEnabled(true);
await Purchases.setup("public_key");
PurchaserInfo purchaserInfo = await Purchases.getPurchaserInfo();
print(purchaserInfo);
print("buraya kadar iyi");
Offerings offerings = await Purchases.getOfferings();
print(offerings);
// optional error handling
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
//if (!mounted) return;
_purchaserInfo = purchaserInfo;
if(purchaserInfo.entitlements.all["content-usage"]!=null){
if ( purchaserInfo.entitlements.all["content-usage"]!.isActive) {
print("trueee");
return true;
}
}
return false;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: ispaymentdone(),
builder: (context, snapshot) {
if (snapshot.data == null)
return SizedBox(
child: Center(child: CircularProgressIndicator(color:Colors.purple)),
height: 10.0,
width: 10.0,
);
else if (snapshot.data == true)
return NewHomeScreen();
else
return MainPayment(purchaserInfo: _purchaserInfo, offer: offer);
},
);
}
}
This is because u r using null check operator on your variables but the value of that particular variable is NULL at a point when u want to use it ,either u have to give some initial value to that variable or make sure value is not null.
It is my mistake:
return MainPayment(purchaserInfo: _purchaserInfo, offer: offer);
I didn't assign anything to offer variable.

Future Buider does not get the data until after multiple iterations. In Flutter

I have an asynchronous function that obtains information from my bd in firebase, when debugging this code fragment I can see that the data is obtained without any problem, this data will be used to display in widgets and I pass it through a future builder, the problem is that although when debugging I realize that the data are there, Future builder does not detect them and snapshot has null value, it is until after several iterations when snapshot finally has data and allows me to use them, I do not understand what is wrong in the construction of my Future Builder.
Here is the code of my function where I get the data and the construction of the Future Buider.
Function where data are obtained.
Future<List<Guide>> getGuidesList() async {
var guidesProvider = Provider.of<GuidesProvider>(context, listen: false);
Checkuser data = await ManagerDB().checkuser(auth.email);
List<Guide> aux = new List();
Guide guide;
List guides = await guidesProvider.setGuidesFromUser(data);
if (guides != null) {
for (var i = 0; i < guides.length; i++) {
await guides[i].get().then((DocumentSnapshot guides) {
guide = Guide.fromMap(guides.data(), guides.reference.path);
aux.add(guide);
});
}
if (this.mounted) {
setState(() {});
}
print('Guias cargadas correctamente');
return aux;
} else {
print('Lista vacia');
return aux;
}
}
Fragmento de Funcion donde creo mi FutureBuider.
return Scaffold(
resizeToAvoidBottomInset: false,
key: _scaffoldKey,
appBar: appBar,
drawer: DrawerNavigationMenu(
getContext: widget.getcontext,
),
body: FutureBuilder<List<Guide>>(
future: getGuidesList(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return ListCourses(
getContext: widget.getcontext,
items: snapshot.data,
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
);
if (this.mounted) {
setState(() {});
}
Delete this part. You are unnecessarily rebuilding your scaffold and re-calling FutureBuilder. Let FutureBuilder take care of processing the future and rebuilding the scaffold for you.

Displaying multiple screens based on ternary condition in dart

I have a scenario where I am showing a spinner when a page loads and while it's loading, it fetches some data in DB and sets a bool value to either true or false based on data availability.
I then want to share either screen A or B based on the boolean result.
I have done the following in my code but the app keeps showing the spinner. Any ideas what I might be doing incorrectly?
return _isLoading?
Center(child:Loading(),):
_isPersonalInfoSubmitted?ScreenA():ScreenB();
Second Attempt (Using Future Builder)
I want to show categories if the loggedin user is an admin. Else, for the rest of the users, I want to fetch address of the user from the DB. If the address is null, show Personal Details Screen else show Categories.
return FutureBuilder (
future: userId=='ADMIN_ID'?
Provider.of<Categories>(context,listen:false).fetchAndReturnCategories():
Provider.of<Addresses>(context,listen: false).fetchAndReturnAddress(userId)!=null?
Provider.of<Categories>(context,listen:false).fetchAndReturnCategories():null,
builder: (context, snap) {
inspect(snap);
if (snap.hasData) {
var categoriesData = Provider.of<Categories>(context);
return snap.hasData?
Scaffold(...) : PersonalDetails();
What happens here is that the method fetchAndReturnCategories gets executed even if the userID is not admin id. Do I have the correct setup?
prefer to use FutureBuilder
FutureBuilder<SomeClass>(
future: fetchdatFuture,
builder: (ctx, snap) {
if (snap.hasData) {
return snap.data?ScreenA():ScreenB();
} else if (snap.connectionState == ConnectionState.waiting)
{
return Center(child:Loading());
}
return Text("Error");
},
)
I think you have missed the setState to rebuild your widget
var bool _isLoading = true;
return _isLoading?
Center(child:Loading(),):
_isPersonalInfoSubmitted?ScreenA():ScreenB();
void _apiCall() {
// After Success of API Call
setState((){
_isLoading = false;
})
}

How to wait for a regular (non async) code (function) in flutter?

I want to search for a word in a big text file that is local (not calling any HTTP or API).
I'm using it with FutureBuilder because only the opening of the text file is async (the rest isn't)
but the opening itself is very fast.
I want to render CircularProgressIndicator, while searching, but it seems that the moment it finishes opening the file, the CircularProgressIndicator stops, and I have a blank screen for the long searching time.
What can I do to present a loading screen also while doing the regular for loop?
What I have is something like this pseudocode:
Future<Array> searchData() async{
results = [];
someBigTextFile= await getTextFile();
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: searchData(),
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
The Future call should not be in your build function. Make the call to your future and use the result in your build function.
Future<Array> result = searchData();
Widget buildResults(BuildContext context) {
return FutureBuilder(
future: result,
builder: (BuildContext context, AsyncSnapshot<Array> snapshot){
if (snapshot.connectionState != ConnectionState.done) {
print("not done yet");
return CircularProgressIndicator();
} else {
return snapshot.data;
}
}
}
Where you make the call for result will depend on the structure of the rest of your code.
Give this a try. With the .then() syntax you can specifically stop sync code from running before a future returns.
Future<Array> searchData() async{
results = [];
await getTextFile().then((someBigTextFile){
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
});
}
You should use isolates to spawn totally new working thread. Something like:
Array searchData( String someBigTextFileContents) {
results = [];
for(row in someBigTextFile){ // this loop takes a lot of time
if(row contains this.query) results.add(row);
}
return results;
}
Future<Array> searchData() async {
someBigTextFileContents= await getTextFile();
return compute( searchData, someBigTextFileContents )
}
Better example on here: https://flutter.dev/docs/cookbook/networking/background-parsing

How to inform FutureBuilder that database was updated?

I have a group profile page, where a user can change the description of a group. He clicks on the description, gets on a new screen and saves it to Firestore. He then get's back via Navigator.pop(context) to the group profile page which lists all elements via FutureBuilder.
First, I had the database request for my FutureBuilder inside the main build method (directly inside future builder 'future: request') which was working but I learnt it is wrong. But now I have to wait for a rebuild to see changes. How do I tell FutureBuilder that there is a data update?
I am loading Firestore data as follows within the group profile page:
Future<DocumentSnapshot> _future;
#override
void initState() {
super.initState();
_getFiretoreData();
}
Future<void> _getFiretoreData() async{
setState(() {
this._future = Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();});
}
The FutureBuilder is inside the main build method and gets the 'already loaded' future like this:
FutureBuilder(future: _future, ...)
Now I would like to tell him: a change happened to _future, please rebuild ;-).
Ok, I managed it like this (which took me only a few lines of code). Leave the code as it is and get a true callback from the navigator to know that there was a change on the second page:
// check if second page callback is true
bool _changed = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ProfileUpdate(userId: globals.userId.toString())),
);
// if it's true, reload future data
_changed ? _getFiretoreData() : Container();
On the second page give the save button a Navigator.pop(context, true).
i would advice you not to use future builder in this situation and use future.then() in an async function and after you get your data update the build without using future builder..!
Future getData() async {
//here you can call the function and handle the output(return value) as result
getFiretoreData().then((result) {
// print(result);
setState(() {
//handle your result here.
//update build here.
});
});
}
How about this?
#override
Widget build(BuildContext context) {
if (_future == null) {
// show loading indicator while waiting for data
return Center(child: CircularProgressIndicator());
} else {
return YourWidget();
}
}
You do not need to set any state. You just need to return your collection of users in your GetFirestoreData method.
Future<TypeYouReturning> _getFirestoreData() async{
return Firestore.instance
.collection('users')
.document(globals.userId.toString())
.get();
}
Inside your FutureBuilder widget you can set it up something like Theo recommended, I would do something like this
return FutureBuilder(
future: _getFirestoreData(),
builder: (context, AsyncSnapshot<TypeYouReturning> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.data.length == 0)
return Text("No available data just yet");
return Container();//This should be the desire widget you want the user to see
}
},
);
Why don't you use Stream builder instead of Future builder?
StreamBuilder(stream: _future, ...)
You can change the variable name to _stream for clarity.