How to wait for value before calling method Flutter/Dart - flutter

I am having a pretty hard time with certain actions in flutter. I currently have a method in an outside class that updates a db that my widget relies on for displaying info. I am correctly updating the values in the db and updating the UI correctly. BUT I am having a hard time getting an input first, THEN having that method function. I have tried having it all in the same body and no dice, I have tried to have the addStock method show the input and does not work. The only thing that has been a ban-aid has been to use Navigator.push to the screen again or using a time delayed. Both have produced undesired consequences. I have also tried having the addStock method inside the displayAmountToADD on pressing okay and does not update UI.
//a button inside the UI
onPressed: () async {
displayAmountToAdd(context, index);
setState(() {});
},
....
Future<void> displayAmountToAdd(
BuildContext context,
int index,
) async {
final _textFieldController = TextEditingController();
double materialKG = 0;
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Enter amount to add'),
content: Row(
children: <Widget>[
Expanded(
child: TextField(
onChanged: (materialQuanity) {
materialKG = double.parse(materialQuanity);
},
controller: _textFieldController,
decoration: InputDecoration(hintText: "KG"),
),
),
],
),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
materialKG = double.parse(_textFieldController.text);
addStock(context, mapM[index]['quanity'], mapM[index]['name'],
materialKG);
Navigator.pop(context);
},
),
TextButton(
child: Text("Cancel"),
onPressed: () {
Navigator.pop(context);
})
],
);
},
);
//return Future.delayed(Duration(seconds: 4),()=>materialKG); //TRYING TO AVOID THIS
}
//outside the ui file
addStock(
BuildContext context,
double currentQuanity,
String name,
double amountToAdd
) async {
//final db = await database;
double newStock;
late double materialKG;
newStock=currentQuanity+amountToAdd;
await db.rawUpdate(
'UPDATE materials SET quanity = $newStock WHERE name = "$name" ');
mapM = await db.query('materials'); //update values
//the following is only because setState is not working properly on other screen
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text("Added $amountToAdd KG to $name"),
));
}

displayAmountToAdd and showDialog method are future. Use await before theses to hold method to finish.
A sample example:
const oneSecond = Duration(seconds: 1);
// ยทยทยท
Future<void> printWithDelay(String message) async {
await Future.delayed(oneSecond);
print(message);
}
Learn more about async-await.

Related

Returning data from .pop() to use it anywhere

I have written a code that return bool type variables. Whether you like the movie. If you like the movie then it returns true, if you don't like the movie then it returns false. But since the .pop() method works in ElevatedButton, I cannot reach it from another class. How can I reach the value?
ElevatedButton(
child: Text(
"Go to new page"
),
onPressed: () async {
final answer = await Navigator.of(context).push<bool>(
MaterialPageRoute(
builder: (context) {
return VideoScreen("Did you like the video?");
},
)
);
},
),
However, I cannot say like:
ElevatedButton(
child: Text(
"Go to new page"
),
onPressed: () async {
final answer = await Navigator.of(context).push<bool>(
MaterialPageRoute(
builder: (context) {
return VideoScreen("Did you like the video?");
},
)
);
},
),
Text(answer)
],
);
So how can I reach that value? Callback or something? Thanks in advance
Use can pass value to parent route while pop like
Navigator.of(context).pop(YourValue);
While you push make sure to await.
final result = await Navigator.of(context)....;
print(result);

passing a function as a parameter to a regular class in flutter

I am trying to pass a function to a regular class (not a widget class) in flutter and inside that class, I have a dialog box. I want to call the dialog box and when the user presses a button the function is passed as a parameter that should trigger.
This is my regular class code
import 'package:finsec/core/res/strings.dart';
import 'package:flutter/material.dart';
import '../../../../core/res/text_sizes.dart';
import '../../data/repositories/app_database.dart';
class ShowDialog {
final void Function() onPressCallback;
BuildContext context;
ShowDialog (this.onPressCallback, this.context) ;
Future<String> showMyDialog() async {
return showDialog<String>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Income Data'),
content: SingleChildScrollView(
child: ListBody(
children: const <Widget>[
Text(
'Do you want to apply these changes for future income transactions?',
style: TextStyle(
fontSize: text_size_18,
),
),
Text(
'\nPress NO if changes are only for this week income. Press Yes to apply changes to future weeks.',
style: TextStyle(
fontSize: text_size_18,
),
),
],
),
),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.pop(context, successful);
},
child: const Text(cancelButton),
),
TextButton(
onPressed: () {
onPressCallback();
Navigator.pop(context, successful);
},
child: const Text(noButton),
),
TextButton(
onPressed: () {
onPressCallback();
Navigator.pop(context, successful);
},
child: const Text(yesButton),
),
],
);
},
);
}
}
I am calling ShowDialog class like this in my widget class. Below is the function call
CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () async {
await ShowDialog(
await database.deleteIncomeData(transaction),
context,
);
},
),
My code is working but not as expected. When I pass my function as a parameter, the database.deleteIncomeData(transaction) executes immediately and my showMyDialog() function in ShowDialog class doesn't get called. When I pass a function as a parameter to ShowDialog class, I don't want the function to execute immediately. I want my dialog box function to be called and show a dialog box. When the user presses a button on the dialog box, then the function parameter should execute.
Can someone help me how to modify my code to accomplish what I described above? Thanks in advance!
I recommend learning more about Future and async/await.
Be careful, because your class ShowDialog is waiting for two parameters, one is a Function and the other is the context.
There are some errors in this line
await ShowDialog(
await database.deleteIncomeData(transaction),
context,
);
First, you don't need to await because here you are calling the constructor. You have to call the showMyDialog method.
await ShowDialog(
await database.deleteIncomeData(transaction),
context,
).showMyDialog();
So, in that case, you will have something to await. The result of showMyDialog.
And the second one is that you are not passing a Function to the constructor, you are passing the value that await database.deleteIncomeData(transaction) returns.
(Unless the value that returns is a Function, in that case, it is ok)
And to fix that you have to pass a Function that calls database.deleteIncomeData(transaction)
await ShowDialog(
() => await database.deleteIncomeData(transaction),
context,
).showMyDialog();

Show upload progress indicator on top using getx

I am working on video uploading using the flutter_uploader package. So a progress needs to be shown on the top, during video uploading. How to achieve this in flutter using getx package? Or any other alternative methods are available?
Call api fun and as well as show uploading percentage on a custom dialog,
Like this-
() async {
_storiesController.createStory(widget.path);
showDialog(
context: context,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
title: Row(
children:[
CircularProgressIndicator(),
Obx(()=> Text(' Uploading ${(_storiesController.progressBar.value*100).toInt()}%'))
]
),
actions: <Widget>[
FlatButton(
child: Text("SKIP"),
onPressed: () {
Get.back();
Get.back();
},
),
],
),
);
},
);
},
**get percentage from api **
onSendProgress: (int sent, int total) {
final progress = sent / total;
print('progress: $progress ($sent/$total)');
// setState(() {
progressBar.value = sent / total;
test.value = progressBar.value.toString();
print('actual uploading :${progressBar.value}');
// });
},

how to rebuild dialog Widget on changing bool variable state?

im trying to submit form on Dialog and i have a DateTimePicker button and need to make a validation on it also before submitting , what i want to do is showing a text error in case no date picked by changing my own variable "isValid" to false but the UI is not updating , i have to close the dialog and reopen it to see the error text even though i wrapped my column with a StatefulBuilder
my dialog photo here
here is my code
StatefulBuilder(builder: (context, StateSetter setState) {
return isValid == false
? Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
Text(
'error',
style: TextStyle(
color: Colors.red, fontSize: 10),
),
],
)
: Column(
children: [
ElevatedButton(
onPressed: () {
DateTimePicker(context)
.then((value) => setState(() {
_appointmentDateTime = value;
}));
},
child: Text(getTimeDate())),
],
);
})
Validating form + toggling the isValid Value is working fine
OutlinedButton(
onPressed: () async {
if (_formKey.currentState.validate() &&
_appointmentDateTime != null) {
String date = DateFormat('yyyy-MM-dd hh:mm')
.format(_appointmentDateTime);
var appointment = Appointment(
patientName: widget.patient.name,
date: date,
hospital: _hospitalController.text,
await FirebaseApi.addPatientAppointment(
widget.patient.id, appointment);
print('Appointment Created ');
_formKey.currentState.reset();
setState(() {
translator = null;
_appointmentDateTime = null;
});
Navigator.pop(context);
}
else {
setState(() {
isValid = !isValid;
});
}
},
child: Text('Add Appointment')),
It can get confusing when writing the code like this when dealing with Dialogs. The setState you are using in the OutlinedButton is not the same as the setState used in the StatefulBuilder. You need to enclose your OutlinedButton inside the StatefulBuilder too. If you ever use a StatefulBuilder inside a stateful widget, it is better to use a different name like e.g setDialogState.
It is even better to create a separate stateful widget class just for your Dialog contents and pass the formKey and anything else than using a StatefulBuilder in this case to avoid confusion.

Only update UI when there are changes in the server, using streambuilder

I am using streambuilder to check whether a new order is placed or not.
I am checking the order status, if the order status is unknown I want to show a pop up, which works fine. but if i don't select an option to update the order status, streambuilder refreshes after a few seconds, and show another pop up on top of it.
Get Orders Function:
Future<Orders> getOrders() async {
String bsid = widget.bsid;
try {
Map<String, dynamic> body = {
"bsid": bsid,
};
http.Response response = await http.post(
Uri.parse(
"**API HERE**"),
body: body);
Map<String, dynamic> mapData = json.decode(response.body);
Orders myOrders;
print(response.body);
if (response.statusCode == 200) {
print("Success");
myOrders = Orders.fromJson(mapData);
}
return myOrders;
} catch (e) {}
}
Here's the stream function:
Stream<Orders> getOrdersStrem(Duration refreshTime) async* {
while (true) {
await Future.delayed(refreshTime);
yield await getOrders();
}}
StreamBuilder:
StreamBuilder<Orders>(
stream: getOrdersStrem(
Duration(
seconds: 2,
),
),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
CircularProgressIndicator.adaptive(),
);
}
var orders = snapshot.data.statedatas;
return ListView.builder(
itemCount: orders.length,
itemBuilder: (BuildContext context, int index) {
var orderResponse =
snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponse == "0") {
print("order status unknown");
Future.delayed(Duration.zero, () {
_playFile();
showCupertinoDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Center(
child: Text(
"#${orders[index].ordrAutoid}",
),
),
content: Row(
children: [
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.green; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "accept");
// setState(() {});
},
child: Text('Accept'),
),
),
SizedBox(
width: 15,
),
SizedBox(
width: 120,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty
.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(
MaterialState.pressed))
return Colors.black;
return Colors
.red; // Use the component's default.
},
),
),
onPressed: () async {
_stopFile();
Navigator.pop(context);
await changeOrderStatus(
orders[index].orid, "Reject");
// setState(() {});
},
child: Text('Reject'),
),
),
// TextButton(
// onPressed: () async {
// _stopFile();
// Navigator.pop(context);
// await changeOrderStatus(
// orders[index].orid, "reject");
// },
// child: Text('reject'),
// ),
],
),
),
);
}).then((value) {
_stopFile();
print("ENDING");
});
}
return Container();
Create a variable to check for the last known order status, outside your if statement, and when a new value comes, compare it to the old value first, then do the if statement logic.
//This is outside the stream builder:
String orderResponseCheck = "";
.
.
.
//This is inside your streambuidler, if the orderResponseCheck is still equal to "", the if statement will be executed,
//and the value of orderResponse wil be assigned to it. This will only show the alert dialog if the orderResponse status changes from the one that previously triggered it.
var orderResponse =snapshot.data.statedatas[index].strAccept;
print(orderResponse);
if (orderResponseCheck != orderResponse && orderResponse == "0") {
orderResponseCheck = orderResponse;
.
.
.
//logic same as before
You shouldn't call showCupertinoDialog (and probably _playFile()) from your build method. Wrapping it with Future.delayed(Duration.zero, () { ... }) was probably a workaround for an error that was given by the framework.
The build method can get executed multiple times. You probably want a way to run _playFile and show the dialog that isn't depending on the UI. I don't think StreamBuilder is the right solution for this.
You could use a StatefulWidget and execute listen on a stream from the initState method. initState will only be called once.
From what I'm reading, you're querying your API every two seconds.
Every time your API answers, you're pushing the new datas to your StreamBuilder, which explains why you're having multiple pop-ups are stacking.
One simple solution to your problem would be to have a boolean set to true when the dialog is displayed to avoid showing it multiple times.
bool isDialogShowing = false;
...
if (orderResponse == "0" && !isDialogShowing) {
isDialogShowing = true;
...
But there are a few mistakes in your code that you should avoid like :
Infinite loops
Querying your API multiple times automatically (it could DDOS your service if plenty of users are using your app at the same time)
Showing your Dialog in a ListView builder