I try to implement Firebase authentication in my mobile app. (I am very new to this..)
I have the following code which attempts to create the user for the first time:
class WpAuthService {
FirebaseAuth _auth = FirebaseAuth.instance;
Stream<WpUser> get wpUser {
return _auth.authStateChanges().map((User firebaseUser) =>
(firebaseUser != null) ? WpUser(uid: firebaseUser.uid) : null);
}
Future<String> createUserWithEmail(email, password) async {
UserCredential userCredential;
try {
userCredential = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
} on FirebaseAuthException catch (e) {
print(e.code + " - " + e.message);
return e.message;
}
return 'SUCCESS';
}
}
And in another file, I am trying to call the createUserWithEmail function as following:
class SignupForm extends StatefulWidget {
#override
_SignupFormState createState() => _SignupFormState();
}
class _SignupFormState extends State<SignupForm> {
final _formKey = GlobalKey<FormState>();
var _userEmail = "";
var _userPassword = "";
String _opResult;
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter a valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(labelText: 'Email address'),
onSaved: (value) {
_userEmail = value;
},
),
TextFormField(
key: ValueKey('password'),
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 characters long.';
}
return null;
},
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onSaved: (value) {
_userPassword = value;
},
),
SizedBox(height: 12),
RaisedButton(
child: Text('Sign up',
style: Theme.of(context).textTheme.headline6),
onPressed: _trySubmit,
),
],
),
),
),
),
),
);
}
}
When I run the above piece of code, it prints out the following:
I/flutter ( 4032): MESSAGE:
I/flutter ( 4032): null
════════ Exception caught by gesture ═══════════════════════════════════════════
The following assertion was thrown while handling a gesture:
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 370 pos 10: 'data != null'
Looking at some other examples, I expect my implementation to work but something is seemingly wrong. How can I use the return value as String from the createUserWithEmail function?
by default your _opResult variable is null and your passing it to the Text widget of the Snackbar which throws that assertion.
You need either to first wait for the response to return or change your code to be inside the then method.
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
WpAuthService()
.createUserWithEmail(_userEmail, _userPassword)
.then((value) {
setState(() {
_opResult = value;
});
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
});
}
}
Your _opResult is null. And it is not equal to 'SUCCESS'. And you are trying to set it into Text() widget. Text widget requires a string parameter, not null.
You can set a default string when initializing the _opResult. Like this:
String _opResult = "";
print('MESSAGE:');
print(_opResult);
if (_opResult != 'SUCCESS') {
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(_opResult),
backgroundColor: Theme.of(context).errorColor,
),
);
}
Solved.
In the createUserWithEmail function, I was returning String values as the following:
return e.message;
return 'SUCCESS';
I updated it so that the function now returns as stated below:
return Future.value(e.message);
return Future.value('SUCCESS');
Everything else is the same and it worked.
But I saw some other examples where people were just returning String values from their functions. My problem is solved but is this behavior really expected? You are more than welcome to educate me.
Related
I have made a online store app with flutter and backend on firebase , in sign up page whenever click on create account button it says same error, no matter if I put data on Text Fields or not, I am really confused about this!
the error is
given String is empty or null
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../widgets/custom_button.dart';
import '../widgets/custom_input.dart';
import 'const.dart';
import 'login.dart';
class SingUP extends StatefulWidget {
SingUP({Key? key}) : super(key: key);
#override
State<SingUP> createState() => _SingUPState();
}
class _SingUPState extends State<SingUP> {
Future<void> advanceAlertDialog(String message) {
return Get.defaultDialog(
title: 'Invalid Input',
content: Text(message),
actions: [
ElevatedButton(
onPressed: () => Get.back(),
child: Text('OK !'),
)
],
contentPadding: EdgeInsets.all(15.0),
);
}
Future<String?> signInMethod() async {
try {
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: takeEmail, password: takePassword);
return 'ok';
} on FirebaseAuthException catch (error) {
if (error.code == 'weak-password') {
return 'the password is too week';
} else if (error.code == 'email-already-in-use') {
return 'The account with this email is already exist !';
}
return error.message;
} catch (error) {
return error.toString();
}
}
void signIn() async {
setState(() {
isLoading = true;
});
String? result = await signInMethod();
if (result != 'ok') {
advanceAlertDialog(result!);
setState(() {
isLoading = false;
});
} else {
setState(() {
isLoading = false;
});
Get.to(LoginPage());
Get.snackbar('Successful', 'Account Created !',
backgroundColor: Colors.greenAccent, icon: Icon(Icons.check_circle));
}
}
bool isLoading = false;
String takeEmail = '';
String takePassword = '';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.only(top: 50),
child: Text(
'Create a New Account !',
style: Constans.headingTextStyle,
textAlign: TextAlign.center,
),
),
Column(
children: [
CustomInput(
hintText: 'Enter Your Email ...',
onChanged: (value) {
takeEmail = value;
},
checkPassword: false,
),
CustomInput(
hintText: 'Enter Your Password ...',
onChanged: (value) {
takeEmail = value;
},
checkPassword: true,
),
CustomButton(
text: 'Create Account',
loading: isLoading,
onTap: () {
signIn();
},
mode: false,
)
],
),
CustomButton(
text: 'Back to Login',
onTap: () => Get.back(),
mode: true,
loading: isLoading,
)
],
),
),
);
}
}
Emain and password widgets are the same takeEmail = value;, change the password onChange value to takePassword = value;, also where is exactly the error thrown?. I advice to add breakpoints to signInMethod() to see the input values of email: takeEmail, password: takePassword (both of this strings) and after the on FirebaseAuthException catch (error) { to see if the error is thrown from firebase or is in your code.
I am registering a user in firebase and each time I try to register it shows me the mentioned error and donot send credentials to the firebase. Although it is getting the credentials from the firebase for login but shows error while storing values in firebase. Below is the code for only register that is getting the email address and password. I have another question that like password and email how could I store other details in firebase e.g Age, Gender etc. Kindly help me go through this.
class _ClientRegistrationScreenState extends State<ClientRegistrationScreen> {
bool showSpinner = false;
final _auth = FirebaseAuth.instance;
File image;
//final ImagePicker _picker = ImagePicker();
String password;
String confirmPassword;
String email;
String name;
bool _passwordVisible = false;
bool _confirmPasswordVisible = false;
#override
void initState() {
_passwordVisible = false;
_confirmPasswordVisible = false;
}
final _formKey = GlobalKey<FormState>();
Expanded(
child: Center(
child: TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return '*Email Address Required';
}
return null;
},
),
),
),
),
Expanded(
child: Center(
child: TextFormField(
onChanged: (value){
password = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
return '*Password Required';
}
if (password != confirmPassword) {
return 'Password Donot Match';
}
return null;
},
onPressed: () {
setState(() {
_passwordVisible = !_passwordVisible;
});
},
),
),
),
),
),
Expanded(
child: Center(
child: TextFormField(
onChanged: (value){
confirmPassword = value;
},
validator: (value) {
if (value == null || value.isEmpty) {
return '*Confirm Password';
}
if (password != confirmPassword) {
return 'Password Donot Match';
}
return null;
},
onPressed: () {
setState(() {
_confirmPasswordVisible = !_confirmPasswordVisible;
});
},
),
),
),
),
),
RoundedButton(
colour: Colors.yellow,
opColour: Colors.yellow.withOpacity(0.2),
title: 'Register',
buttonTextColor: Colors.black,
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() {
showSpinner = true;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
try {
final newUser = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
if(newUser!=null){
Navigator.pushNamed(context, MainEmployeeScreen.id);
print(name);
}
setState(() {
showSpinner = false;
});
}
catch(e){
print(e);
}
}
),
Did you initialize firebase in main function?
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
I am doing login and sign up using firebase. I have two error. First is the error message from firebase that did not show at the snack bar. The second is after I add StreamBuilder my apps become black screen.
Does anyone know how to solve this question? Thanks in advance.
Here is the code:
Main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
return HomePage();
}
return AuthScreen();
}),
);
}
}
AuthScreen.dart
class AuthScreen extends StatefulWidget {
#override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = FirebaseAuth.instance;
var _isLoading = false;
void _submitAuthForm(
String email,
String password,
String username,
bool isLogin,
BuildContext ctx,
) async {
UserCredential authResult;
try {
setState(() {
_isLoading = true;
});
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await FirebaseFirestore.instance
.collection('users')
.doc(authResult.user.uid)
.set({
'username': username,
'email': email,
});
}
} on PlatformException catch (err) {
var message = 'An error occurred, pelase check your credentials!';
if (err.message != null) {
message = err.message;
}
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: Theme.of(ctx).errorColor,
),
);
setState(() {
_isLoading = false;
});
} catch (err) {
print(err);
setState(() {
_isLoading = false;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).primaryColor,
body: AuthForm(
_submitAuthForm,
_isLoading,
),
);
}
}
Auth_form.dart
class AuthForm extends StatefulWidget {
AuthForm(
this.submitFn,
this.isLoading,
);
final bool isLoading;
final void Function(
String email,
String password,
String userName,
bool isLogin,
BuildContext ctx,
) submitFn;
#override
_AuthFormState createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
final _formKey = GlobalKey<FormState>();
var _isLogin = true;
var _userEmail = '';
var _userName = '';
var _userPassword = '';
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
widget.submitFn(_userEmail.trim(), _userPassword.trim(), _userName.trim(),
_isLogin, context);
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: ValueKey('email'),
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter a valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email address',
),
onSaved: (value) {
_userEmail = value;
},
),
if (!_isLogin)
TextFormField(
key: ValueKey('username'),
validator: (value) {
if (value.isEmpty || value.length < 4) {
return 'Please enter at least 4 characters';
}
return null;
},
decoration: InputDecoration(labelText: 'Username'),
onSaved: (value) {
_userName = value;
},
),
TextFormField(
key: ValueKey('password'),
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 characters long.';
}
return null;
},
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
onSaved: (value) {
_userPassword = value;
},
),
SizedBox(height: 12),
if (widget.isLoading) CircularProgressIndicator(),
if (!widget.isLoading)
RaisedButton(
child: Text(_isLogin ? 'Login' : 'Signup'),
onPressed: _trySubmit,
),
if (!widget.isLoading)
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Text(_isLogin
? 'Create new account'
: 'I already have an account'),
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
)
],
),
),
),
),
),
);
}
}
home.dart
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Container();
}
}
To show a SnackBar first define a key:
final key = GlobalKey<ScaffoldMessengerState>();
Then, add a ScaffoldMessenger to your widget tree:
ScaffoldMessenger(
key: key, // assign the key property to your previously created key
child: Scaffold(...),
)
Now you can show the SnackBar:
key.currentState.showSnackBar(SnackBar(...))
Please add code for your home page, so we can inspect it and see why it shows a black screen, you probably forgot to wrap it in a Scaffold. You should return a Scaffold in the home page and the black screen will go away.
I'm having troubles with creating users in my app, I get an error that says I don't have sufficient permissions even though I should have permission. My security rules are allowing users to be created so I don't really understand why I am getting this error. What's even more strange is that it works on my mates computer, he can create users (with the exact same userdetails). He is on a pc and I on a mac not sure if that matters?
The error I get is the following:
[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: PlatformException(Error 7, FIRFirestoreErrorDomain, Missing or insufficient permissions.)
Our security rules are:
service cloud.firestore {
//match /databases/{database}/documents {
match /databases/{database}/documents {
match /{document=**} {
allow read: if request.auth != null;
}
match /Users/{userId} {
allow update: if request.auth.uid == userId;
}
match /Users/{document=**} {
allow create;
}
match /Recipes/{document=**} {
allow create: if request.auth != null;
allow update, delete: if request.resource.data.userId == request.auth.uid;
}
match /Recipes/{document=**}/newRatings {
allow create, update: if request.auth != null;
}
}
} ```
Our code for registering users is:
´´´ import 'package:cibus/pages/loginScreens/username_screen.dart';
import 'package:cibus/pages/loginScreens/verify_screen.dart';
import 'package:cibus/services/colors.dart';
import 'package:flutter/material.dart';
import 'package:cibus/services/login/auth.dart';
import 'package:cibus/services/constants.dart';
import 'package:cibus/pages/loginScreens/e-sign_in_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_user_stream/firebase_user_stream.dart';
import 'package:cibus/services/my_page_view.dart';
import 'package:provider/provider.dart';
import 'package:cibus/services/login/user.dart';
import 'package:cibus/services/database.dart';
import 'package:cibus/pages/loading_screen.dart';
const registerButtonColor = kTurquoise;
const formSizedBox = SizedBox(height: 20.0);
const EdgeInsets formPadding =
EdgeInsets.symmetric(vertical: 10.0, horizontal: 50.0);
const TextStyle textStyleErrorMessage =
TextStyle(color: Colors.red, fontSize: 14.0);
const TextStyle textStyleRegisterButton = TextStyle(color: Colors.white);
OutlineInputBorder textInputBorder = OutlineInputBorder(
borderRadius: BorderRadius.circular(25.0),
);
class RegisterScreen extends StatefulWidget {
final Function toggleView;
RegisterScreen({this.toggleView});
#override
_RegisterScreenState createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
final AuthService _auth = AuthService();
final _formKey = GlobalKey<FormState>();
bool loading = false;
bool isVerified = false;
//final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
//text field state
String email = '';
String password = '';
String error = '';
String name = '';
String description = '';
String _currentUsername;
int age = 0;
int dropdownValue = null;
final TextEditingController _pass = TextEditingController();
final TextEditingController _confirmPass = TextEditingController();
#override
Widget build(BuildContext context) {
return loading
? LoadingScreen()
: Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
appBar: AppBar(
backgroundColor: Theme.of(context).backgroundColor,
elevation: 0.0,
title: Text('Sign up to Cibus', style: TextStyle(color: kCoral)),
actions: <Widget>[
FlatButton.icon(
icon: Icon(Icons.person, color: kCoral),
label: Text('Sign in', style: TextStyle(color: kCoral)),
onPressed: () {
//widget.toggleView();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return EmailSignIn();
},
),
);
},
),
],
),
body: Padding(
padding: formPadding,
child: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(children: <Widget>[
formSizedBox,
TextFormField(
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Email',
),
validator: (val) =>
val.isEmpty ? 'Enter an email' : null,
onChanged: (val) {
setState(() => email = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Password',
),
obscureText: true,
controller: _pass,
validator: (String val) {
Pattern pattern =
r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!##\$&*~]).{8,}$';
RegExp regex = new RegExp(pattern);
print(val);
if (val.isEmpty) {
return 'Please enter password';
} else if (val.length < 8) {
return 'Minimum 8 characters required';
} else if (!val.contains(RegExp(r'[A-Z]'))) {
return 'One upper case letter required.';
} else if (!val.contains(RegExp(r'[a-z]'))) {
return 'One lower case letter required.';
} else if (!val.contains(RegExp(r'[0-9]'))) {
return 'One digit required.';
} else if (!val
.contains(RegExp(r'[!##$%^&*(),.?":{}|<>]'))) {
return 'One special character required.';
} /*
else {
if (!regex.hasMatch(val))
return 'Enter valid password: \n'
'Password must contain at least one upper case letter. \n'
'Password must contain at least one lower case letter. \n'
'Password must contain at least one digit. \n'
'Password must contain at least one special character.'; */
else
return null;
//}
},
onChanged: (val) {
setState(() => password = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: ' Re-enter Password',
),
obscureText: true,
controller: _confirmPass,
validator: (String val) {
if (val.isEmpty)
return 'Re-enter password field is empty';
if (val != _pass.text)
return 'passwords do not match';
return null;
},
onChanged: (val) {
setState(() => password = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Name',
),
validator: (val) =>
val.isEmpty ? 'Enter your name' : null,
onChanged: (val) {
setState(() => name = val);
},
),
formSizedBox,
TextFormField(
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Description',
),
minLines: 5,
maxLines: 10,
validator: (val) =>
val.isEmpty ? 'Enter your description' : null,
onChanged: (val) {
setState(() => description = val);
},
),
formSizedBox,
TextFormField(
maxLength: 20,
decoration: InputDecoration(
enabledBorder: textInputBorder,
border: textInputBorder,
labelText: 'Username',
),
validator: (val) {
if (val.length < 3)
return 'Username must be more than 2 characters';
/*else if (checkUsername == false)
return 'Username is allready taken';*/
return null;
},
onChanged: (val) {
setState(() {
_currentUsername = val;
print(_currentUsername);
});
}),
formSizedBox,
RaisedButton(
color: kCoral,
child: Text('Register', style: textStyleRegisterButton),
onPressed: () async {
if (_formKey.currentState.validate()) {
setState(() => loading = true);
bool isUsernameFree = await DatabaseService()
.isUsernameTaken(username: _currentUsername);
print(' checkUsername: $isUsernameFree');
if (!isUsernameFree) {
dynamic result =
await _auth.registerWithEmailAndPassword(
email, password, name, description, age);
if (result == null) {
setState(() {
error = 'Email is already registered';
_verificationEmailDialog();
});
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) {
return VerifyScreen();
},
),
);
}
} else {
setState(() {
error = 'Username is already taken';
});
_usernameDialog();
}
}
},
),
Text(
error,
style: textStyleErrorMessage,
),
])),
),
));
}
Future<void> _usernameDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Username is already taken'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'Unfortunately it seems like your username is allready taken. Please try another one'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Aight bruh'),
onPressed: () {
setState(() {
loading = false;
});
Navigator.of(context)
.pop(); //TODO: When popping try to keep text in forms
},
),
],
);
},
);
}
Future<void> _verificationEmailDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Text('Email is already in use'),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text(
'Unfortunately it seems like the email is already in use. Please try another one'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Aight bruh'),
onPressed: () {
setState(() {
loading = false;
});
Navigator.of(context)
.pop(); //TODO: When popping try to keep text in forms
},
),
],
);
},
);
}
}
and here is the registerWithEmailAndPassword function:
Future registerWithEmailAndPassword(String email, String password,
String name, String description, int age) async {
try {
AuthResult result = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
user.sendEmailVerification();
user.isEmailVerified;
print('Email verification sent?');
//create a new document for the user with the uid
await DatabaseService(uid: user.uid)
.updateUserData(name: name, description: description, age: age);
await DatabaseService(uid: user.uid).updateUserPicture(
pictureURL:
'https://firebasestorage.googleapis.com/v0/b/independent-project-7edde.appspot.com/o/blank_profile_picture.png?alt=media&token=49efb712-d543-40ca-8e33-8c0fdb029ea5');
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
It was a mistake on my part with the Security rules. I had that users could create users but they could only read if they were authenticated, so when I checked if username was taken the error occurred since I did not have the read permissions. I just added the read part on users and it works
match /Users/{document=**} {
allow create: if true;
allow read: if true;
}
´´´
new TextFormField(
validator: (value) async{
if (value.isEmpty) {
return 'Username is required.';
}
if (await checkUser()) {
return 'Username is already taken.';
}
},
controller: userNameController,
decoration: InputDecoration(hintText: 'Username'),
),
I have a form for user, and I want to check if the user already exists in the firestore datebase.
Future checkUser() async {
var user = await Firestore.instance
.collection('users')
.document(userNameController.text)
.get();
return user.exists;
}
This is my function to check if the user document already exists in the database.
But validator gives me this error.
[dart] The argument type '(String) → Future' can't be assigned to the parameter type '(String) → String'.
How should I fix this issue?
At this time I think that you can't associate a Future to a validator.
What you can do is this verifying the data on a button click or in another way and set the state on the validator response var.
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
body: Form(
key: _formKey,
child: Column(children: [
new TextFormField(
validator: (value) {
return usernameValidator;
},
decoration: InputDecoration(hintText: 'Username')),
RaisedButton(
onPressed: () async {
var response = await checkUser();
setState(() {
this.usernameValidator = response;
});
if (_formKey.currentState.validate()) {}
},
child: Text('Submit'),
)
])));
}
I needed to do this for username validation recently (to check if a username already exists in firebase) and this is how I achieved async validation on a TextFormField ( without installation of any additional packages). I have a "users" collection where the document name is the unique username ( Firebase can't have duplicate document names in a collection but watch out for case sensitivity)
//In my state class
class _MyFormState extends State<MyForm> {
final _usernameFormFieldKey = GlobalKey<FormFieldState>();
//Create a focus node
FocusNode _usernameFocusNode;
//Create a controller
final TextEditingController _usernameController = new TextEditingController();
bool _isUsernameTaken = false;
String _usernameErrorString;
#override
void initState() {
super.initState();
_usernameFocusNode = FocusNode();
//set up focus node listeners
_usernameFocusNode.addListener(_onUsernameFocusChange);
}
#override
void dispose() {
_usernameFocusNode.dispose();
_usernameController.dispose();
super.dispose();
}
}
Then in my TextFormField widget
TextFormField(
keyboardType: TextInputType.text,
focusNode: _usernameFocusNode,
textInputAction: TextInputAction.next,
controller: _usernameController,
key: _usernameFormFieldKey,
onEditingComplete: _usernameEditingComplete,
validator: (value) => _isUsernameTaken ? "Username already taken" : _usernameErrorString,)
Listen for focus changes on the widget i.e when it loses focus. You can also do something similar for "onEditingComplete" method
void _onUsernameFocusChange() {
if (!_usernameFocusNode.hasFocus) {
String message = UsernameValidator.validate(_usernameController.text.trim());
//First make sure username is in valid format, if it is then check firebase
if (message == null) {
Firestore.instance.collection("my_users").document(_usernameController.text.trim()).get().then((doc) {
if (doc.exists) {
setState(() {
_isUsernameTaken = true;
_usernameErrorString = null;
});
} else {
setState(() {
_isUsernameTaken = false;
_usernameErrorString = null;
});
}
_usernameFormFieldKey.currentState.validate();
}).catchError((onError) {
setState(() {
_isUsernameTaken = false;
_usernameErrorString = "Having trouble verifying username. Please try again";
});
_usernameFormFieldKey.currentState.validate();
});
} else {
setState(() {
_usernameErrorString = message;
});
_usernameFormFieldKey.currentState.validate();
}
}
}
For completeness, this is my username validator class
class UsernameValidator {
static String validate(String value) {
final regexUsername = RegExp(r"^[a-zA-Z0-9_]{3,20}$");
String trimmedValue = value.trim();
if (trimmedValue.isEmpty) {
return "Username can't be empty";
}
if (trimmedValue.length < 3) {
return "Username min is 3 characters";
}
if (!regexUsername.hasMatch(trimmedValue)) {
return "Usernames should be a maximum of 20 characters with letters, numbers or underscores only. Thanks!";
}
return null;
}
}
I had the same problem while using Firebase's Realtime Database but I found a pretty good solution similar to Zroq's solution. This function creates a simple popup form to have the user input a name. Essentially, I was trying to see if a particular name for a specific user was already in the database and show a validation error if true. I created a local variable called 'duplicate' that is changed anytime the user clicks the ok button to finish. Then I can call the validator again if there is an error, and the validator will display it.
void add(BuildContext context, String email) {
String _name;
bool duplicate = false;
showDialog(
context: context,
builder: (_) {
final key = GlobalKey<FormState>();
return GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(new FocusNode()),
child: AlertDialog(
title: Text("Add a Workspace"),
content: Form(
key: key,
child: TextFormField(
autocorrect: true,
autofocus: false,
decoration: const InputDecoration(
labelText: 'Title',
),
enableInteractiveSelection: true,
textCapitalization: TextCapitalization.sentences,
onSaved: (value) => _name = value.trim(),
validator: (value) {
final validCharacters =
RegExp(r'^[a-zA-Z0-9]+( [a-zA-Z0-9]+)*$');
if (!validCharacters.hasMatch(value.trim())) {
return 'Alphanumeric characters only.';
} else if (duplicate) {
return 'Workspace already exists for this user';
}
return null;
},
),
),
actions: <Widget>[
FlatButton(
child: const Text("Ok"),
onPressed: () async {
duplicate = false;
if (key.currentState.validate()) {
key.currentState.save();
if (await addToDatabase(_name, email) == false) {
duplicate = true;
key.currentState.validate();
} else {
Navigator.of(context).pop(true);
}
}
},
),
FlatButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop(false);
},
),
],
),
);
});
}