I've this simple login screen with two TextFormField for email and password. When I try to enter text in any of these text boxes, keyboard appears momentarily and disappears every time I focus on text field to enter data.
class LogInPage extends StatefulWidget {
final String title;
LogInPage({Key key, this.title}) : super(key: key);
#override
_LogInPageState createState() => new _LogInPageState();
}
class _LogInPageState extends State<LogInPage> {
static final formKey = new GlobalKey<FormState>();
String _email;
String _password;
Widget padded({Widget child}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: child,
);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Card(
child:
Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
padding: const EdgeInsets.all(16.0),
child: Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
padded(
child: TextFormField(
key: Key('email'),
decoration: InputDecoration(labelText: 'Email'),
autocorrect: false,
validator: (val) => val.isEmpty
? 'Email can\'t be empty.'
: null,
onSaved: (val) => _email = val,
)),
padded(
child: TextFormField(
key: Key('password'),
decoration:
InputDecoration(labelText: 'Password'),
obscureText: true,
autocorrect: false,
validator: (val) => val.isEmpty
? 'Password can\'t be empty.'
: null,
onSaved: (val) => _password = val,
)),
]))),
])),
])));
}
}
This is the form:
EDIT
I think the problem lies in the way I'm calling this page like below. Is it okay to call another page from FutureBuilder ?
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("LogIn Demo"),
),
body: FutureBuilder<FirebaseUser>(
future: Provider.of<FireAuthService>(context).currentUser(),
builder: (context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.error != null) {
return Text(snapshot.error.toString());
}
return snapshot.hasData
? UserProfilePage(snapshot.data)
: LogInPage(title: 'Login');
} else {
return Container(
child: CircularProgressIndicator(),
);
}
},
),
);
}
Clean your code first and rebuild, perform testing with real device as well.
static GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
Related
I need some required values to submit.
I'm using TabBarView to view different sections.
Here's my code.
add_products_screen.dart
class _AddProductScreenState extends State<AddProductScreen> {
#override
Widget build(BuildContext context) {
final formkey = GlobalKey<FormState>();
return Form(
key: formkey,
child: DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
appBar: AppBar(
title: const Text('Add Products'),
bottom: const TabBar(
isScrollable: true,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
width: 4,
color: Colors.deepOrange,
),
),
tabs: [
Tab(child: Text('General')),
Tab(child: Text('Attributes')),
],
),
),
drawer: const CustomDrawer(),
body: const TabBarView(
children: [
GeneralTab(),
AttributeTab(),
],
),
persistentFooterButtons: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () {
if (formkey.currentState!.validate()) {}
},
child: const Text('Save Product'),
),
),
],
),
),
],
),
),
);
}
}
form_field_input.dart
class FormFieldInput extends StatelessWidget {
final String? label;
final void Function(String)? onChanged;
const FormFieldInput({
Key? key,
this.label,
this.onChanged,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
label: Text(label!),
),
validator: (value) {
if (value!.isEmpty) {
return '$label is required';
}
return null;
},
onChanged: onChanged,
);
}
}
general_tab.dart
class _GeneralTabState extends State<GeneralTab>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Consumer<ProductProvider>(
builder: (context, provider, child) {
return ListView(
padding: const EdgeInsets.all(15.0),
children: [
FormFieldInput(
label: 'Product Name',
onChanged: (value) {
provider.getFormData(productName: value);
},
),
FormFieldInput(
label: 'Description',
onChanged: (value) {
provider.getFormData(description: value);
},
),
],
);
},
);
}
}
attributes_tab.dart
class _AttributeTabState extends State<AttributeTab>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Consumer<ProductProvider>(
builder: (context, provider, _) {
return ListView(
padding: const EdgeInsets.all(15.0),
children: [
FormFieldInput(
label: 'Brand',
onChanged: (value) {
provider.getFormData(brand: value);
},
),
FormFieldInput(
label: 'Remarks',
onChanged: (value) {
provider.getFormData(remarks: value);
},
),
],
);
},
);
}
}
My error is when I pressed save product button validator is showing only the 1st tab textformfields.
2nd tab textformfields validators are only showing when I go to that tab.
Otherwise, it won't show.
Here are some screenshots.
Before I go to 2nd tab and press save product button
After I go to 2nd tab and press save product button
How do I solve this error?
A form with a key will validate all of its children.
In your first case General tab alone created so those two Formfileds are the children of the Form.
But in your second case as you have opened the attributes tab, both the General and Attributes tab is loaded and now all 4 Form Fields are children of the Form.
So,
Wrap the general_tab.dart and attributes_tab.dart with individual Form widget with seperate form key.
Then validate them alone with their keys.
I'm using bloc to handle the state of a password manager app. But I get the error: Error: Could not find the correct Provider above this AddPassWidget Widget
I have placed BlocProvider in home_screen where I show the passwords in a listview. There is a floating action button which is used to add new password. The error occur while clicking on save button. I guess I'm providing wrong context in it but can't figure out where I'm missing.
My code looks like:
Home Screen:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) =>
PassBloc(context.read<PassRepository>())..add(PassLoadEvent()),
child: Scaffold(
appBar: _appBar(),
body: _bodyView(),
drawer: const SideMenu(),
floatingActionButton: _floatingActionButton(),
),
);
}
Widget _bodyView() {
return BlocBuilder<PassBloc, PassState>(
builder: (context, state) {
if (state is PassInitial) {
return _loadingView();
} else if (state is PassLoadedState) {
return state.passList.isEmpty
? _emptyPassView()
: _passListView(state.passList);
}
return _loadingView();
},
);
}
Widget _floatingActionButton() {
return FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () => showModalBottomSheet(
context: context,
builder: (context) => const AddPassWidget(),
),
);
}
Add pass dialog
class AddPassWidget extends StatefulWidget {
const AddPassWidget({Key? key}) : super(key: key);
#override
State<AddPassWidget> createState() => _AddPassWidgetState();
}
class _AddPassWidgetState extends State<AddPassWidget> {
final TextEditingController _websiteTEC = TextEditingController();
final TextEditingController _userNameTEC = TextEditingController();
final TextEditingController _passwordTEC = TextEditingController();
Uuid uuid = const Uuid();
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Text(
"Add Password",
style: TextStyle(fontSize: 20),
),
const SizedBox(height: 15),
TextFormField(
autocorrect: false,
autofocus: true,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Website Name",
),
controller: _websiteTEC,
),
const SizedBox(height: 10),
TextFormField(
autocorrect: false,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Username",
),
controller: _userNameTEC,
),
const SizedBox(height: 10),
TextFormField(
obscureText: true,
enableSuggestions: false,
autocorrect: false,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: "Password",
),
controller: _passwordTEC,
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text("Cancel")),
ElevatedButton(
onPressed: () {
context.read<PassBloc>().add(PassAddEvent(
pass: PassModel(
id: uuid.v4(),
websiteName: _websiteTEC.text,
username: _userNameTEC.text,
password: _passwordTEC.text,
),
));
Navigator.pop(context);
},
child: const Text('Save'),
),
],
),
],
),
),
),
);
}
}
I am working on Flutter TextFormField. and i want to display an error message below the TextFormField but when i debug i got this error
The method 'validate' was called on null.
Receiver: null
Tried calling: validate()
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController _titleController;
TextEditingController _descriptionController;
final _formKey = GlobalKey<FormState>();
#override
void initState() {
super.initState();
_titleController = new TextEditingController(text: widget.products.adTitle);
_descriptionController = new TextEditingController(text: widget.products.description); #override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Products")
),
body: Container(
margin: EdgeInsets.all(15.0),
alignment: Alignment.center,
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
controller: _titleController,
decoration: InputDecoration(labelText: 'Title'),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Text is empty';
}
return null;
},
RaisedButton(
child: (widget.products.id != null)? Text('Update') : Text('Add'),
onPressed:(){
if (_formKey.currentState.validate()) {
child: Text('Submit');
}
in order to use the validate function, your Column should be wrap in Form
Container(
margin: EdgeInsets.all(15.0),
alignment: Alignment.center,
child: Form(
key: _formKey,
child: Column(children: <Widget>[
TextFormField(
controller: _titleController,
decoration:InputDecoration(labelText: 'Title'),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Text is empty';
}
return null;
},
)
]))),
I have a list of custom TextFormField's that i added to them a delete icon
all I am trying to do is when I press the delete button it will be deleted from the list and the view
i tried adding a function to my form field with no success
I think my approach isn't the best way to implement what i want, I am open to any idea
here is the code
import 'package:flutter/material.dart';
typedef DeleteCallback = void Function(Key key);
class DynamicFormField extends FormField<String>{
DynamicFormField({
Key key,
FormFieldSetter<String> onSaved,
FormFieldValidator<String> validator,
String initialValue = "",
bool autovalidate = false,
DeleteCallback onDelete(Key key),
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
builder: (FormFieldState<String> state) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
flex: 5,
child: TextFormField(
decoration: const InputDecoration(
hintText: 'Enter Player Name',
),
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
),
),
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: onDelete(key)
),
],
);
}
);
}
DynamicFormField(
key: UniqueKey(),
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
onSaved: (input) => {players.add(Player(input))},
onDelete: f,
),
);
}
void f(Key key){
fields.removeWhere((item) => item.key == key);
}
tnx
I solved it using ListView builder
import 'package:flutter/material.dart';
import 'package:rummy/models/player.dart';
import 'package:rummy/screens/game_screen.dart';
class NewGame extends StatefulWidget {
NewGame({Key key}) : super(key: key);
#override
_NewGameState createState() => _NewGameState();
}
class _NewGameState extends State<NewGame> {
final _formKey = GlobalKey<FormState>();
List<Widget> fields;
List<Player> players;
_NewGameState() {
players = new List<Player>();
fields = new List();
print(players);
fields.add(generateField());
}
Widget generateField() {
return TextFormField(
decoration: const InputDecoration(
hintText: 'Enter Player Name',
),
onSaved: (input) => {players.add(Player(input))},
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox.expand(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: <Widget>[
Form(
key: _formKey,
child: Expanded(
child: ListView(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).size.height,
child: Builder(
builder: (BuildContext context) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: fields.length,
itemBuilder:
(BuildContext context, int postion) {
return Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Expanded(
child: fields[postion],
),
IconButton(
icon: Icon(Icons.delete_outline),
onPressed: () => {
setState(() {
print(postion);
fields.removeAt(postion);
})
}),
],
);
},
);
},
),
)
],
),
)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
print("asdasd");
if (_formKey.currentState.validate()) {
players.clear();
_formKey.currentState.save();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GameScreen(players),
));
} else
print(_formKey.currentState.validate());
},
child: Text('Submit'),
),
RaisedButton(
onPressed: () {
setState(() {
fields.add(generateField());
});
},
child: Text('Add New Player'),
),
],
),
],
mainAxisAlignment: MainAxisAlignment.center,
),
),
),
);
}
}
I used this
https://github.com/MobMaxime/Flutter-To-Do-App/blob/master/lib/screens/todo_list.dart
The effect is that you can't open the keyboard. I did some digging around this issue elsewhere, and most issues are when the widget is stateful (but mine is not).The following is the LoginWidget. I'm using the provider package, which I suspect might be doing something underneath the covers. Can anyone spot something I don't see? :
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
String email, password;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
return ChangeNotifierProvider<LoginModel>(
builder: (context) => LoginModel(ViewState.Idle),
child: Consumer<LoginModel>(
builder: (context, model, child) => Scaffold(
appBar: AppBar(
title: Text("Sign in"),
),
body: Form(
key: formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextFormField(
validator: (input) {
if (input.isEmpty) {
return "Email required";
}
},
onSaved: (input) => email = input,
decoration: InputDecoration(labelText: 'Email'),
),
TextFormField(
validator: (input) {
if (input.isEmpty) {
return "Password required";
}
},
onSaved: (input) => password = input,
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
),
model.state == ViewState.Loading
? CircularProgressIndicator()
: RaisedButton(
onPressed: () async {
if (formKey.currentState.validate()) {
formKey.currentState.save();
bool success =
await model.login(email, password);
if (success) {
// Navigator.pushNamed(context, '/');
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage()),
);
}
}
},
child: Text('Sign in'),
),
if (model.state == ViewState.Error)
Center(child: Text(model.apiErrorMessage))
],
)),
)));
}
}
The solution was to move the final GlobalKey<FormState> formKey = GlobalKey<FormState>() out of the builder and make it static.
But if you have a list of "LoginPage" it will trigged widget use the same key.
I prefer wrap the parent widget with a Consumer widget.