I am trying to validate uniqe username for SignUp Page.
Here is my TextFormField code:
TextFormField(
onSaved: (deger) => _username = deger!,
textInputAction: TextInputAction.done,
keyboardType: TextInputType.name,
controller: usernameController,
decoration: const InputDecoration(
suffixIcon: Icon(Icons.person),
label: Text("username"),
),
),
Here is my Button:
ElevatedButton(
onPressed: () async {
final valid = await _checkUserName(
usernameController.text);
if (valid!) {
Get.snackbar(
"hata", "username exist");
} else if (formKey.currentState!.validate()) {
formKey.currentState!.save();
//myAuthController codes here it doesnt metter.
} else {
debugPrint("error");
}
},
child: const Text("SIGNUP"),
),
My function for validate existed username in Firestore:
Future<bool?> _checkKullaniciAdi(String username) async {
final result = await FirebaseFirestore.instance
.collection("customer")
.where('username', isEqualTo: username)
.get();
return result.docs.isEmpty;
}
This codes are always returning
Get.snackbar("hata", "username exist");
What can I do ?
if (valid!) {
Get.snackbar("hata", "username exist");
}
if the username is valid, show hata username exists
That's what the above code says. This is more likely what you wanted:
if (!valid!) {
Get.snackbar("hata", "username exist");
}
The first ! means not, so if it is not valid, the second ! means that valid can't be null, if valid is null, it will throw an error.
Finally, the reason why we have to add the second ! is because your _checkKullaniciAdi method returns bool? so it can return true, false or null. But it doesn't return null, you marked it as a possibility, but it never happens, you could change the return type to Future<bool> and that way you would be able to remove the second !
Future<bool> _checkKullaniciAdi(String username) {
}
if (!valid) {
}
Related
I tried to change the password in app with flutter (Firebase) but it give me and error Another exception was thrown: Null check operator used on a null value. so can someone help me fix this one.
do I need to add something to check null or remove something.
class ChangingPasswordScreen extends StatefulWidget {
static const routeName = '/ChangingPasswordScreen';
//late final String? email;
//final User user;
//ChangingPasswordScreen({required this.user});
#override
State<StatefulWidget> createState() {
return _ChangingPasswordState();
}
}
class _ChangingPasswordState extends State<ChangingPasswordScreen> {
final _formKey = GlobalKey<FormState>();
var newPassword = " ";
final newPasswordController = TextEditingController();
#override
void dispose() {
newPasswordController.dispose();
super.dispose();
}
final currentUser = FirebaseAuth.instance.currentUser;
changePassword() async {
try {
await currentUser!.updatePassword(newPassword);
FirebaseAuth.instance.signOut();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SigninScreen(),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Your Password has been changed'),
),
);
} catch (error) {}
}
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
setState(() {
newPassword = newPasswordController.text;
});
changePassword();
}
},
child: Text(
'Change Password',
style: TextStyle(fontSize: 18.0),
),
),
In,
await currentUser!.updatePassword(newPassword);
make sure that currentUser is not null before using !. ! indicate that the variable will never be null.
Try something like this,
if(currentUser != null){
try {
await currentUser!.updatePassword(newPassword);
FirebaseAuth.instance.signOut();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => SigninScreen(),
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Your Password has been changed'),
),
);
} catch (error) {
print("Error thrown : $error");
}
}
Note: Never leave a catch block empty. Log the error while debugging. Handle the error properly. If you leave a catch block empty, you'll never know if it throws an Exception or not.
I'm logging out of the application with authentication, but I still get uid null error
This is my signOut method
Future<bool> signOut() async {
try {
await _auth.signOut();
_user = null;
return true;
} catch (e) {
return false;
}
}
This is my widget.I can log out successfully, but when switching to the next page it gives uid error and switches
actions: <Widget>[
PopupMenuButton<String>(
onSelected: (value) async {
switch (value) {
case 'Çıkış Yap':
//problem here
var provider = Provider.of<AuthServices>(context, listen: false);
await provider.signOut();
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => GirisEkrani()));
break;
case 'Profil Ayarları':
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProfilGuncellemeEkrani()));
break;
}
},
itemBuilder: (BuildContext context) {
return {'Profil Ayarları', 'Çıkış Yap'}.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
),
],
Error
The getter 'uid' was called on null.
Receiver: null
Tried calling: uid
It looks like you're using the user's uid in the GirisEkrani widget.
You should remove any reference to the uid in there since the user is already signed out and you've set the _user variable in your provider to null.
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.
dynamic result in this method should return a 'null' from the signInWithEmail method but returned a Future instead. Hence, it does not go to the if statement.
Sign in class
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
color: Colors.teal,
textColor: Colors.white,
child: Text("Login"),
onPressed: () async {
if(_formKey.currentState.validate()){
dynamic result = _auth.signInWithEmail(email, password);
if(result == null){
setState(() {
error = 'Email and Password does not match';
});
}
}
}
),
Here is the signInWithEmail method. I tried to print the something in the catch to make sure it runs, and it does and should have returned a 'null'
Firebase sign-in method.
Future signInWithEmail(String email, String password) async {
try{
AuthResult result = await _auth.signInWithEmailAndPassword(email: email, password: password);
FirebaseUser user = result.user;
return _firebaseUser(user);
} catch(e) {
print(e.toString());
return null;
}
}
You can convert future into simple value using then method. Following code clear your idea.
_auth.signInWithEmail(email, password).then((result){
if(result == null){
setState(() {
error = 'Email and Password does not match';
});
}
});
Try the following:
Future signInWithEmail(String email, String password) async {
await _auth.signInWithEmailAndPassword(email: email, password: password).then((result) {
print(result);
return result;
})
.catchError((error) {
print("Something went wrong: ${error.message}");
return error.message;
});
}
Use the catchError (This is the asynchronous equivalent of a "catch" block.) to be able to handles errors emitted by this Future
Then inside onPressed, do the following:
onPressed: () async {
if(_formKey.currentState.validate()){
_auth.signInWithEmail(email, password).then((result) {
if(result.user == null){
setState(() {
error = result;
});
}
}
}
Then since the method signInWithEmailAndPassword() returns a Future<AuthResult>, therefore check if result.user is equal to null and assign the result(error.message) to the error variable.
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);
},
),
],
),
);
});
}