I have 3 files:
database.dart
import 'package:cloud_firestore/cloud_firestore.dart';
class DatabaseService{
final String uid;
DatabaseService({required this.uid});
final CollectionReference userCollection = FirebaseFirestore.instance.collection('users');
Future updateUserData(String name) async {
return await userCollection.doc(uid).set({
'name': name,
});
}
another file called auth.dart
class AuthService{
final FirebaseAuth _auth = FirebaseAuth.instance;
Future registerWithEmailAndPassword(String email, String password) async {
try{
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
// Create a new document for the user with the uid
await DatabaseService(uid: user!.uid).updateUserData();
return _userFromFirebaseUser(user);
}
catch(e){
print(e);
return null;
}
}
}
And another file called register.dart with this code:
import 'package:flutter/material.dart';
class Register extends StatefulWidget {
const Register({Key? key}) : super(key: key);
#override
_RegisterState createState() => _RegisterState();
}
class _RegisterState extends State<Register> {
String name = '';
#override
Widget build(BuildContext context) {
return Container(
child: Form(
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration
(
contentPadding: EdgeInsets.fromLTRB(20.0, 15.0, 20.0, 15.0),
hintText: "Full Name",
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0))
),
validator: (val) => val!.isEmpty ? 'Enter an email' : null,
onChanged: (val){
setState(() => name = val);
},
),
],
),
),
);
}
}
I want to get the data from TextFormField on register.dart to pass into the function updateUserData on auth.dart. This means the Name will be the data from the keyboard input by the user.
How can I do it? Can someone help me, please?
In your case you are using a Form in combination to TextFormField, in order to retrieve your value you can set a Key to your Form and use it to retrieve your data.
In the case of a simple TextField, you would assign a TextEditingController to it and retrieve its value that way.
Here is an example with a Form, a Key, and validators:
You can then use this value to call your auth fonction with the name as parameter.
final _formKey = GlobalKey<FormState>();
String name = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: (newValue) {
setState(() => name = newValue);
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save(); // Calls onSaved method in your fields
// Other actions such as call your update method
}
},
child: Text('Save'),
),
],
),
),
);
}
Related
i am confused where can i implement the init state condition for future builder. see whats wrong in my code. in flutter documentation is refer initState for Future builder widget.
` #override
void initState() {
super.initState();
futureLoginuser = loginuser();
}`
i am trying to navigate new screen after response data arrives.my complete code is here i am using go_router for navigation.
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _mobileKey = GlobalKey<FormState>();
final _passwordKey = GlobalKey<FormState>();
get message => null;
get messagecode => null;
get userinfo => null;
get token => null;
Future<Loginuser> loginuser(String mobile, String password) async {
final response = await http.post(
Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
} else {
throw Exception('Failed to update');
}
#override
Widget build(BuildContext context) {
return Form(
key: _mobileKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
key: _passwordKey,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: ElevatedButton(
onPressed: () {
FutureBuilder<Loginuser>(
future: loginuser(mobileController.text.toString(),
passwordController.text.toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
print('snapshsot.hasdata');
context.go('/Home');
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
return const CircularProgressIndicator();
},
);
}
child: const Text('Submit')),
),
]));
}
}
`
You are using the FutureBuilder wrong it is a Widget that builds itself based on the latest snapshot of interaction with a Future. Its usually used to build widgets that need input when a certain future is completed.
In your case use this:
//first make the onPressed function async
child ElevatedButton(
child: Container(),
onPressed: () async {
// then await the future You want to complete and then use `.then()`
//method to implement the code that you want to implement when the future is completed
await loginuser(mobileController.text.toString(),
passwordController.text.toString())
.then((result) {
print('future completed');
context.go('/Home');
// For errors use onError to show or check the errors.
}).onError((error, stackTrace) {
print(error);
});
});
And do validate the form for any error with form key validation method.
the only way to wait for the future to complete and do something is to use the Asynchronous function either directly as I have shown above or by using the try/catch method both will work fine.
Try this
LoginUser? loginUser
#override
void initState() async{
super.initState();
futureLoginuser = await loginuser();
... // 👈 Your navigation here
}`
Try to make your build responsive by checking for loginUser
#override
Widget build(BuildContext context) {
futureLoginuser == null ?
CircularProgressIndicator() : <Rest of your widget>
}
The way you are trying to implement is not correct, here is very basic example to do
class LoginForm extends StatefulWidget {
const LoginForm({Key? key}) : super(key: key);
#override
LoginFormState createState() {
return LoginFormState();
}
}
class LoginFormState extends State<LoginForm> {
TextEditingController mobileController = TextEditingController();
TextEditingController passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
// your other variables
bool isValidating = false;
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
TextFormField(
controller: mobileController,
autofocus: true,
keyboardType: TextInputType.phone,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Mobile Number",
),
),
TextFormField(
controller: passwordController,
keyboardType: TextInputType.visiblePassword,
decoration: const InputDecoration(
border: InputBorder.none,
hintText: "Enter Your Password",
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: isValidating
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () async {
if (_formKey.currentState!.validate()) {
setState(() {
isValidating = !isValidating;
});
final r = await loginuser(
mobileController.text.toString(),
passwordController.text.toString());
if (r != null) {
// save user state locally, using hive or what alternative you want
context.go('/Home');
} else {
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content: Text('Failed'),
));
}
setState(() {
isValidating = !isValidating;
});
}
},
child: const Text('Submit')),
),
]));
}
Future<Loginuser?> loginuser(String mobile, String password) async {
final response = await http.post(Uri.parse('https:random.url/api/login'),
body: {'mobile': mobile, 'password': password});
if (response.statusCode == 200) {
return Loginuser.fromJson(jsonDecode(response.body));
}
return null;
}
}
my error was the email address is badly formated
I/flutter (29470): [firebase_auth/invalid-email] The email address is badly formatted.
i was tried solve it but already i have the same problem
my auth form code is
import 'package:flutter/material.dart';
class AuthForm extends StatefulWidget {
const AuthForm(this.submitFn, {Key? key}) : super(key: key);
final void Function(
String username,
String email,
String password,
bool isLogin,
BuildContext context,
) submitFn;
#override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
var emailController = TextEditingController();
final _formKey = GlobalKey<FormState>();
var _isLogin = true;
var _userName = '';
var _userEmail = '';
var _userPassword = '';
void _trySubmit() {
final isValid = _formKey.currentState!.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState!.save();
widget.submitFn(
_userEmail.toString(),
_userName.trim(),
_userPassword.trim(),
_isLogin,
context,
);
}
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
margin: const EdgeInsets.all(20),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextFormField(
key: const ValueKey('email'),
validator: (value) {
if (value!.isEmpty || !value.contains('#')) {
return 'Please Enter valid email address.';
} else {
return null;
}
},
controller: emailController,
keyboardType: TextInputType.emailAddress,
decoration: const InputDecoration(
labelText: 'Email Address',
),
onSaved: (value) {
_userEmail = value!;
},
),
if (!_isLogin)
TextFormField(
key: const ValueKey('username'),
validator: (value) {
if (value!.isEmpty || value.length < 6) {
return 'Please enter at least 6 characters.';
} else {
return null;
}
},
decoration: const InputDecoration(
labelText: 'Username',
),
onSaved: (value) {
_userName = value!;
},
),
TextFormField(
key: const ValueKey('password'),
validator: (value) {
if (value!.isEmpty || value.length < 8) {
return 'Password must be at least 7 characters along.';
} else {
return null;
}
},
decoration: const InputDecoration(
labelText: 'Password',
),
obscureText: true,
onSaved: (value) {
_userPassword = value!;
},
),
const SizedBox(
height: 12,
),
ElevatedButton(
onPressed: _trySubmit,
child: Text(_isLogin ? 'Login' : 'Signup')),
TextButton(
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
child: Text(
_isLogin
? 'Create a new account'
: 'I already have an account',
style: const TextStyle(color: Colors.pink),
))
],
),
),
),
),
),
);
}
}
and my authscreen code is
import 'package:chat_app/Widgets/auth/auth_form.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AuthScreen extends StatefulWidget {
const AuthScreen({Key? key}) : super(key: key);
#override
State<AuthScreen> createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final _auth = FirebaseAuth.instance;
void _submitAuthForm(
String username,
String email,
String password,
bool isLogin,
BuildContext context,
) async {
UserCredential authResult;
try {
if (isLogin) {
authResult = await _auth.signInWithEmailAndPassword(
email: email, password: password);
} else {
authResult = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
}
} on PlatformException catch (error) {
var message = 'An error occured, please check your credentials';
if (error.message != null) {
message = error.message!;
}
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(message),
backgroundColor: Theme.of(context).errorColor,
));
} catch (error) {
// ignore: avoid_print
print(error.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.pink,
body: AuthForm(_submitAuthForm),
);
}
}
when i tried to sign up, i see the error in badly formated in my email address but i entered the right email address, can anyone help me please?
I am using Flutter to make a registration form using Firebase Authentication but this message keeps showing when I try to handle the authentication while trying to show the user the registration error message using the Scaffold.of(context).showSnackBar SnackBar(content: Text(e.message)at Sign-up widget
First I created an auth.dart file
import 'package:firebase_auth/firebase_auth.dart';
class Auth {
final FirebaseAuth _auth = FirebaseAuth.instance;
//Sign up method
Future SignupClass(String email, String password) async {
final UserCredential user = await _auth.createUserWithEmailAndPassword(
email: email, password: password);
}
Then I created this TextField class to modularize my code
class Textfield extends StatelessWidget {
final String hint;
final IconData icon;
final Function savingData;
String _emptyMessage(String str) {
switch (hint) {
case 'Enter your name':
return 'Name is required';
case 'Enter your e-mail':
return 'Email is required';
case 'Enter your Password':
return 'Password is required';
}
}
Textfield(
{#required this.savingData, #required this.hint, #required this.icon});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: TextFormField(
validator: (value) {
if (value.isEmpty) {
return _emptyMessage(hint);
}
return null;
},
onSaved: savingData
}}
and This is the Sig-up widget
class SignupScreen extends StatelessWidget {
static String id = '/SignupScreen';
GlobalKey<FormState> _globalKey = GlobalKey<FormState>();
String _email, _password, _name;
final _auth = Auth();
#override
Widget build(BuildContext context) {
return Scaffold(Textfield(
hint: 'Enter your name',
icon: Icons.person,
savingData: (value) {
_name = value;
},
),
SizedBox(
height: height * .02,
),
Textfield(
hint: 'Enter your e-mail',
icon: Icons.email,
savingData: (value) {
_email =value;
},
),
SizedBox(
height: height * .02,
),
Textfield(
hint: 'Enter your Password',
icon: Icons.lock,
savingData: (value) {
_password = value;
},
),
SizedBox(
height: height * .05,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 120.0),
child: Builder(
builder: (context) => RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
child: Text(
'Register',
style: TextStyle(color: Colors.white),
),
color: Colors.black,
onPressed: () async {
if (_globalKey.currentState.validate()) {
try {
await _auth.SignupClass(_email, _password);
_globalKey.currentState.save();
}on FirebaseException catch (e) {
print(e.toString());
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(e.message),
),
);
}
}
}),
),
),
)
}
and I wrapped my main function with Firebase Initialize
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
You'll need to convert your widget to a StatefulWidget and call setState() on your text fields' onChanged methods. Because, the widget's state doesn't get updated until a build is called, your _email, _password and _name fields will never be updated. This will fix your issue, but there are better ways of doing this.
There are many state management libraries out there that greatly assist in keeping that kind of state outside the UI (Generally, it's better to separate any non-UI state from the UI). Here are a few recommendations:
BloC
Provider
RxDart
There are many tutorials on YouTube that you can check out. A few I recommend are from AndyJulow, ResoCoder, and MarcusNg.
Very simple login form, works on iOS and web, but not on Android. Is there something fundamental, or is there some bug related to this. Basically, clicking on the "submit" button does not make make the login call.
The AppState is a singelton class in case you want to know.
Basically it is identical to the form defined in the Flutter documentation: https://flutter.dev/docs/cookbook/forms/validation
import 'package:flutter/material.dart';
import 'package:flutter_poc/app_state.dart';
import 'package:http/http.dart' as http;
import 'dart:convert' as convert;
class AuthPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(child: MyCustomForm()),
),
);
}
}
class MyCustomForm extends StatefulWidget {
#override
MyCustomFormState createState() {
return MyCustomFormState();
}
}
class MyCustomFormState extends State<MyCustomForm> {
final _formKey = GlobalKey<FormState>();
String _username;
String _password;
Future<String> getAuthToken(username, password) async {
final postData = {'username': username, 'password': password};
final loginUrl = 'https://xxx/login';
final response = await http.post(loginUrl, body: postData);
if (response.statusCode == 200) {
final data = convert.jsonDecode(response.body);
return data['token'];
}
return null;
}
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
decoration: InputDecoration(hintText: 'Email'),
validator: (value) {
if (value.isEmpty) {
return 'Enter email';
}
return null;
},
onSaved: (val) => {_username = val},
),
TextFormField(
decoration: InputDecoration(hintText: 'Password'),
validator: (value) {
if (value.isEmpty) {
return 'Enter password';
}
return null;
},
obscureText: true,
onSaved: (val) => {_password = val},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () async {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
final token = await getAuthToken(_username, _password);
if (token == null) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('failed to login')));
} else {
AppState().setToken(token);
}
}
},
child: Text('Submit'),
),
),
],
),
);
}
}
I didn't notice the problem directly, as I never got any direct response, so always rebooted. But now when I ran in verbose mode I took a cup of coffee, and noticed that there were a DNS resolution error. Basically the Android emulator that I used did not pick up the DNS from the host machine, so it could not resolve the api address. Solved my problem by setting dns manually, like here: Internet stopped working on Android Emulator (Mac OS)
I have a question about InheritedWidget. Since most of the pages in my apps used the user object, so I created an InheritedWidget class called UserProvider so I don't need to pass the user object along my widget tree. It works fine until I tried to logout and login with another user. The User remains the old one. I do a bit of research and it seems that the value inside InheritedWidget class cannot be changed. It there a way to rewrite it so I can take advantage of InheritedWidget and still able to change the value of the user object?
UserProvider Class:
class UserProvider extends InheritedWidget {
UserProvider({Key key, Widget child, this.user}) : super(key: key, child: child);
final User user;
/* #override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
*/
#override
bool updateShouldNotify(UserProvider oldWidget) {
return user != oldWidget.user;
}
static UserProvider of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(UserProvider) as UserProvider);
}
}
HomePage class:
class HomePage extends StatefulWidget {
HomePage({this.auth, this.onSignedOut,this.userId});
final BaseAuth auth;
final VoidCallback onSignedOut;
final String userId;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _userName;
String _userEmail;
String _userPicURL;
User currentUser;
void _signOut() async {
try {
await widget.auth.signOut();
widget.onSignedOut();
} catch (e) {
print(e);
}
}
#override
void initState() {
super.initState();
currentUser = User(widget.userId);
currentUser.loadUserData();
...
#override
Widget build(BuildContext context) {
return UserProvider(
user: currentUser,
...
LoginPage class:
class LoginPage extends StatefulWidget {
LoginPage({this.auth, this.onSignedIn});
final BaseAuth auth;
final VoidCallback onSignedIn;
#override
//_LoginPageState createState() => _LoginPageState();
State<StatefulWidget> createState() => _LoginPageState();
}
enum FormType {
login,
register
}
class _LoginPageState extends State<LoginPage> {
final formKey = new GlobalKey<FormState>();
String _uid;
String _email;
String _password;
String _birthday;
String _fullname;
FormType _formType = FormType.login;
bool validateAndSave() {
final form = formKey.currentState;
if (form.validate()) {
form.save();
return true;
} else {
return false;
}
}
void _addData(String email, String fullname, String birthday) async {
_uid = await widget.auth.currentUser();
Firestore.instance.runTransaction((Transaction transaction) async{
Firestore.instance.collection("Users").document(_uid).setData(
{
"id": _uid,
"email" : email,
"fullname": fullname,
"birthday" : birthday
});
});
}
void validateAndSubmit() async{
final form = formKey.currentState;
if (validateAndSave()) {
try {
if (_formType == FormType.login) {
String userId = await widget.auth.signInWithEmailAndPassword( _email.trim(), _password.trim());
} else {
String userId = await widget.auth.createUserWithEmailAndPassword( _email.trim(), _password.trim());
_addData(_email, _fullname, _birthday);
}
widget.onSignedIn();
}
catch (e)
{
print('Error: $e');
}
} else {
print('form is invalid');
}
}
void moveToRegister () {
formKey.currentState.reset();
setState(() {
_formType = FormType.register;
});
}
void moveToLogin () {
formKey.currentState.reset();
setState(() {
_formType = FormType.login;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Login"),
backgroundColor: const Color(0xFF86d2dd),
),
body: new Container(
padding: EdgeInsets.all(16.0),
child: new Form(
key: formKey,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: buildInputs() + buildSubmitButtons(),
)
)
)
);
}
List<Widget> buildInputs() {
if (_formType == FormType.login) {
return [
new TextFormField(
decoration: new InputDecoration(labelText: "Email"),
validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
onSaved: (value) => _email = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: "Password"),
obscureText: true,
validator: (value) => value.isEmpty ? 'Password can\'t be empty' : null,
onSaved: (value) => _password = value,
),
];
} else {
return [
new TextFormField(
decoration: new InputDecoration(labelText: "Email"),
validator: (value) => value.isEmpty ? 'Email can\'t be empty' : null,
onSaved: (value) => _email = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: "Password"),
obscureText: true,
validator: (value) => value.isEmpty ? 'Password can\'t be empty' : null,
onSaved: (value) => _password = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: "Name "),
validator: (value) => value.isEmpty ? 'Name can\'t be empty' : null,
onSaved: (value) => _fullname = value,
),
new TextFormField(
decoration: new InputDecoration(labelText: "Birthday (MM/DD)"),
validator: (value) => value.isEmpty ? 'Birthday can\'t be empty' : null,
onSaved: (value) => _birthday = value,
),
];
}
}
List<Widget> buildSubmitButtons() {
if (_formType == FormType.login) {
return [
new RaisedButton(
child: new Text('Login', style: new TextStyle(fontSize: 20.0)),
onPressed: validateAndSubmit,
),
new FlatButton(
child: new Text('Create an account', style: new TextStyle(fontSize: 20.0)),
onPressed: moveToRegister,
)
];
} else {
return [
new RaisedButton(
child: new Text('Create an account', style: new TextStyle(fontSize: 20.0)),
onPressed: validateAndSubmit,
),
new FlatButton(
child: new Text('Have an account? Login', style: new TextStyle(fontSize: 20.0)),
onPressed: moveToLogin,
)
];
}
}
}
I'm experimenting with InheritedWidget myself. After reading https://stackoverflow.com/a/51912243/7050833 I would try placing the UserProvider above the MaterialApp.
UserProvider(child: MaterialApp(...