Related
i am confused where can i implement the init state condition for future builder. see whats wrong in my code. in flutter documentation is refer initState for Future builder widget.
` #override
void initState() {
super.initState();
futureLoginuser = loginuser();
}`
i am trying to navigate new screen after response data arrives.my complete code is here i am using go_router for navigation.
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _mobileKey = GlobalKey<FormState>();
final _passwordKey = GlobalKey<FormState>();
get message => null;
get messagecode => null;
get userinfo => null;
get token => null;
Future<Loginuser> loginuser(String mobile, String password) async {
final response = await http.post(
Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
} else {
throw Exception('Failed to update');
}
#override
Widget build(BuildContext context) {
return Form(
key: _mobileKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
key: _passwordKey,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
FutureBuilder<Loginuser>(
future: loginuser(mobileController.text.toString(),
passwordController.text.toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
print('snapshsot.hasdata');
context.go('/Home');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
child: const Text('Submit')),
),
]));
}
}
`
You are using the FutureBuilder wrong it is a Widget that builds itself based on the latest snapshot of interaction with a Future. Its usually used to build widgets that need input when a certain future is completed.
In your case use this:
//first make the onPressed function async
child ElevatedButton(
child: Container(),
onPressed: () async {
// then await the future You want to complete and then use `.then()`
//method to implement the code that you want to implement when the future is completed
await loginuser(mobileController.text.toString(),
passwordController.text.toString())
.then((result) {
print('future completed');
context.go('/Home');
// For errors use onError to show or check the errors.
}).onError((error, stackTrace) {
print(error);
});
});
And do validate the form for any error with form key validation method.
the only way to wait for the future to complete and do something is to use the Asynchronous function either directly as I have shown above or by using the try/catch method both will work fine.
Try this
LoginUser? loginUser
#override
void initState() async{
super.initState();
futureLoginuser = await loginuser();
... // 👈 Your navigation here
}`
Try to make your build responsive by checking for loginUser
#override
Widget build(BuildContext context) {
futureLoginuser == null ?
CircularProgressIndicator() : <Rest of your widget>
}
The way you are trying to implement is not correct, here is very basic example to do
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
// your other variables
bool isValidating = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: isValidating
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
setState(() {
isValidating = !isValidating;
});
final r = await loginuser(
mobileController.text.toString(),
passwordController.text.toString());
if (r != null) {
// save user state locally, using hive or what alternative you want
context.go('/Home');
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Failed'),
));
}
setState(() {
isValidating = !isValidating;
});
}
},
child: const Text('Submit')),
),
]));
}
Future<Loginuser?> loginuser(String mobile, String password) async {
final response = await http.post(Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
return null;
}
}
I have a chat-app that uses a single TextField controlled by a TextEditingController to enter text messages.
Pressing the associated IconButton sends the message if the message is not empty and then clears the TextEditingController. This all works perfectly. After sending the message, the text input field gets cleared.
BUT, here comes the bug, if I press the send button again, the message is sent once more. How come and how can I prevent this?
class NewMessage extends StatefulWidget {
#override
_NewMessageState createState() => _NewMessageState();
}
class _NewMessageState extends State<NewMessage> {
final _controller = TextEditingController();
var _enteredMessage = '';
void _sendMessage() async {
FocusScope.of(context).unfocus();
final user = FirebaseAuth.instance.currentUser;
final userData = await FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.get();
FirebaseFirestore.instance.collection('chat').add({
'text': _enteredMessage,
'createdAt': Timestamp.now(),
'userId': user.uid,
'username': userData.data()['username'],
'userImage': userData.data()['image_url']
});
_controller.clear();
}
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 8),
padding: EdgeInsets.all(8),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _controller,
textCapitalization: TextCapitalization.sentences,
autocorrect: true,
enableSuggestions: true,
decoration: InputDecoration(labelText: 'Send a message...'),
onChanged: (value) {
setState(() {
_enteredMessage = value;
});
},
),
),
IconButton(
color: Theme.of(context).primaryColor,
icon: Icon(
Icons.send,
),
onPressed: _enteredMessage.trim().isEmpty ? null : _sendMessage,
)
],
),
);
}
}
The use of a TextEditingController AND an onChanged event for a TextField can be problematic. The issue is discussed in depth here: TextEditingController vs OnChanged
In my case, I finally decided to go for an TextEditingController only solution. This way, we can get rid of the _enteredMessage variable and the onChanged/setState() statements alltogether.
Instead, we need to add a listener to our TextEditingController and call setState() in our initState() method.
Finally, we need to dispose the _controller in the dispose() method to prevent memory leaks.
Here is the code of my TextEditingController only solution:
class NewMessage extends StatefulWidget {
#override
_NewMessageState createState() => _NewMessageState();
}
class _NewMessageState extends State<NewMessage> {
var _controller = TextEditingController();
#override
void initState() {
_controller = TextEditingController();
_controller.addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
void _sendMessage() async {
FocusScope.of(context).unfocus();
final user = FirebaseAuth.instance.currentUser;
final userData = await FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.get();
FirebaseFirestore.instance.collection('chat').add({
'text': _controller.text,
'createdAt': Timestamp.now(),
'userId': user.uid,
'username': userData.data()['username'],
'userImage': userData.data()['image_url']
});
_controller.clear();
}
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 8),
padding: EdgeInsets.all(8),
child: Row(
children: <Widget>[
Expanded(
child: TextField(
controller: _controller,
textCapitalization: TextCapitalization.sentences,
autocorrect: true,
enableSuggestions: true,
decoration: InputDecoration(labelText: 'Send a message...'),
),
),
IconButton(
color: Theme.of(context).primaryColor,
icon: Icon(
Icons.send,
),
onPressed: _controller.text.trim().isEmpty ? null : _sendMessage,
),
],
),
);
}
}
You are clearing the controller in the button callbkack
_controller.clear(), but what you are really sending to Firebase is not the _controller text but rather the variable
_enteredMessage which does not get cleared.
if you just send the controller text instead of _enteredMessage the problem should be solved:
FirebaseFirestore.instance.collection('chat').add({
'text': _controller.text,
'createdAt': Timestamp.now(),
'userId': user.uid,
'username': userData.data()['username'],
'userImage': userData.data()['image_url']
});
Also always dispose your controllers in the Stateful Widget onDispose method to avoid memory leaks.
EDIT:
The condition on which the button callback gets called should also change to:
...
onPressed: _controller.text.isEmpty ? null : _sendMessage
...
I've found the easiest solution here is to replace
onPressed: _enteredMessage.trim().isEmpty ? null : _sendMessage,
with
onPressed: () {
if (_controller.text.trim().isNotEmpty) _sendMessage();
}
I have got the value stored in Documents directory in initState() using widget.storage.readCounter() function. I want to get the value and store in TextEditingController as an initial value. But I am not able to get the value out of the initState() block. How do I get the value out of the initState() block? Here is my full code:
class _UserForm extends State<UserForm> {
TextEditingController uid;
static String userid;
#override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
userid = value.toString();
/*setState(() => {
userid = value.toString(),
});*/
});
uid = TextEditingController(text: userid);
}
final _formKey = GlobalKey<FormState>();
//TextEditingController uid = TextEditingController(text: userid);
final TextEditingController myController = TextEditingController();
final TextEditingController myController2 = TextEditingController();
final TextEditingController myController3 = TextEditingController();
final TextEditingController myController4 = TextEditingController();
final TextEditingController myController5 = TextEditingController();
final TextEditingController myController6 = TextEditingController();
#override
void dispose() {
// Clean up the controller when the widget is disposed.
uid.dispose();
myController.dispose();
myController2.dispose();
myController3.dispose();
myController4.dispose();
myController5.dispose();
myController6.dispose();
super.dispose();
}
Future<SubmitData> _futureSubmitData;
#override
Widget build(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('uid:' + uid.text),
);
},
);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
/*Visibility(
visible: false,
child: TextFormField(
controller: uid,
),
),*/
TextFormField(
controller: myController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration:
InputDecoration(hintText: 'Sale amount as per party'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter amount.';
}
return null;
},
),
TextFormField(
controller: myController2,
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: 'Item quantity'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter quantity.';
}
return null;
},
),
TextFormField(
controller: myController3,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(hintText: 'Rate'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter rate.';
}
return null;
},
),
TextFormField(
controller: myController4,
decoration: InputDecoration(hintText: 'Broker name'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter broker name.';
}
return null;
},
),
TextFormField(
controller: myController5,
decoration: InputDecoration(hintText: 'Party name'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter party name.';
}
return null;
},
),
TextFormField(
controller: myController6,
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
hintText: 'Amount receivable as per party'),
validator: (value) {
if (value.isEmpty) {
return 'Please enter amount.';
}
return null;
},
),
Container(
margin: EdgeInsets.only(top: 10),
child: RaisedButton(
color: Colors.purple[400],
padding: EdgeInsets.only(
left: 110, top: 10, right: 110, bottom: 10),
textColor: Colors.white,
onPressed: () {
if (_formKey.currentState.validate()) {
setState(() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('uid:' + uid.text),
);
},
);
/*_futureSubmitData = userData(
uid.text,
myController.text,
myController2.text,
myController3.text,
myController4.text,
myController5.text,
myController6.text);*/
});
Scaffold.of(context).removeCurrentSnackBar();
}
},
child: Text('Submit', style: TextStyle(fontSize: 18.0)),
),
),
],
),
(_futureSubmitData != null)
? FutureBuilder<SubmitData>(
future: _futureSubmitData,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.type == '1') {
/*showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Data submitted successfully.'),
);
},
);*/
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text('Data submitted successfully.',
style:
TextStyle(color: Colors.green[100]))));
return Text('');
} else {
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(
content: Text(
'Data not submitted. You have already submitted for today.',
style: TextStyle(color: Colors.red[100]))));
return Text('');
}
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(child: CircularProgressIndicator());
},
)
: Text(''),
],
));
}
}
But I got the value of userid in initState(). But when I printed the value in showDialog() in Widget build(BuildContext context) which is after the initState() block, it gives blank value. My problem is how to get the value of uid in the Widget build(BuildContext context) block?
I might be wrong (edit: I am wrong.), but I believe your problem is that "technically" you aren't assigning the value of your variable inside the initState method, but in a callback.
What you could do is rewrite the callback as such:
#override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
setState( () => {
userid = value.toString();
});
});
}
Initialize your TextController and userid variables first outside your initState in your state Class
TextEditingController uid;
String userid;
#override
void initState() {
super.initState();
widget.storage.readCounter().then((int value) {
userid = value.toString();
uid = TextEditingController(text: userid);
});
print(userid); // This is to make sure you're actually getting a value back. Check your console.
}
You should now have access to the value of "userid" through your TextEditingController
After validating a form and sending a request from flutter to the server backend: I want to set any potential error message from the server to be displayed in the original form. Preferably exactly like a validation error.
For instance:
Widget build(BuildContext context) {
...
TextFormField(
onFieldSubmitted: (value) => _signIn(),
validator: (input) {
if (input.length < 6)
return 'Your password is too short';
return null;
},
onSaved: (input) => _password = input,
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
)
...
}
Future<void> _signIn() async {
final formState = _formKey.currentState;
if (!formState.validate()) return;
formState.save();
try {
... // do fancy request stuff
} catch (e) {
// this is where I want to set the "validation" error
}
}
It's actually super simple and the validation error still works aswell.
String? _errorMsg;
Widget build(BuildContext context) {
...
TextFormField(
onFieldSubmitted: (value) => _signIn(),
validator: (input) {
if (input.length < 6)
// will set the errorText directly, no need for a variable here
return 'Your password is too short';
return null;
},
onSaved: (input) => _password = input,
decoration: InputDecoration(
labelText: 'Password',
errorText: _errorMsg,
),
obscureText: true,
)
...
}
Future<void> _signIn() async {
setState(() {
_errorMsg = null; // clear any existing errors
});
final formState = _formKey.currentState;
if (!formState.validate()) return;
formState.save();
try {
... // do fancy request stuff
} catch (e) {
setState(() {
_errorMsg = 'Wrong password.';
});
}
}
I suppose, I could think of a solution, but I think it's kind of ugly.
I could have an "error" variable, that is set when the request fails.
I would then call formState.validate() a second time, in there: check the error variable and return it if it's not null.
You can use flutter_form_bloc and use addError method of TextFieldBloc.
usernameField.addError('That username is taken. Try another.');
Keep in mind that you can also use asynchronous validators.
This is a complete example:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^0.21.0
form_bloc: ^0.5.0
flutter_form_bloc: ^0.4.1+1
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:form_bloc/form_bloc.dart';
void main() {
runApp(MaterialApp(home: SignUpForm()));
}
class SignUpFormBloc extends FormBloc<String, String> {
final usernameField = TextFieldBloc();
final passwordField =
TextFieldBloc(validators: [Validators.passwordMin6Chars]);
#override
List<FieldBloc> get fieldBlocs => [usernameField, passwordField];
#override
Stream<FormBlocState<String, String>> onSubmitting() async* {
// Form logic...
try {
await _signUp(
throwException: true,
username: usernameField.value,
password: passwordField.value,
);
yield currentState.toSuccess();
} catch (e) {
// When get the error from the backend you can
// add the error to the field:
usernameField.addError('That username is taken. Try another.');
yield currentState
.toFailure('The error was added to the username field.');
}
}
Future<void> _signUp({
#required bool throwException,
#required String username,
#required String password,
}) async {
print(username);
print(password);
await Future<void>.delayed(Duration(seconds: 2));
if (throwException) throw Exception();
}
}
class SignUpForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<SignUpFormBloc>(
builder: (context) => SignUpFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<SignUpFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Sign Up Form')),
body: FormBlocListener<SignUpFormBloc, String, String>(
onSubmitting: (context, state) {
// Show the progress dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator(),
),
),
),
),
);
},
onSuccess: (context, state) {
// Hide the progress dialog
Navigator.of(context).pop();
// Navigate to success screen
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SuccessScreen()));
},
onFailure: (context, state) {
// Hide the progress dialog
Navigator.of(context).pop();
// Show snackbar with the error
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.failureResponse),
backgroundColor: Colors.red[300],
),
);
},
child: ListView(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.usernameField,
decoration: InputDecoration(labelText: 'Username'),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.passwordField,
decoration: InputDecoration(labelText: 'Password'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: formBloc.submit,
child: Center(child: Text('SUBMIT')),
),
),
],
),
),
);
},
),
);
}
}
class SuccessScreen extends StatelessWidget {
const SuccessScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[300],
body: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Icon(
Icons.sentiment_satisfied,
size: 100,
),
RaisedButton(
color: Colors.green[100],
child: Text('Sign out'),
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SignUpForm())),
)
],
),
),
),
);
}
}
A simple solution:
Make a key for the widgets state:
GlobalKey<_CustomErrorTextField> _passwordTextFieldState = GlobalKey();
Set the Error message using the key:
_passwordTextFieldState.currentState.updateError(errorMsg);
Reset the error after 2 seconds:
Future.delayed(Duration(seconds: 2), () {
// Runs after duration sec
_passwordTextFieldState.currentState.updateError(null);
});
Set up the widget (be sure to set the key):
CustomErrorTextField(
key: _passwordTextFieldState,
label: "Password",
currentPassword: password,
validator: yourValidator,
callback: passwordCallback,
obscureText: hidePassword.value - a bool value show/hide password
)
Here is the Widget:
class CustomErrorTextField extends StatefulWidget {
CustomErrorTextField({
Key key,
this.label,
this.currentPassword,
this.validator,
this.callback,
this.obscureText = false
}): super(key: key);
final String label;
final String currentPassword;
final FormFieldValidator<String> validator;
final Function callback;
final obscureText;
#override
_CustomErrorTextField createState() => _CustomErrorTextField();
}
class _CustomErrorTextField extends State<CustomErrorTextField> {
String errorMsg;
updateError(String errorMsg){
setState(() {
this.errorMsg = errorMsg;
});
}
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
labelText: widget.label,
errorText: errorMsg
),
initialValue: widget.currentPassword,
keyboardType: TextInputType.visiblePassword,
validator: widget.validator,
onSaved: (String val) {
widget.callback(val);
},
obscureText: widget.obscureText,
);
}
}
This is the code to get the Login response. If there is an error I want to show an Alert dialog saying that there was an error during Login.
Future<String> login(String username, String password) async {
Map<String, dynamic> params = {
'username': username,
'password': password,
};
final response = await http.post('apiurl', body: params);
if (response.statusCode != 200)
throw Exception(response.body);
return response.body;
}
I'm adding the code from where the call to login happens. There is a TODO in the _loginController where I want to Show an Alert Dialog which shows the error.
class LoginWidgetState extends State<LoginWidget> {
var _usernameController = new TextEditingController();
var _passwordController = new TextEditingController();
void _loginButtonClickHandler() {
var username = _usernameController.text;
var password = _passwordController.text;
login(username, password).then((response) {
}).catchError((e) {
//TODO : show an Alert Here
});
}
#override
Widget build(BuildContext context) {
var container = Center(
child: Container(
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: "username",
),
controller: _usernameController,
),
TextField(
obscureText: true,
decoration: InputDecoration(hintText: "password"),
controller: _passwordController,
),
RawMaterialButton(
onPressed: _loginButtonClickHandler,
child: Text("Login"),
)
],
),
),
);
return container;
}
}
To give more context to the accepted answer...
If you make a remote API call like this:
login(username, password).then((response) {
}).catchError((e) {
//TODO : show an Alert Here
});
then you can replace the TODO with this (if you are calling it from a button click):
_showAlertDialog(context);
Or this (if you are calling it from within a build() method or initState():
WidgetsBinding.instance.addPostFrameCallback((_) => _showAlertDialog(context));
Where the method is defined as
void _showNewVersionAvailableDialog(BuildContext context) {
final alert = AlertDialog(
title: Text("Error"),
content: Text("There was an error during login."),
actions: [FlatButton(child: Text("OK"), onPressed: () {})],
);
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Notes
You need to have a reference to the BuildContext.
For more info about addPostFrameCallback, read this article.
Refer here to show the dialog.
Send context to _loginButtonClickHandler and you are done. Cheers
You can use RFlutter Alert library for that. It is easily customizable and easy-to-use alert/popup dialog library for Flutter. I hope it will help you.
Example alert with RFlutter Alert:
Alert(context: context, title: "RFLUTTER", desc: "Flutter is awesome.").show();
*I'm one of developer of RFlutter Alert.
This will help you!
Future<String> login(String username, String password) async {
Map<String, dynamic> params = {
'username': username,
'password': password,
};
final response = await http.post('apiurl', body: params);
if (response.statusCode != 200)
{
throw Exception(response.body);
}
else
{
showWhateverFunction();
}
return response.body;
}