TextFormField validator message does not appear properly - android

I have TextFormFields for email and password authentication. I want to display different messages depending on the error thrown by FireBaseAuthException, but the method I use to let the TextFormField know that there is an error is fundamentally flawed. Here's the code before I explain:
bool _mailError = false;
bool _passwordError = false;
String _errorMessage = 'default error message';
startAuth() async {
final validity = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (validity) {
_formKey.currentState.save();
submitForm(_email, _password);
}
}
submitForm(String email, String password) async {
final auth = FirebaseAuth.instance;
UserCredential userCredential;
try {
if (_isLoginPage) {
userCredential = await auth.signInWithEmailAndPassword(
email: email, password: password);
} else {
// for auth
userCredential = await auth.createUserWithEmailAndPassword(
email: email, password: password);
// for Firestore
String uid = userCredential.user.uid;
await FirebaseFirestore.instance.collection("users").doc(uid).set({
"email": email,
"password": password,
});
}
} on FirebaseAuthException catch (err) {
switch (err.code) {
case 'email-already-in-use':
_mailError = true;
_errorMessage = 'E-mail already in use';
break;
case 'user-not-found':
_errorMessage = 'User not found';
_mailError = true;
break;
.
.
.
}
print("zort");
print(err);
}
}
#override
Widget build(BuildContext context) {
return SizedBox(
.
.
.
TextFormField(
keyboardType: TextInputType.emailAddress,
key: const ValueKey("email"),
validator: (val) {
if (val.isEmpty || !val.contains("#")) {
return "Invalid e-Mail";
> } else if (_mailError) {
_mailError = false;
return _errorMessage;
}
return null;
},
.
.
.
Container(
padding: const EdgeInsets.all(5),
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
),
onPressed: () {
> startAuth();
},
child: _isLoginPage
? const Text("Log in")
: const Text("Sign up"),
),
),
Now here's what happens. For example, when an email of an already existing user is entered and the sign up button is pressed, the error with the code email-already-in-use gets thrown, _mailError gets set to true and _errorMessage gets set to User not found. But since _mailError is initially false, the if statement in the validator does not get executed on the first button press, so I have to press the button a second time, that is, when _mailError is true, for the if statement to get executed and _mailError to be set to false again. See the logical problem here? This also means that pressing it a third time removes the error, although it should remain on the screen.
I know, this is a very basic logic error. I feel pretty silly for even attempting this, but I don't know what other way I can use to let the TextFormField know that there is an error. What can I do?

Related

Sign in/up takes so long time to load from firebase

I'm new here and this is my first question and hopefully I get answers.
So, I'm building a flutter mobile application which is shows the home page first but if you click on profile icon you have to sign in to create profile and save your work.
The problem is when I try to sign in or register it takes 3-5 min to show the home page and that's extremely long.
Here's my auth page:
class AuthService{
final FirebaseAuth _auth = FirebaseAuth.instance;
//creat user obj based on FirebaseUser
UserModel? _userFromFirebaseUser (User? user){
return user != null ? UserModel(uid: user.uid) : null;
}
//sign up with email & password
Future signUp(String email, String password) async {
try{
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user);
}
on FirebaseAuthException catch (e){
Utils.showSnackBar(e.message, Colors.red);
}
}
//log in with email & password
Future logIn(String email, String password) async {
try{
UserCredential result = await _auth.signInWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user);
}
on FirebaseAuthException catch (e){
Utils.showSnackBar(e.message, Colors.red);
}
}
}
and this is the sign up button function:
ElevatedButton(
onPressed: () async {
final isValid = _formKey.currentState!.validate();
if(!isValid) return;
showDialog(
context: context,
builder: (context) => const Center(child: CircularProgressIndicator()),
);
await _auth.signUp(_emailCont.text.trim(), _passwordCont.text.trim());
navigatorKey.currentState!.popUntil((route) => route.isFirst);
},
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 138, vertical: 13),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30)),
),
child: const Text(
'Sign Up',
style: TextStyle(fontSize: 22, letterSpacing: 1.5),
),
),
...................................................................
Firebase is actually fast enough that a normal auth operation should execute very fast.
The only reason I could say it's the problem is the android emulator you're using on your machine. It's the issue with the recent versions of Android Emulator on the Windows platform.
so:
Change the emulator you're using to another emulator or try on a real device.
If the issue still occurs to you, downgrade the emulator version to an older version from here https://developer.android.com/studio/emulator_archive

App crashing if already one 'await' is active

I have an app for showing world times. I have a page for changing different locations around the world. It's a ListView.
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: colorOne,
appBar: AppBar(
backgroundColor: Colors.black,
title: Text("Change location"),
centerTitle: true,
elevation: 0.0,
),
body: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.fromLTRB(5, 10, 5, 0),
itemCount: locations.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
updateTime(index);
... rest code
As you can see, when I tap on ListTIle, it calls updateTime function
updateTime function:
void updateTime(index) async {
WorldTime instance = locations[index];
await instance.getTime();
Navigator.pop(context, {
"location": instance.location,
"flag": instance.flag,
"time": instance.time,
"date": instance.date,
"isDayTime": instance.isDayTime,
});
// obtain shared preferences
final savingLastLocation = await SharedPreferences.getInstance();
// set value
savingLastLocation.setString("location", instance.location);
savingLastLocation.setString("url", instance.url);
savingLastLocation.setString("flag", instance.flag);
}
If user starts spamming on tiles while awaiting for that function, app will either show full blank grey screen or drop red screen of death saying "boolean expression must be null".
How can I add some kind of loading screen/widget or prevent calling function again if it's already called once?
You can wrap your screen with IgnorePointer, which ignores any click.
Create bool variable.
bool ignore = false;
bool methodcalled = false; // new added line variable
Now wrap your scaffold with IgnorePointer.
return IgnorePointer(
ignoring: ignore,
child: Scaffold(
now, set ignore variable to true when user tap on any item.
onTap: () {
setState(() {
ignore = true;
});
updateTime(index).then((_){
setState(() {
ignore = false;
});
});
.... rest code
Add return in your method.
return 1
void updateTime(index) async {
if(!methodcalled){
methodcalled = !methodcalled;
}else{
return 0;
}
WorldTime instance = locations[index];
await instance.getTime();
Navigator.pop(context, {
"location": instance.location,
"flag": instance.flag,
"time": instance.time,
"date": instance.date,
"isDayTime": instance.isDayTime,
});
// obtain shared preferences
final savingLastLocation = await SharedPreferences.getInstance();
// set value
savingLastLocation.setString("location", instance.location);
savingLastLocation.setString("url", instance.url);
savingLastLocation.setString("flag", instance.flag);
methodcalled = !methodcalled; // added line
return 1; // added line
}
onPressed set like this
onPressed: () async {
dynamic result =
await Navigator.pushNamed(context, '/location');
if (result != null) {
setState(() {
data = {
'location': result['location'],
'flag': result['flag'],
'time': result['time'],
'isDateTime': result['isDateTime']
};
});
}
},

Sign up and signin with google and firebase not redirecting to destined page

Am working on a project that I was suppose to finished by next week latest, but am facing issue of not redirecting to destined page in flutter google and firebase sign up/sign in.
Here's my dependencies version:
google_sign_in: ^4.0.14
cloud_firestore: ^0.12.11
fluttertoast: ^3.1.3
shared_preferences: ^0.4.3
Here's my login code logic.
import 'package:shared_preferences/shared_preferences.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'home.dart';
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
final GoogleSignIn googleSignIn = new GoogleSignIn();
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
SharedPreferences preferences;
bool loading = false;
bool isLogedin = false;
#override
void initState() {
super.initState();
issignedIn();
}
void issignedIn() async {
setState(() {
loading = true;
});
preferences = await SharedPreferences.getInstance();
isLogedin = await googleSignIn.isSignedIn();
if (isLogedin) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
setState(() {
loading = false;
});
}
Future handleSignIn() async {
preferences = await SharedPreferences.getInstance();
setState(() {
loading = true;
});
GoogleSignInAccount googleUser = await googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication =
await googleUser.authentication;
AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: googleSignInAuthentication.idToken,
accessToken: googleSignInAuthentication.accessToken);
FirebaseUser firebaseUser =
(await firebaseAuth.signInWithCredential(credential));
if (firebaseUser != null) {
final QuerySnapshot result = await Firestore.instance
.collection("users")
.where("id", isEqualTo: firebaseUser.uid)
.getDocuments();
final List<DocumentSnapshot> documents = result.documents;
if (documents.length == 0) {
//Insert the user to our collection
Firestore.instance
.collection("users")
.document(firebaseUser.uid)
.setData({
"id": firebaseUser.uid,
"username": firebaseUser.displayName,
"email": firebaseUser.email,
"profilePicture": firebaseUser.photoUrl
});
await preferences.setString("id", firebaseUser.uid);
await preferences.setString("username", firebaseUser.displayName);
await preferences.setString("email", firebaseUser.email);
await preferences.setString("photoUrl", firebaseUser.displayName);
} else {
await preferences.setString("id", documents[0]['id']);
await preferences.setString("username", documents[0]['username']);
await preferences.setString("email", documents[0]['email']);
await preferences.setString("photoUrl", documents[0]['photoUrl']);
}
Fluttertoast.showToast(msg: "Logged in successfully");
setState(() {
loading = false;
});
} else {}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
elevation: 0.0,
backgroundColor: Colors.red,
centerTitle: true,
title: Text(
"Login",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
body: Stack(
children: <Widget>[
Center(
child: FlatButton(
color: Colors.red,
onPressed: () {
handleSignIn();
},
child: Text(
"Sign in/ Sign up with google",
style: TextStyle(color: Colors.white),
),
),
),
Visibility(
visible: loading ?? true,
child: Container(
alignment: Alignment.center,
color: Colors.white.withOpacity(0.9),
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
),
),
),
)
],
),
);
}
}
It keeps loading like forever. May be am not doing something right. A help with the code is highly appreciated.
Please, help out.
enter image description here
Just check quickly that the build.gradle file edits were made in the correct file as there are two of them and i've seen that mistake often. Also there is a problem with some android x devices as covered here that could cause this.
1) check the indentation of imports at the head of the page and make sure you have included import 'package:flutter/material.dart';.
2) ~line 50 ish: FirebaseUser firebaseUser =(await firebaseAuth.signInWithCredential(credential));
you may want to try replacing with: FirebaseUser firebaseUser = (await firebaseAuth.signInWithCredential(credential)).user;
3) ... will run a test later if i have time ( ゚ヮ゚)
Later => with the change above, this code does work for me. I loaded it into one of my firebase projects and it logs me in. It does not redirect to home as you have not called issignedIn() in the button flow. You could add it in after Fluttertoast.showToast(msg: "Logged in successfully"); replacing setState((){...}); but i would recommend changing this function to a onAuthStateChange method if you are comfortable using listeners. If you still cant get it working, post the content of you build.gradle files and the pubspec.yaml and any errors from the debug console.
and just as a note: adding try, catch logic or then.error with asynchronous functions is recommended, especially when using networks here. I see people ignore it for prototyping but it can really help with Firebase custom error states
I made this video showing how to get firebase auth setup with flutter. It's a bit of a different code structure than what you posted but I think more flexible if you were interested in refactoring. https://youtu.be/iTYD13w6Duo

How do I pass user input data from page to page in Flutter?

I am writing an app in Flutter and am trying to do a 2 page sign up process. Page 1 is their email, password and repeat password, and Page 2 is additional details about them for their account. This is part of a personal project.
I am trying to pass the data from the first sign up page, to the second. Once the user fills out the second page and presses sign up. The data is then collated and a FirebaseUser is created in Authentication and in the FireStore database.
a) Is this the right way to do it? AKA passing data from one page to the other. Then completing signup then, but if a user exists before completing second page then they have not created an account.
b) Should I instead just be adding information on the second page to the account created on the first? To me this makes sense, but I'm thinking in terms of usability, a user who doesn't complete the full sign up process, likely did not want an account set up for them.
I have tried countless tutorials on passing data from one page to another, however I always get errors relating to invalid constructor names, to const errors, or I go down a rabbit hole of just creating new objects and passing things along.
Signup.dart (Page 1)
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(email: _email, password: _password)
.then((user) => {
Firestore.instance.collection('users').document(user.user.uid).setData({"email": _email, "password": _password}),
});
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => ExtraSignUpInfo()));
ExtraSignUpInfo.dart (Page 2)
class ExtraSignUpInfo extends StatefulWidget {
#override
_ExtraSignUpInfoState createState() => _ExtraSignUpInfoState();
}
class _ExtraSignUpInfoState extends State<ExtraSignUpInfo> {
String _name;
String _company;
String _jobTitle;
String _teamName;
String _industry;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
I want the user just created to be sent to ExtraSignUpInfo() page, so then the email and password can be created later after ExtraSignUpInfo() page form fields are filled in.
you can try to pass the parameters using arguments and sending it with the named route call to the navigator, see this example on cookbook:
https://flutter.dev/docs/cookbook/navigation/navigate-with-arguments
1) In the second screen you have to create the ScreenArguments class like this:
class ScreenArguments {
final String email;
final String password;
ScreenArguments(this.email, this.password);
}
2) Initiate the vars on second screen itself:
String email;
String password;
3) Call the navigator from a button(for example) on the first screen sending the values:
Navigator.pushNamed(context, "/secondScreen", arguments: email, password)
*Add the named route to your main.dart for this to work.
4) Use the values sent from screen1 to screen2.
Hope it helps.
You could also try using a stepper widget where you collect the email, password, etc. on successive steps in sort of a form "wizard." There are many variants (Google 'stepper widget').
Here is a very basic setup adding a TextFormField you can use and add validation to:
import 'package:flutter/material.dart';
class StepperForm extends StatefulWidget {
static Future<void> show(BuildContext context) async {}
#override
_StepperFormState createState() => _StepperFormState();
}
class _StepperFormState extends State<StepperForm> {
///Stepper variables and functions
//declare the currentStep (starting point) as an int 0
int _currentStep = 0;
//Create a list of steps. Use TextFormFields for the email/password. Add validation if needed.
List<Step> _myStepperForm() {
List<Step> _steps = [
Step(
title: Text("Enter Your Email"),
//state: StepState.complete,
isActive: _currentStep >= 0,
content: TextFormField(
decoration: InputDecoration(
labelText: 'Email',
suffixIcon: Icon(Icons.email),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
validator: (value) =>
value.isNotEmpty ? null : 'email can\'t be empty',
//Additional validation code as needed
),
),
Step(
title: Text("Second"),
isActive: _currentStep >= 1,
content: Text("My Second Example"),
),
Step(
title: Text("Third"),
isActive: _currentStep >= 2,
content: Text("My Third Example"),
),
Step(
title: Text("Fourth"),
isActive: _currentStep >= 3,
content: Text("My Fourth Example"),
),
];
return _steps;
}
//Create function for continue button
onStepContinue() {
setState(() {
if (this._currentStep < this._myStepperForm().length - 1) {
this._currentStep = this._currentStep + 1;
} else {
//Completion Code
print('The form is complete.');
}
});
}
//create cancel function
onStepCancel() {
setState(() {
if (this._currentStep > 0) {
this._currentStep = this._currentStep - 1;
} else {
this._currentStep = 0;
}
});
}
//Create the Stepper Widget
Widget _stepperWidget() => Container(
margin: EdgeInsets.only(top: 10),
color: Colors.orangeAccent,
child: Stepper(
//type: StepperType.horizontal,
currentStep: this._currentStep,
steps: _myStepperForm(),
onStepCancel: onStepCancel,
onStepContinue: onStepContinue,
onStepTapped: (step) {
setState(() {
this._currentStep = step;
});
},
),
);
//Call Stepper Function in Scaffold. SingleChildScrollView helps with different screen sizes
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('My Stepper Form'),
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
_stepperWidget(),
SizedBox(height: 600)
],
),
),
);
}
}

Flutter - Async Validator of TextFormField

In my app the user has to insert a name in the textformfield. While the user is writing a query should be made to the database, that controls if the name already exists. This query returns the number of how many times the name exists. Till now I am able to make it when I press a button.
This is the function that returns the count of the names:
checkRecipe(String name) async{
await db.create();
int count = await db.checkRecipe(name);
print("Count: "+count.toString());
if(count > 0) return "Exists";
}
And this is the TextFormField, which should be validated async:
TextField(
controller: recipeDescription,
decoration: InputDecoration(
hintText: "Beschreibe dein Rezept..."
),
keyboardType: TextInputType.multiline,
maxLines: null,
maxLength: 75,
validator: (text) async{ //Returns an error
int count = await checkRecipe(text);
if (count > 0) return "Exists";
},
)
The error of the code is:
The argument type Future can't be assigned to the parameter type
String
I do know what the error means. But I do not know how to work around could look like. It would be awesome if somebody could help me.
I have found a solution.
My Code looks now like this:
//My TextFormField validator
validator: (value) => checkRecipe(value) ? "Name already taken" : null,
//the function
checkRecipe<bool>(String name) {
bool _recExist = false;
db.create().then((nothing){
db.checkRecipe(name).then((val){
if(val > 0) {
setState(() {
_recExist = true;
});
} else {
setState(() {
_recExist = false;
});
}
});
});
return _recExist;
}
Perhaps you could run your async check using the onChange handler and set a local variable to store the result.
Something like:
TextFormField(
controller: recipeDescription,
decoration: InputDecoration(hintText: "Beschreibe dein Rezept..."),
keyboardType: TextInputType.multiline,
maxLines: null,
maxLength: 75,
onChanged: (text) async {
final check = await checkRecipe(text);
setState(() => hasRecipe = check);
},
validator: (_) => (hasRecipe) ? "Exists" : null,
)
I wanted a same behavior for one of our apps and ended up writing a widget (which I recently published to pub.dev).
AsyncTextFormField(
controller: controller,
validationDebounce: Duration(milliseconds: 500),
validator: isValidPasscode,
hintText: 'Enter the Passcode')
You can pass in a Future<bool> function for the validator and set an interval before the text is sent to server.
The code is available on github.

Categories

Resources