Flutter : Show an Alert Dialog after an async Api call - flutter

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

Related

How to display N snackbar in a single page with scaffold?

I am trying to send a reset password link to users email.
Case 1: Display a snackbar when User has no email registered.
Case 2: Display a snackbar when email is sent.
Case 3: Display a snackbar if error.
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
This is the build function:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: InkWell(...
This function Displays the snackbar:
//SnackBar
void showInSnackBar(String value) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(value),
duration: const Duration(seconds: 5),
action: SnackBarAction(
label: 'OKAY',
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
),
));
}
I am calling show the snackbar for this button:
CustomButton(
name: 'Get Confirmation Email',
color: Colors.redAccent,
onPressed: () async {
if (_formKey.currentState.validate()) {
FocusScope.of(context).unfocus();
try {
//Check if email already exist
List<String> res =
await _auth.checkEmail(_emailController.text);
print(res);
if (res == ['password']) {
await _auth.resetPassword(_emailController.text);
showInSnackBar('Email sent.Check your mail');
} else {
showInSnackBar('Email is not registered');
}
} catch (e) {
showInSnackBar(e);
}
}
},
)
Output:[password]
Problem:
on the UI it shows:Email is not registered,but the it should be Email sent.Check your mail.
I do not unserstand what is wrong here.Is it not getting the right context?
you just test your condition like
String res = await _auth.checkEmail(_emailController.text);
print(res);
if (res=="password") {
await _auth.resetPassword(_emailController.text);
showInSnackBar('Email sent.Check your mail');
}

How can I get token response with username and password textfields? dart/flutter

I'm kind of new to rest api and flutter.
I wanna login with the username and password and get the token response.
How can I get the username and password from the text field for authorization?
I'm also not sure about the auth code is correct?
imports
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _HomePageState();
}
}
class _HomePageState extends State<HomePage> {
final usernameController = TextEditingController();
final passwordController = TextEditingController();
Future<String> authorization() async {
var request = http.MultipartRequest(
'POST', Uri.parse('url'));
request.fields.addAll({
'grant_type': 'info',
'client_id': 'info',
'username': username,
'password': password,
});
http.StreamedResponse response = await request.send();
if (response.statusCode == 200) {
Map<String, dynamic> auth =
jsonDecode(await response.stream.bytesToString());
return auth['access_token'];
} else {
return "";
}
}
void _setText() {
setState(() {
username = usernameController.text;
password = passwordController.text;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
backgroundColor: Colors.green,
),
body: Column(
children: [
Padding(
child: TextField(
decoration: InputDecoration(labelText: 'username'),
controller: usernameController,
),
),
Padding(
child: TextField(
decoration: InputDecoration(labelText: 'password'),
controller: passwordController,
),
),
RaisedButton(
onPressed: _setText,
child: Text('Login'),
),
],
),
);
}
}
Since you are using TextEditingControllers, you won't have to use Strings to hold the values of username and password.
change them like this:
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
Then, change your API post to this:
Future<String> authorization() async {
var request = http.MultipartRequest(
'POST', Uri.parse('url'));
request.fields.addAll({
'grant_type': 'info',
'client_id': 'info',
'username': usernameController.text, //notice you have to use .text
'password': passwordController.text, //notice you have to use .text
});
Your onPressed like this:
RaisedButton(
onPressed: () async {
String token = await authorization();
print(token); //This will be your token.
},
child: Text('Login'),),
you don't need this anymore:
//void _setText() {
//setState(() {
//username = usernameController.text;
// password = passwordController.text;
//});
//}
You should change a bit your methods to gives the parameters to you function authorization that will call your API :
// Add parameters
Future<String> authorization(username,password) async {
...
}
void _setText() {
// This will call your API with the form parameters
result = await authorization(usernameController.text, passwordController.text);
// result should have your token (= the response from the API)
}

Flutter - whenComplete() not working as expected when using Providers

I'm trying to display a loading while doing an API Request and when finished to show the list with the response or a custom widget to show a message(EmptyListWidget). The problem is that the whenComplete() method is being executed before the async function is finished.
I also tried using then() and using FutureBuilder but I also can't make it work using Provider (allways returns null).
If someone could help, I would really appreciate it.. thanks :)
My List Widget:
class _AbsencesListState extends State<AbsencesList> {
bool _isLoading = false;
bool _isInit = true;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_isInit) {
setState(() => _isLoading = true);
Provider.of<AbsencesTypes>(context, listen: false)
.getAbsencesTypes(widget.ctx)
.whenComplete(() {
setState(() => _isLoading = false);
});
_isInit = false;
}
}
#override
Widget build(BuildContext context) {
final absences = Provider.of<Absences>(context).items;
return Stack(
children: [
_isLoading
? const Center(child: CircularProgressIndicator())
: absences.length > 0
? Container()
: EmptyListWidget(ListType.InconsistenciesList),
ListView.builder(
itemBuilder: (_, index) {
return GestureDetector(
onTap: () {},
child: Card(
elevation: 2.0,
child: ListTile(
leading: CircleAvatar(
child: const Icon(Icons.sick),
backgroundColor: Theme.of(context).accentColor,
foregroundColor: Colors.white,
),
title: Padding(
padding: const EdgeInsets.only(top: 3),
child: Text(absences[index].absenceType.name),
),
subtitle: Text(
absences[index].firstDate
),
),
),
);
},
itemCount: absences.length,
)
],
);
}
}
The async function:
class AbsencesTypes with ChangeNotifier {
List<AbsenceType> _absencesTypesList = [];
List<AbsenceType> get items {
return [..._absencesTypesList];
}
void emptyAbsencesTypeList() {
_absencesTypesList.clear();
}
Future<void> getAbsencesTypes(BuildContext context) async {
SharedPreferences _prefs = await SharedPreferences.getInstance();
String token = _prefs.getString(TOKEN_KEY);
http.get(
API_URL,
headers: {"Authorization": token},
).then(
(http.Response response) async {
if (response.statusCode == 200) {
final apiResponse = json.decode(utf8.decode(response.bodyBytes));
final extractedData = apiResponse['content'];
final List<AbsenceType> loadedAbsencesTypes = [];
for (var absenceType in extractedData) {
loadedAbsencesTypes.add(
AbsenceType(
id: absenceType["id"],
name: absenceType["name"].toString(),
code: absenceType["code"].toString(),
totalAllowedDays: absenceType["totalAllowedDays"],
),
);
}
_absencesTypesList = loadedAbsencesTypes;
} else if (response.statusCode == 401) {
Utility.showToast(
AppLocalizations.of(context).translate("expired_session_string"));
Utility.sendUserToLogin(_prefs, context);
}
notifyListeners();
},
);
}
}
Your problem here is probably that you're calling http.get without awaiting for it's result.
The getAbsencesTypes returns the Future<void> as soon as the http.get method is executed, without waiting for the answer, and it results in your onComplete method to be triggered.
A simple fix would be to add the await keyword before the http.get, but you could do even better.
In your code, you're not fully using the ChangeNotifierProvider which could solve your problem. You should check the Consumer class which will be pretty useful for you here, but since it's not your initial question I won't go more in depth on this subject.

How to get tokenID from Stripe credit card in flutter

I want to get the tokenID from my credit card. I saw this Article is using StripeSource.addSource but the latest stripe package did not have StripeSource.addSource. So I do like this:
StripePayment.paymentRequestWithCardForm(
CardFormPaymentRequest())
.catchError((e) {
print('ERROR ${e.toString()}');
}).then((PaymentMethod paymentMethod) async {
final CreditCard testCard = CreditCard(
number: paymentMethod.card.number,
expMonth: paymentMethod.card.expMonth,
expYear: paymentMethod.card.expYear
);
StripePayment.createTokenWithCard(testCard).then((token) {
print(token.tokenId);
});
});
But I get the error Unhandled Exception: PlatformException(invalidRequest, Missing required param: card[number]., null)
Anyone can help me solve this problem? Thank you
I do like this:
class _MyHomePageState extends State<MyHomePage> {
static String secret = 'sk_test_xxxxxxxxxxxxx';
TextEditingController cardNumberController = new TextEditingController();
TextEditingController expYearController = new TextEditingController();
TextEditingController expMonthController = new TextEditingController();
#override
initState() {
super.initState();
StripePayment.setOptions(
StripeOptions(publishableKey: "pk_testxxxxxx", merchantId: "Test", androidPayMode: 'test')
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: cardNumberController,
decoration: InputDecoration(labelText: 'Card Number'),
),
TextField(
controller: expMonthController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Expired Month'),
),
TextField(
controller: expYearController,
keyboardType: TextInputType.number,
decoration: InputDecoration(labelText: 'Expired Year'),
),
RaisedButton(
child: Text('card'),
onPressed: () {
final CreditCard testCard = CreditCard(
number: cardNumberController.text,
expMonth: int.parse(expMonthController.text),
expYear: int.parse(expYearController.text)
);
StripePayment.createTokenWithCard(testCard).then((token) {
print(token.tokenId);
createCharge(token.tokenId);
});
},)
],
),
),
);
}
static Future<Map<String, dynamic>> createCharge(String tokenId) async {
try {
Map<String, dynamic> body = {
'amount': '2000',
'currency': 'usd',
'source': tokenId,
'description': 'My first try'
};
var response = await http.post(
'https://api.stripe.com/v1/charges',
body: body,
headers: { 'Authorization': 'Bearer ${secret}','Content-Type': 'application/x-www-form-urlencoded'}
);
return jsonDecode(response.body);
} catch (err) {
print('err charging user: ${err.toString()}');
}
return null;
}
}
You do not need a token if you have a paymentMethod.
This code works for me:
PaymentMethod paymentMethod = PaymentMethod();
paymentMethod = await StripePayment.paymentRequestWithCardForm(
CardFormPaymentRequest(),
).then((PaymentMethod paymentMethod) {
return paymentMethod;
}).catchError((e) {
print('Errore Card: ${e.toString()}');
});
After this, you can check the paymentMethod and if not null you can go ahead with charging the card

Flutter: Reset Password takes me to Home Page instead of back to Login Page

I am new to Flutter, when I press Submit on Reset Password a reset email is sent however I am navigated to my Home Page instead of back to my Login Page. What am I doing wrong.
Code is below:
import 'package:flutter/material.dart';
import 'stacked_icons.dart';
import 'auth.dart';
class LoginPage extends StatefulWidget {
LoginPage({this.auth, this.onSignedIn});
final BaseAuth auth;
final VoidCallback onSignedIn;
#override
State<StatefulWidget> createState() => _LoginPage();
}
enum FormType {
login,
register,
reset
}
class _LoginPage extends State<LoginPage> {
final formKey = new GlobalKey<FormState>();
String _email;
String _password;
String _name;
FormType _formType = FormType.login;
bool validateAndSave() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
return true;
}
return false;
}
void validateAndSubmit() async {
if (validateAndSave()) {
try {
if (_formType == FormType.login) {
String userId = await widget.auth.singInWithEmailAndPassword(_email, _password);
print('Signed in: $userId');
} else if (_formType == FormType.reset){
await widget.auth.sendPasswordResetEmail(_email);
print("Password reset email sent");
//Navigator.of(context).pushReplacementNamed ('moveToReset');
setState(() {
_formType = FormType.login;
});
} else if (_formType == FormType.register){
String userId = await widget.auth.createUserWithEmailAndPassword(_email, _password, _name);
print('Registered user: $userId');
setState(() {
_formType = FormType.login;
});
}
widget.onSignedIn();
} catch (e) {
print('Error: $e');
showDialog(
context: context,
builder: (context){
return AlertDialog(
title: Text('Sign in failed'),
content: Text(e.toString()),
actions: [
FlatButton(
child: Text('OK'),
onPressed: () => Navigator.of(context).pop(),
),
],
);
}
);
}
}
}
void moveToRegister(){
formKey.currentState.reset();
setState(() {
_formType = FormType.register;
});
}
void moveToLogin(){
formKey.currentState.reset();
setState(() {
_formType = FormType.login;
});
}
void moveToReset(){
formKey.currentState.reset();
setState(() {
_formType = FormType.reset;
});
}
Here is snippet of the Submit Button
else if (_formType == FormType.reset){
return [
new Row(
children: <Widget>[
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 20.0, right: 20.0, top: 10.0),
child: GestureDetector(
onTap: () {
validateAndSubmit();
},
child: new Container(
alignment: Alignment.center,
height: 60.0,
decoration: new BoxDecoration(
color: Color(0xFF18D191),
borderRadius: BorderRadius.circular(10.0)),
child: new Text(
"Submit",
style: new TextStyle(
fontSize: 20.0, color: Colors.white),
),
),
),
),
),
],
),
Please send correct code to navigate back to login after reset.
I have tried the Navigator.pushReplacementNamed however I do not know how to implement the String.
I have also tried the Navigator.pop and I get and error message when I press my Submit Button.
My assumption was that the setState would do the job but I am seeing that its not working or maybe I did not put it in correctly.
As indicated above I am still new to Flutter and am trying to figure out where I am going wrong.
You don't handle your reset case properly according to your use case.
There is out-commented navigation code which would navigate you to a moveToReset Page.
//Navigator.of(context).pushReplacementNamed ('moveToReset');
I suggest using your moveToLogin() method and change it's logic to include a navigation to the actual login page. It's method name is misleading to the current logic it contains.
https://flutter.dev/docs/cookbook/navigation/named-routes
Possible solutions:
Define a ResetPage Widget and a route to it.
Add the following to your validateOnSubmit in the else if(_formType == FormType.reset)
Navigator.pushNamed(context, '/yourResetPageRoute');
This way you'll use the Router and a new Page.
Another option to simply display the reset form ist your started apporach in the second code snippet. There you return a Widget (Row) to which you can add a Reset Form. This is no separate page and doesn't use routing.