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

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

Related

Do not use BuildContexts across async gaps - Flutter

I use SnackBar after, the await.
So, It's showing error like this:
Do not use BuildContexts across async gaps
I used if (!mounted) this line to remove the error. It removed the problem but, SnackBar is not showing. When complete the task
My code here:
Future removeMethod() async {
String res = await DatabaseMethods().idReject(widget.uid);
if (res == "success") {
if (!mounted) return;
showSnackBar(context, "Job done!");
} else {
if (!mounted) return;
showSnackBar(context, "Error!!");
}
}
showSnackBar is customWidget. code of it:
void showSnackBar(BuildContext context, String title) {
final snackBar = SnackBar(
content: CustomText(
text: title,
size: 16,
),
backgroundColor: darkblueColor,
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
What can I do about this?. I want to show snackbar after await.
This basic example works based on your snippets. Here a snackbar is called after the result of a future is returned (e.g. a database call). If this helps?
import 'dart:math';
import 'package:flutter/material.dart';
class SnackBarAfterFutureResult extends StatelessWidget {
const SnackBarAfterFutureResult({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: OutlinedButton(
onPressed: () async {
await removeMethod()
.then((value) => showSnackBar(context, value));
},
child: const Text('Database call')),
),
);
}
}
void showSnackBar(BuildContext context, bool result) {
String text = "Job done!";
if (!result) {
text = "Error!!";
}
final snackBar = SnackBar(content: Text(text), backgroundColor: Colors.blue);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
Future<bool> removeMethod() async {
//e.g. getting result from database call
bool isSuccess = false;
await Future.delayed(const Duration(seconds: 2), () {
isSuccess = Random().nextBool();
});
return isSuccess;
}

Do not use BuildContexts across async gaps flutter [duplicate]

This question already has answers here:
Do not use BuildContexts across async gaps
(10 answers)
Closed last month.
I've made a function for registration and I'm getting a warning Do not use BuildContexts across async gaps. on Utils.flushBarErrorMessage("No Internet", context); I'm new to flutter and want to know how to use async and await.
Future _registration() async {
String name = _nameController.text.trim();
String email = _emailController.text.trim();
String password = _passwordController.text.trim();
String phone = _phoneController.text.trim();
if (name.isEmpty) {
Utils.flushBarErrorMessage("Type your name", context);
} else if (email.isEmpty) {
Utils.flushBarErrorMessage("Type your email", context);
} else if (!GetUtils.isEmail(email)) {
Utils.flushBarErrorMessage("Type valid email address", context);
} else if (password.isEmpty) {
Utils.flushBarErrorMessage("Type your password", context);
} else if (password.length < 6) {
Utils.flushBarErrorMessage(
"password can't be less than 6 characters", context);
} else if (phone.isEmpty) {
Utils.flushBarErrorMessage("Type your phone", context);
}
else {
var connectivityResult = await (Connectivity().checkConnectivity());
if (connectivityResult == ConnectivityResult.mobile ||
connectivityResult == ConnectivityResult.wifi) {
ApiCall.signUp(name, email, password, phone).then((value) {
if (value.statusCode == 200) {
if (json.decode(value.body)['success'] != null) {
if (json.decode(value.body)["success"]) {
RegisterResponse registerResponseModel =
RegisterResponse.fromJson(json.decode(value.body));
Navigator.pushNamed(context, VerifyUser.routeName);
Utils.flushBarErrorMessage(
'User Registered Successfully', context);
if (kDebugMode) {
print('User Registered Successfully');
}
} else {
Utils.flushBarErrorMessage(
json.decode(value.body)["en_message"], context);
if (kDebugMode) {
print(json.decode(value.body).toString());
}
}
}
} else {
Utils.flushBarErrorMessage('invalid data', context);
if (kDebugMode) {
print(json.decode(value.body).toString());
}
}
});
} else {
Utils.flushBarErrorMessage("No Internet", context);
}
}
}
calling this _registration()
ElevatedButton(
onPressed: () {
_registration();
},
child: const Text('SignUp')),
Here is my flushBarErrorMessage.
class Utils {
static void flushBarErrorMessage(String message, BuildContext context) {
showFlushbar(
context: context,
flushbar: Flushbar(
forwardAnimationCurve: Curves.decelerate,
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
padding: const EdgeInsets.all(15),
titleColor: Colors.white,
duration: const Duration(seconds: 3),
borderRadius: BorderRadius.circular(10),
reverseAnimationCurve: Curves.easeInOut,
icon: const Icon(
Icons.error,
size: 28,
color: Colors.white,
),
flushbarPosition: FlushbarPosition.TOP,
positionOffset: 20,
message: message,
backgroundColor: Colors.red,
)..show(context));
}
}
The problem is that after an await, every use of the BuildContext will show this warning. This warning happens because using a BuildContext after an await could happen after the widget is disposed of. This way, the context wouldn't exist anymore and the app could even crash because of this. Check out the official lint documentation:
Storing BuildContext for later usage can easily lead to difficult-to-diagnose crashes. Asynchronous gaps are implicitly storing BuildContext and are some of the easiest to overlook when writing code.
The easy solution, from the official docs, is the need to check for State.mounted. The code would look something like this on every place the warning shows up:
...
} else {
if (mounted) Utils.flushBarErrorMessage("No Internet", context);
}
...
Try this:
///Add context
Future _registration(BuildContext context) async {
...
if(!mounted) return;
Navigator.pushNamed(context, VerifyUser.routeName);
...
}
When calling:
ElevatedButton(
onPressed: () {
//make sure your class is of StatefulWidget()
_registration(context); ///Add context
},
child: const Text('SignUp')),
The simplest answer for this warning is:
StatelessWidget
keep a reference of the context usage before the await keyword.
example:
// navigation
onPressed: () async {
final router = GoRouter.of(context);
final result = await [
Permission.location,
Permission.locationAlways,
Permission.locationWhenInUse
].request();
if (...) {
router.go(AppRouter.dashboard);
} else {
router.go(AppRouter.askForCustomLocation);
}
// cubit
onPressed: () async {
final appSettingsCubit = BlocProvider.of<AppSettingsCubit>(context);
final result = await [
Permission.location,
Permission.locationAlways,
Permission.locationWhenInUse
].request();
if (...) {
appSettingsCubit.locationProptAsked();
} else {
appSettingsCubit.locationProptAsked();
}
StatefullWidget
just wrap the context usage with a if(mounted) logic
example:
// navigation
onPressed: () async {
final result = await [
Permission.location,
Permission.locationAlways,
Permission.locationWhenInUse
].request();
if (...) {
if(mounted) {
router.go(AppRouter.dashboard);
}
} else {
if(mounted) {
router.go(AppRouter.dashboard);
}
}
// cubit
onPressed: () async {
final result = await [
Permission.location,
Permission.locationAlways,
Permission.locationWhenInUse
].request();
if (...) {
if(mounted) {
BlocProvider.of<AppSettingsCubit>(context).locationProptAsked();
}
} else {
if(mounted) {
BlocProvider.of<AppSettingsCubit>(context).locationProptAsked();
}
}
That's all from my experience.

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.

How to check user is logged in or not with phone authentication using firebase in flutter?

Here, I'm authenticated through the phone number with OTP code using firebase but
after login succeeded, it navigated through home page but when I click on back
it drags me login Screen.
here, the code I have tried, but it doesn't work
#override
void initState() {
super.initState();
isSignedIn();
}
void isSignedIn() async {
this.setState(() {
isLoading = true;
});
firebaseAuth.currentUser().then((user){
if(user !=null){
Navigator.of(context).pushReplacementNamed('/homepage');
}else{
verifyPhone();
}
});
this.setState(() {
isLoading = false;
});
}
Method for getting OTP code
Future<void> verifyPhone()async{
final PhoneCodeAutoRetrievalTimeout autoRetrieval=(String verId){
this.verificationId=verId;
};
final PhoneCodeSent smsCodeSent=(String verId, [int forceCodeResend]){
this.verificationId=verId;
smsCodeDialog(context).then((value){
print("Signed in");
});
};
final PhoneVerificationCompleted verificationCompleted = (AuthCredential credential) {
print("verified");
};
final PhoneVerificationFailed verfifailed=(AuthException exception){
print("${exception.message}");
};
await firebaseAuth.verifyPhoneNumber(
phoneNumber: this.phoneNo,
codeAutoRetrievalTimeout: autoRetrieval,
codeSent: smsCodeSent,
timeout: const Duration(seconds: 10),
verificationCompleted: verificationCompleted,
verificationFailed: verfifailed
);
}
here the dialog box for sign in with OTP code
Future<bool> smsCodeDialog(BuildContext context){
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return new AlertDialog(
title: Text('Enter sms Code'),
content: TextField(
onChanged: (value){
this.smsCode=value;
},
),
contentPadding: const EdgeInsets.all(10.0),
actions: <Widget>[
new FlatButton(
child: Text("Done"),
onPressed: (){
firebaseAuth.currentUser().then((user){
if(user !=null){
Navigator.of(context).pop();
Navigator.of(context).pushReplacementNamed('/homepage');
}else{
Navigator.of(context).pop();
signIn();
}
});
},
)
],
);
}
);
}
method for Sign in with phone number
signIn()async{
AuthCredential credential= PhoneAuthProvider.getCredential(
verificationId: verificationId,
smsCode: smsCode
);
await firebaseAuth.signInWithCredential(credential).then((user){
Navigator.of(context).pushReplacementNamed('/homepage');
print('signed in with phone number successful: user -> $user');
}).catchError((onError){
print(onError);
});
}
`
Welcome Shruti Ramnandan Sharma in Stackoverflow and Flutter dev.
Your code seems to working fine with me, I coded for you a one page dart that can test you the whole code with fixing your problem with going back to Login or VerifyPhone page.
Note: I changed your order of code in verifyPhone() method.
And Changed Navigator.of(context).pushReplacementNamed('/homepage'); to
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => HomeRoute()));
The whole code here
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
void main() => runApp(VerifyPhoneRoute());
class VerifyPhoneRoute extends StatefulWidget {
#override
_VerifyPhoneRouteState createState() {
return _VerifyPhoneRouteState();
}
}
class _VerifyPhoneRouteState extends State<VerifyPhoneRoute> {
bool isLoading = false;
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
String verificationId;
String phoneNo = "Your number here";
String smsCode;
#override
void initState() {
super.initState();
isSignedIn();
}
void isSignedIn() async {
this.setState(() {
isLoading = true;
});
firebaseAuth.currentUser().then((user) {
if (user != null) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeRoute()),
);
} else {
verifyPhone();
}
});
this.setState(() {
isLoading = false;
});
}
Future<void> verifyPhone() async {
final PhoneVerificationCompleted verificationCompleted =
(AuthCredential credential) {
print("verified");
};
final PhoneVerificationFailed verifyFailed = (AuthException exception) {
print("${exception.message}");
};
final PhoneCodeSent smsCodeSent = (String verId, [int forceCodeResend]) {
this.verificationId = verId;
smsCodeDialog(context).then((value) {
print("Signed in");
});
};
final PhoneCodeAutoRetrievalTimeout autoRetrieval = (String verId) {
this.verificationId = verId;
};
await firebaseAuth.verifyPhoneNumber(
phoneNumber: this.phoneNo,
codeAutoRetrievalTimeout: autoRetrieval,
codeSent: smsCodeSent,
timeout: const Duration(seconds: 10),
verificationCompleted: verificationCompleted,
verificationFailed: verifyFailed);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Inapp Plugin by dooboolab'),
),
body: Center(
child: RaisedButton(
child: Text("Verify"),
onPressed: () {
verifyPhone();
}),
),
),
);
}
Future<bool> smsCodeDialog(BuildContext context) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return new AlertDialog(
title: Text('Enter sms Code'),
content: TextField(
onChanged: (value) {
this.smsCode = value;
},
),
contentPadding: const EdgeInsets.all(10.0),
actions: <Widget>[
new FlatButton(
child: Text("Done"),
onPressed: () {
firebaseAuth.currentUser().then((user) {
if (user != null) {
Navigator.of(context).pop();
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeRoute()),
);
} else {
Navigator.of(context).pop();
signIn();
}
});
},
)
],
);
});
}
signIn() async {
AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: verificationId, smsCode: smsCode);
await firebaseAuth.signInWithCredential(credential).then((user) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeRoute()),
);
print('signed in with phone number successful: user -> $user');
}).catchError((onError) {
print(onError);
});
}
}
class HomeRoute extends StatefulWidget {
#override
_HomeRouteState createState() {
return _HomeRouteState();
}
}
class _HomeRouteState extends State<HomeRoute> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter Inapp Plugin by dooboolab'),
),
body: Center(
child: Text("Welcome There."),
),
),
);
}
}
This code works fine with me. So if there's any problem happened with you again, don't be hesitate to comment on this answer. And if this answered your question and solve your problem, please make it as answer.
Use method below by pass verificationID come from API firebase when code sent and code enter by user, so if method return FirebaseUser the code is correct if return null the code enter by user is not correct
Future<FirebaseUser> getUserFromCodePhone(String code, String verificationID) async {
FirebaseAuth mAuth = FirebaseAuth.instance;
AuthCredential phoneAuthCredential = PhoneAuthProvider.getCredential(
verificationId: verificationID, smsCode: code);
try {
AuthResult result = await mAuth.signInWithCredential(phoneAuthCredential);
FirebaseUser currentUser = await mAuth.currentUser();
if (currentUser != null && result.user.uid == currentUser.uid) {
return currentUser;
} else {
return null;
}
} on PlatformException catch (_) {}
return null;
}
How it work ? : when use signInWithCredential method if code passed to
AuthCredential is false then the method will throw PlatformException
so out from try block and return null

Flutter : Show an Alert Dialog after an async Api call

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