I have multiple form in each step of a Stepper, Form are in external file because in my App each Step can contain a different Form. I want that when user click on "Continue" the form will be validated and in an error situation the user will be warned. I tried to use Inherited Widget but it gives me a "null on getter". The code below:
Screen that contain Steps
import 'package:flutter/material.dart';
import 'package:pberrycoffeemaker/widgets/function_appbar.dart';
import 'package:pberrycoffeemaker/widgets/inputs_0.dart';
import 'package:pberrycoffeemaker/widgets/stepper_banner.dart';
class FunctionScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
int indexStep = 0;
double bottomHeight = (MediaQuery.of(context).size.height * 40) / 100;
double cardHeight = (MediaQuery.of(context).size.height * 60) / 100;
double cardWidth = (MediaQuery.of(context).size.width * 85) / 100;
FirstTypeInput firstTypeOfInput = FirstTypeInput();
GlobalKey<FormState> key = new GlobalKey<FormState>();
return Scaffold(
body: Stack(
children: <Widget>[
AppbarBack(
height: bottomHeight,
),
Align(
alignment: Alignment(0.0, .65),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(15.0)),
child: Container(
child: StepperBanner(
firstTypeInputKey: key,
test: 18,
firstTypeInputField: {},
child: Stepper(
currentStep: indexStep,
onStepContinue: (){
print(StepperBanner.of(context).test);
//StepperBanner.of(context).firstTypeInputKey.currentState.validate();
},
//type: StepperType.horizontal,
steps: <Step>[
Step(
content: firstTypeOfInput,
title: Text("Theorical"),
),
Step(
content: //Second Step Content,
title: Text("Practical")),
],
),
),
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.black,
blurRadius: 10.0,
offset: Offset(0.0, 0.75))
]),
width: cardWidth,
height: cardHeight,
),
),
),
],
),
);
}
}
Form contained in first Step
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pberrycoffeemaker/functions/coffeeCalculation.dart';
import 'package:pberrycoffeemaker/widgets/stepper_banner.dart';
// ignore: must_be_immutable
class FirstTypeInput extends StatefulWidget {
final Map<String, double> submittedField = {
"tds": 0.0,
"ext": 0.0,
"dw": 0.0,
"af": 0.0,
"co2": 0.0,
"co2p": 0.0,
"ih": 0.0,
"ihp": 0.0,
"wtemp": 0.0,
"twh": 0.0,
"alk": 0.0
};
#override
State<StatefulWidget> createState() {
return _FirstTypeInput();
}
}
class _FirstTypeInput extends State<FirstTypeInput> {
Map<String, FocusNode> _focusNodes;
GlobalKey<FormState> formKey;
#override
void initState() {
super.initState();
_focusNodes = {
"ext": new FocusNode(),
"dw": new FocusNode(),
"af": new FocusNode(),
"co2": new FocusNode(),
"co2P": new FocusNode(),
"ih": new FocusNode(),
"ihP": new FocusNode(),
"wt": new FocusNode(),
"twh": new FocusNode(),
"alk": new FocusNode()
};
}
String percentageValidator(String value){
if (double.parse(value) < 0 || double.parse(value) > 100){
return "Insert value between 0 - 100";
}
return null;
}
#override
Widget build(BuildContext context) {
return Form(
key: StepperBanner.of(context).firstTypeInputKey,
//key: widget.formKey,
child: Column(
children: <Widget>[
// * TDS
TextFormField(
//validator: percentageValidator,
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["ext"]);
},
decoration: InputDecoration(
labelText: "TDS%", hintText: "Insert TDS%", suffix: Text("%")),
),
// * EXT
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["dw"]);
},
focusNode: _focusNodes["ext"],
decoration: InputDecoration(
labelText: "EXT%", hintText: "Insert EXT%", suffix: Text("%")),
),
// * Drink Weight
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["af"]);
},
focusNode: _focusNodes["dw"],
decoration: InputDecoration(
labelText: "Drink Weight",
hintText: "Insert drink weight",
suffix: Text("g")),
),
// * Absorption Factor
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["co2"]);
},
focusNode: _focusNodes["af"],
decoration: InputDecoration(
labelText: "Absorption Factor",
hintText: "Insert absorptio factor",
suffix: Text("g")),
),
// * CO2
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["co2P"]);
},
focusNode: _focusNodes["co2"],
decoration: InputDecoration(
labelText: "CO2", hintText: "Insert CO2", suffix: Text("g")),
),
// * CO2 Precision
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["ih"]);
},
focusNode: _focusNodes["co2P"],
decoration: InputDecoration(
labelText: "CO2 Precision",
hintText: "Insert CO2 Precision",
suffix: Text("%")),
),
// * Internal Humidity
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["ihP"]);
},
focusNode: _focusNodes["ih"],
decoration: InputDecoration(
labelText: "Internal Humidity",
hintText: "Insert internal humidity",
suffix: Text("%")),
),
// * Internal Humidity Precision
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["wt"]);
},
focusNode: _focusNodes["ihP"],
decoration: InputDecoration(
labelText: "Internal Humidity Precision",
hintText: "Insert internal humidity precision",
suffix: Text("%")),
),
// * Water Temperature
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["twh"]);
},
focusNode: _focusNodes["wt"],
decoration: InputDecoration(
labelText: "Water Temperature",
hintText: "Insert water temperature",
//TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
suffix: Text("C°|F°|K°")),
),
// * Total Water Hardness
TextFormField(
onFieldSubmitted: (value) {
FocusScope.of(context).requestFocus(_focusNodes["alk"]);
},
focusNode: _focusNodes["twh"],
decoration: InputDecoration(
labelText: "Total Water Hardness",
hintText: "Insert total water hardness",
//TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
suffix: Text("PPM°|F°|D°")),
),
// * Alkalinity
TextFormField(
focusNode: _focusNodes["alk"],
decoration: InputDecoration(
labelText: "Alkalinity",
hintText: "Insert alkalinity",
//TODO da decidere se settare nelle impostazioni l'unità di misura oppure mettere un dropdown sotto
suffix: Text("PPM°|F°|D°")),
),
],
),
);
}
}
Inherited Class, StepperBanner:
import 'package:flutter/material.dart';
class StepperBanner extends InheritedWidget {
final Map<String, double> firstTypeInputField;
final GlobalKey<FormState> firstTypeInputKey;
final int test;
StepperBanner({Widget child, this.firstTypeInputField,this.test, this.firstTypeInputKey}) : super(child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static StepperBanner of(BuildContext context) =>
context.inheritFromWidgetOfExactType(StepperBanner);
}
Should I manage validation with Inherited Class or there are some other metods?
Use a list GlobalKey to keep each form's key and in Continue call formKeys[currStep].currentState.validate()
formKeys is global variable, In your case for separate form file you can use global library to access it Global Variables in Dart
For demo, each from only have one field
code snippet
List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];
...
onStepContinue: () {
setState(() {
if(formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
full code
import 'package:flutter/material.dart';
//import 'package:validate/validate.dart'; //for validation
void main() {
runApp( MyApp());
}
List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppScreenMode();
}
}
class MyData {
String name = '';
String phone = '';
String email = '';
String age = '';
}
class MyAppScreenMode extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.lightGreen,
),
home: Scaffold(
appBar: AppBar(
title: Text('Steppers'),
),
body: StepperBody(),
));
}
}
class StepperBody extends StatefulWidget {
#override
_StepperBodyState createState() => _StepperBodyState();
}
class _StepperBodyState extends State<StepperBody> {
int currStep = 0;
static var _focusNode = FocusNode();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
static MyData data = MyData();
#override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {});
print('Has focus: $_focusNode.hasFocus');
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
List<Step> steps = [
Step(
title: const Text('Name'),
//subtitle: const Text('Enter your name'),
isActive: true,
//state: StepState.error,
state: StepState.indexed,
content: Form(
key: formKeys[0],
child: Column(
children: <Widget>[
TextFormField(
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (String value) {
data.name = value;
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Please enter name';
}
},
decoration: InputDecoration(
labelText: 'Enter your name',
hintText: 'Enter a name',
//filled: true,
icon: const Icon(Icons.person),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Phone'),
//subtitle: const Text('Subtitle'),
isActive: true,
//state: StepState.editing,
state: StepState.indexed,
content: Form(
key: formKeys[1],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.phone,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please enter valid number';
}
},
onSaved: (String value) {
data.phone = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your number',
hintText: 'Enter a number',
icon: const Icon(Icons.phone),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Email'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
// state: StepState.disabled,
content: Form(
key: formKeys[2],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
autocorrect: false,
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter valid email';
}
},
onSaved: (String value) {
data.email = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your email',
hintText: 'Enter a email address',
icon: const Icon(Icons.email),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Age'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
content: Form(
key: formKeys[3],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.number,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length > 2) {
return 'Please enter valid age';
}
},
maxLines: 1,
onSaved: (String value) {
data.age = value;
},
decoration: InputDecoration(
labelText: 'Enter your age',
hintText: 'Enter age',
icon: const Icon(Icons.explicit),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
// Step(
// title: const Text('Fifth Step'),
// subtitle: const Text('Subtitle'),
// isActive: true,
// state: StepState.complete,
// content: const Text('Enjoy Step Fifth'))
];
#override
Widget build(BuildContext context) {
void showSnackBarMessage(String message,
[MaterialColor color = Colors.red]) {
Scaffold
.of(context)
.showSnackBar( SnackBar(content: Text(message)));
}
void _submitDetails() {
final FormState formState = _formKey.currentState;
if (!formState.validate()) {
showSnackBarMessage('Please enter correct data');
} else {
formState.save();
print("Name: ${data.name}");
print("Phone: ${data.phone}");
print("Email: ${data.email}");
print("Age: ${data.age}");
showDialog(
context: context,
child: AlertDialog(
title: Text("Details"),
//content: Text("Hello World"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Name : " + data.name),
Text("Phone : " + data.phone),
Text("Email : " + data.email),
Text("Age : " + data.age),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
}
}
return Container(
child: Form(
key: _formKey,
child: ListView(children: <Widget>[
Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: this.currStep,
onStepContinue: () {
setState(() {
if(formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
// else {
// Scaffold
// .of(context)
// .showSnackBar( SnackBar(content: Text('$currStep')));
// if (currStep == 1) {
// print('First Step');
// print('object' + FocusScope.of(context).toStringDeep());
// }
// }
});
},
onStepCancel: () {
setState(() {
if (currStep > 0) {
currStep = currStep - 1;
} else {
currStep = 0;
}
});
},
onStepTapped: (step) {
setState(() {
currStep = step;
});
},
),
RaisedButton(
child: Text(
'Save details',
style: TextStyle(color: Colors.white),
),
onPressed: _submitDetails,
color: Colors.blue,
),
]),
));
}
}
working demo
Related
Referring to someone's post, I'm trying to include a new attribute, Age which only receives integer value. However, when using TextEditingController and doing adding to the list, it's about just String which usually doesn't accept integer.
I want to input just numbers (integers) on the textfield 'age' and make the Age value accepted for adding to the list. I tried to use Parse on the class for 'age' but it causes an exception: The following FormatException was thrown building Builder:
Invalid number (at character 1)
My intention is to put the int values into the list as well. Is there any way to do that?
This is the code I've been working on.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: Episode5()));
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class User {
String name;
String age;
int age1;
User({this.name = '', this.age = ''}) : age1 = int.parse(age);
}
class _Episode5State extends State<Episode5> {
TextEditingController nameController = TextEditingController();
TextEditingController ageController = TextEditingController();
int currentIndex = 0;
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
bool update = false;
User user = User(age: '', name: '');
List<User> userList = [
User(name: "a", age: '12'),
User(name: "d", age: '23'),
User(name: "c", age: '19'),
];
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Age"), tooltip: "To Display Age"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
DataColumn(label: Text("Delete"), tooltip: "Delete data"),
],
rows: userList
.map(
(name) => DataRow(
cells: [
DataCell(
Text(name.name),
),
DataCell(
Text(name.age),
),
DataCell(
IconButton(
onPressed: () {
_updateTextControllers(name); // new function here
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
DataCell(
IconButton(
onPressed: () =>
_deleteTextControllers(name), // new function here
icon: Icon(
Icons.delete,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
bodyData(),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (value) {
user.name = value!;
},
maxLines: 1,
validator: (value) {
if (value!.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: ageController,
keyboardType: TextInputType.text,
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value!.isEmpty) {
return 'This field is required';
}
return null;
},
onSaved: (value) {
user.age = value!;
},
decoration: new InputDecoration(
labelText: 'Age',
hintText: 'Age',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
// crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
if (validate() == true) {
form.currentState?.save();
addUserToList(
user.name,
user.age,
);
clearForm();
}
},
),
TextButton(
child: Text("Update"),
onPressed: () {
if (validate() == true) {
form.currentState?.save();
updateForm(user);
clearForm();
}
},
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
void updateForm(User user) {
setState(() {
User user = User(name: nameController.text, age: ageController.text);
userList[currentIndex] = user;
});
}
void _updateTextControllers(User user) {
setState(() {
nameController.text = user.name;
ageController.text = user.age;
});
}
void _deleteTextControllers(User user) {
setState(() {
int currentIndex = userList.indexOf(user);
userList.removeAt(currentIndex);
});
}
void addUserToList(name, age) {
setState(() {
userList.add(User(name: name, age: age));
});
}
clearForm() {
nameController.clear();
ageController.clear();
}
bool validate() {
var valid = form.currentState!.validate();
if (valid) form.currentState!.save();
return valid;
}
}
the parse method cant handle exception, like here you are trying to convert empty string'' to int. I will suggest using .tryParse and provide a default value on exception(null) case, or make age nullable.
User({
this.name = '',
this.age = '',
}) : age1 = int.tryParse(age) ?? 0;
or
class User {
String name;
String age;
int? age1;
User({
this.name = '',
this.age = '',
}) : age1 = int.tryParse(age);
}
I will prefer with final and const constructor.
class User {
String name;
int? age1;
User({
required this.name,
this.age1,
});
}
and creating instance
final User user = User(name: "name", age1: int.tryParse("123"));
Solution throughout the discussion in comments.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MaterialApp(home: Episode5()));
class Episode5 extends StatefulWidget {
#override
_Episode5State createState() => _Episode5State();
}
class User {
String name;
int? age1;
User({
required this.name,
this.age1,
});
}
class _Episode5State extends State<Episode5> {
TextEditingController nameController = TextEditingController();
TextEditingController ageController = TextEditingController();
int currentIndex = 0;
final form = GlobalKey<FormState>();
static var _focusNode = new FocusNode();
bool update = false;
final User user = User(name: "", age1: int.tryParse(""));
List<User> userList = [
User(name: "a", age1: 12),
User(name: "d", age1: 23),
User(name: "c", age1: 19),
];
#override
Widget build(BuildContext context) {
Widget bodyData() => DataTable(
onSelectAll: (b) {},
sortColumnIndex: 0,
sortAscending: true,
columns: <DataColumn>[
DataColumn(label: Text("Name"), tooltip: "To Display name"),
DataColumn(label: Text("Age"), tooltip: "To Display Age"),
DataColumn(label: Text("Update"), tooltip: "Update data"),
DataColumn(label: Text("Delete"), tooltip: "Delete data"),
],
rows: userList
.map(
(name) => DataRow(
cells: [
DataCell(
Text(name.name),
),
DataCell(
Text("${name.age1 ?? 0}"),
),
DataCell(
IconButton(
onPressed: () {
_updateTextControllers(name); // new function here
},
icon: Icon(
Icons.edit,
color: Colors.black,
),
),
),
DataCell(
IconButton(
onPressed: () =>
_deleteTextControllers(name), // new function here
icon: Icon(
Icons.delete,
color: Colors.black,
),
),
),
],
),
)
.toList(),
);
return Scaffold(
appBar: AppBar(
title: Text("Data add to List Table using Form"),
),
body: Container(
child: Column(
children: <Widget>[
bodyData(),
Padding(
padding: EdgeInsets.all(10.0),
child: Form(
key: form,
child: Container(
child: Column(
children: <Widget>[
TextFormField(
controller: nameController,
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (value) {
user.name = value!;
},
maxLines: 1,
validator: (value) {
if (value!.isEmpty) {
return 'This field is required';
}
return null;
},
decoration: new InputDecoration(
labelText: 'Name',
hintText: 'Name',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid),
),
),
SizedBox(
height: 10,
),
TextFormField(
controller: ageController,
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.digitsOnly
],
autocorrect: false,
maxLines: 1,
validator: (value) {
if (value!.isEmpty) {
return 'This field is required';
}
return null;
},
onSaved: (value) {
user.age1 = int.tryParse(value ?? "");
},
decoration: new InputDecoration(
labelText: 'Age',
hintText: 'Age',
labelStyle: new TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
SizedBox(
height: 10,
),
Column(
// crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Center(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextButton(
child: Text("Add"),
onPressed: () {
if (validate() == true) {
form.currentState?.save();
addUserToList(
user.name,
user.age1,
);
clearForm();
}
},
),
TextButton(
child: Text("Update"),
onPressed: () {
if (validate() == true) {
form.currentState?.save();
updateForm(user);
clearForm();
}
},
),
],
),
),
],
),
],
),
),
),
),
],
),
),
);
}
void updateForm(User user) {
setState(() {
User user = User(
name: nameController.text, age1: int.tryParse(ageController.text));
userList[currentIndex] = user;
});
}
void _updateTextControllers(User user) {
setState(() {
nameController.text = user.name;
ageController.text = user.age1.toString();
});
}
void _deleteTextControllers(User user) {
setState(() {
int currentIndex = userList.indexOf(user);
userList.removeAt(currentIndex);
});
}
void addUserToList(name, age1) {
setState(() {
userList.add(User(name: name, age1: age1));
});
}
clearForm() {
nameController.clear();
ageController.clear();
}
bool validate() {
var valid = form.currentState!.validate();
if (valid) form.currentState!.save();
return valid;
}
}
So i'm building an app that has a login (i'm knew to flutter so i'm just getting code on youtube and try to add it) and it's supposed to have a bottom bar that leeds to de home page and the profile screen. In the profile screen he can log out.
The problem is, the app was all good, but I tried to add the bottom bar, (i had to add a AuthWrapper()
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:pap_test/tabs_screen.dart';
import 'login_signup/screens/login_screen.dart';
class AuthWrapper extends StatelessWidget {
const AuthWrapper({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final FirebaseAuth _auth = FirebaseAuth.instance;
return StreamBuilder(
stream: _auth.authStateChanges(),
builder: (context, user) {
User? _user = user.data as User?;
if (_user != null) {
return const TabsScreen();
} else {
return const LoginScreen();
}
},
);
}
}
That takes to the home page and the profile page.
// ignore_for_file: prefer_const_constructors, prefer_const_literals_to_create_immutables
import 'package:flutter/material.dart';
import 'disciplinas/disciplinas_screen.dart';
import 'login_signup/screens/home_screen.dart';
class TabsScreen extends StatefulWidget {
const TabsScreen({Key? key}) : super(key: key);
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
final List<Widget> _pages = [
DisciplinasScreen(),
HomeScreen(),
];
int _selectedPageIndex = 0;
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'',
), //seja qual for a opção do bottom bar selecionada ele irá dar o titulo que indicamos e mesmo para a outra página
),
body: _pages[_selectedPageIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
backgroundColor: Colors.black,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_circle_outlined),
label: 'Profile',
),
],
),
);
}
}
the problem is that when i'm in the app, when i rebuild it, the bottom bar is there, but the app bar gets all weird, and when i go to the profile screen, log out and then sign in the app bar gets back to normal but the bottom bar isn't there.
the rotes in the main
routes: {
'/': (ctx) => AuthWrapper(),
DisciplinaModuloScreen.routeName: (ctx) => DisciplinaModuloScreen(),
DownloadScreen.routeName: (ctx) => DownloadScreen(),
UploadResumoScreen.routeName: (ctx) => UploadResumoScreen(),
},
the log in
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../disciplinas/disciplinas_screen.dart';
import '../screens/registration_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key? key}) : super(key: key);
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
//form key
final _formKey = GlobalKey<FormState>();
//editing controller
final TextEditingController emailController = new TextEditingController();
final TextEditingController passwordController = new TextEditingController();
// firebase
final _auth = FirebaseAuth.instance;
// string for displaying the error Message
String? errorMessage;
#override
Widget build(BuildContext context) {
// email field
final emailField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: emailController,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value!.isEmpty) {
return ('Email obrigatório');
}
// reg expression for email validation
if (!RegExp("^[a-zA-Z0-9+_.-]+#[a-zA-Z0-9.-]+.[a-z]").hasMatch(value)) {
return ('Insira um email válifo');
}
return null;
},
onSaved: (value) {
emailController.text = value!;
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.mail,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Email',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// password field
final passwordField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: passwordController,
obscureText: true,
validator: (value) {
RegExp regex = new RegExp(r'^.{6,}$');
if (value!.isEmpty) {
return ('Insira uma password');
}
if (!regex.hasMatch(value)) {
return ('Utilize 6 ou mais carateres');
}
},
onSaved: (value) {
passwordController.text = value!;
},
textInputAction: TextInputAction.done,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Password',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// login field
final loginButton = Material(
elevation: 5,
borderRadius: BorderRadius.circular(15),
color: Color.fromARGB(255, 28, 209, 216),
child: MaterialButton(
padding: EdgeInsets.fromLTRB(20, 15, 20, 15),
minWidth: MediaQuery.of(context).size.width,
onPressed: () {
signIn(emailController.text, passwordController.text);
},
child: Text(
'Login',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline6,
),
),
);
return Scaffold(
// backgroundColor: ,
body: Center(
child: SingleChildScrollView(
child: Container(
// color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(36.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 200,
child: Image.asset(
'assets/images/owl_white.png',
fit: BoxFit.contain,
),
),
emailField,
SizedBox(height: 15),
passwordField,
SizedBox(height: 30),
loginButton,
SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Não tens conta? ',
style: Theme.of(context).textTheme.bodyText1,
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegistrationScreen(),
),
);
},
child: Text(
'SignUp',
style: Theme.of(context).textTheme.bodyText2,
),
)
],
)
],
),
),
),
),
),
),
);
}
void signIn(String email, String password) async {
if (_formKey.currentState!.validate()) {
try {
await _auth
.signInWithEmailAndPassword(email: email, password: password)
.then((uid) => {
Fluttertoast.showToast(msg: "Login Successful"),
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => DisciplinasScreen())),
});
} on FirebaseAuthException catch (error) {
switch (error.code) {
case "invalid-email":
errorMessage = "Your email address appears to be malformed.";
break;
case "wrong-password":
errorMessage = "Your password is wrong.";
break;
case "user-not-found":
errorMessage = "User with this email doesn't exist.";
break;
case "user-disabled":
errorMessage = "User with this email has been disabled.";
break;
case "too-many-requests":
errorMessage = "Too many requests";
break;
case "operation-not-allowed":
errorMessage = "Signing in with Email and Password is not enabled.";
break;
default:
errorMessage = "An undefined Error happened.";
}
Fluttertoast.showToast(msg: errorMessage!);
print(error.code);
}
}
}
}
the sign up
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import '../../disciplinas/disciplinas_screen.dart';
import '../models/user_model.dart';
class RegistrationScreen extends StatefulWidget {
RegistrationScreen({Key? key}) : super(key: key);
#override
State<RegistrationScreen> createState() => _RegistrationScreenState();
}
class _RegistrationScreenState extends State<RegistrationScreen> {
final _auth = FirebaseAuth.instance;
// our form key
final _formKey = GlobalKey<FormState>();
//editing Controller
final firstNameEditingController = new TextEditingController();
// final secondNameEditingController = new TextEditingController();
final emailEditingController = new TextEditingController();
final passwordEditingController = new TextEditingController();
final confirmPassWordEditingController = new TextEditingController();
String? errorMessage;
#override
Widget build(BuildContext context) {
// first name field
final firstNameField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: firstNameEditingController,
keyboardType: TextInputType.name,
validator: (value) {
RegExp regex = new RegExp(r'^.{3,}$');
if (value!.isEmpty) {
return ('Insira um Nome');
}
if (!regex.hasMatch(value)) {
return ('Utilize 3 ou mais carateres');
}
return null;
},
onSaved: (value) {
firstNameEditingController.text = value!;
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.account_circle,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Primeiro Nome',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// email field
final emailField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: emailEditingController,
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value!.isEmpty) {
return ('Email obrigatório');
}
// reg expression for email validation
if (!RegExp("^[a-zA-Z0-9+_.-]+#[a-zA-Z0-9.-]+.[a-z]").hasMatch(value)) {
return ('Insira um email válifo');
}
return null;
},
onSaved: (value) {
emailEditingController.text = value!;
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.mail,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Email',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// password field
final passwordField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: passwordEditingController,
obscureText: true,
validator: (value) {
RegExp regex = new RegExp(r'^.{6,}$');
if (value!.isEmpty) {
return ('Insira uma password');
}
if (!regex.hasMatch(value)) {
return ('Utilize 6 ou mais carateres');
}
},
onSaved: (value) {
passwordEditingController.text = value!;
},
textInputAction: TextInputAction.next,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Password',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// confirm password field
final confirmPasswordField = TextFormField(
style: Theme.of(context).textTheme.headline6,
autofocus: false,
controller: confirmPassWordEditingController,
obscureText: true,
validator: (value) {
if (confirmPassWordEditingController.text !=
passwordEditingController.text) {
return ('As palavras-passe não coincidem. Tente novamente.');
}
return null;
},
onSaved: (value) {
confirmPassWordEditingController.text = value!;
},
textInputAction: TextInputAction.done,
decoration: InputDecoration(
prefixIcon: Icon(
Icons.vpn_key,
color: Color.fromARGB(255, 28, 209, 216),
),
contentPadding: EdgeInsets.fromLTRB(20, 15, 20, 15),
hintText: 'Confirma a Password',
hintStyle: Theme.of(context).textTheme.headline2,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
//borderSide: BorderSide(color: Colors.pink, width: 5.0),
),
),
);
// signup field
final signUpButton = Material(
elevation: 5,
borderRadius: BorderRadius.circular(15),
color: Color.fromARGB(255, 28, 209, 216),
child: MaterialButton(
padding: EdgeInsets.fromLTRB(20, 15, 20, 15),
minWidth: MediaQuery.of(context).size.width,
onPressed: () {
signUp(emailEditingController.text, passwordEditingController.text);
},
child: Text(
'Sign up',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline6,
),
),
);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Color.fromARGB(255, 28, 209, 216),
),
onPressed: () {
// passing this to our root
Navigator.of(context).pop();
},
),
),
body: Center(
child: SingleChildScrollView(
child: Container(
// color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(36.0),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 200,
child: Image.asset(
'assets/images/owl_white.png',
fit: BoxFit.contain,
),
),
SizedBox(height: 0),
firstNameField,
// SizedBox(height: 15),
// secondNameField,
SizedBox(height: 15),
emailField,
SizedBox(height: 15),
passwordField,
SizedBox(height: 15),
confirmPasswordField,
SizedBox(height: 15),
signUpButton,
],
),
),
),
),
),
),
);
}
void signUp(String email, String password) async {
if (_formKey.currentState!.validate()) {
try {
await _auth
.createUserWithEmailAndPassword(email: email, password: password)
.then((value) => {postDetailsToFirestore()})
.catchError((e) {
Fluttertoast.showToast(msg: e!.message);
});
} on FirebaseAuthException catch (error) {
switch (error.code) {
case "invalid-email":
errorMessage = "Your email address appears to be malformed.";
break;
case "wrong-password":
errorMessage = "Your password is wrong.";
break;
case "user-not-found":
errorMessage = "User with this email doesn't exist.";
break;
case "user-disabled":
errorMessage = "User with this email has been disabled.";
break;
case "too-many-requests":
errorMessage = "Too many requests";
break;
case "operation-not-allowed":
errorMessage = "Signing in with Email and Password is not enabled.";
break;
default:
errorMessage = "An undefined Error happened.";
}
Fluttertoast.showToast(msg: errorMessage!);
print(error.code);
}
}
}
postDetailsToFirestore() async {
// calling our firestore
// calling our model
// sending these values
FirebaseFirestore firebaseFirestore = FirebaseFirestore.instance;
User? user = _auth.currentUser;
UserModel userModel = UserModel();
// writing all the values
userModel.email = user!.email;
userModel.uid = user.uid;
userModel.firstName = firstNameEditingController.text;
await firebaseFirestore
.collection('users')
.doc(user.uid)
.set(userModel.toMap());
Fluttertoast.showToast(msg: 'Conta criada :)');
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => DisciplinasScreen()),
(route) => false);
}
}
all the code is here https://drive.google.com/drive/folders/1TlFibzTPXph_mAzyeS3CADTYxsWF3YIv?usp=sharing
a video of what is happening
https://drive.google.com/file/d/1yLMeQ4xxanmtcVQSERCWwf0uY_JXyJ5b/view?usp=sharing
the database for the users
Given that once a user logs in they are supposed to be sent to TabsScreen as per your authwrapper return const TabsScreen();
As per your code once a user is signed in you send them to DisciplinasScreen
Fluttertoast.showToast(msg: "Login Successful"),
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => DisciplinasScreen())),
Once a user logs in send them to AUth wrapper and let authwrapper figure out where to nav the user
Fluttertoast.showToast(msg: "Login Successful"),
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) =>TabsScreen())),
Navigating them to DisciplinasScreen causes the 'weird' stuff you are seeing as its built to work within TabsScreen
I am trying to use setState class inside the floating action button but, setState class is not able to rerender the ui.Here is my code:
Here i use isLogin bool type variable so it able to toggle the two different ui.In fact the is not abe to working when ever the setState function call it must change the value of that variable is rerender the ui but it is not working .
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AuthForm extends StatefulWidget {
#override
_AuthFormState createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
#override
Widget build(BuildContext context) {
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();
print(_userEmail);
print(_userPassword);
print(_userName);
}
}
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(
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter the valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email Address',
),
onSaved: (newValue) {
_userEmail = newValue;
},
),
TextFormField(
validator: (value) {
if (value.isEmpty || value.length < 4) {
return 'Please enter at least 4 character.';
}
return null;
},
decoration: InputDecoration(
labelText: 'Username',
),
onSaved: (newValue) {
_userName = newValue;
},
),
TextFormField(
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 character long.';
}
return null;
},
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
onSaved: (newValue) {
_userPassword = newValue;
},
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () {
_trySubmit();
},
child: Text(
_isLogin ? 'Login' : 'Signup',
),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Text(
_isLogin
? 'Create New Account.'
: 'I Already Have Account.',
),
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
),
],
),
),
),
),
),
);
}
}
The problem is with the var _isLogin = true; you have defined it inside the build method that's why whenever the setState is called it becomes true and does not change the ui.
Try placing it outside the build method.
Example below:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return AuthForm();
}
}
class AuthForm extends StatefulWidget {
#override
_AuthFormState createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
var _isLogin = true;
#override
Widget build(BuildContext context) {
final _formKey = GlobalKey<FormState>();
var _userEmail = '';
var _userName = '';
var _userPassword = '';
void _trySubmit() {
final isValid = _formKey.currentState.validate();
FocusScope.of(context).unfocus();
if (isValid) {
_formKey.currentState.save();
print(_userEmail);
print(_userPassword);
print(_userName);
}
}
return MaterialApp(
home: Scaffold(
body: 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(
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter the valid email address.';
}
return null;
},
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email Address',
),
onSaved: (newValue) {
_userEmail = newValue;
},
),
TextFormField(
validator: (value) {
if (value.isEmpty || value.length < 4) {
return 'Please enter at least 4 character.';
}
return null;
},
decoration: InputDecoration(
labelText: 'Username',
),
onSaved: (newValue) {
_userName = newValue;
},
),
TextFormField(
validator: (value) {
if (value.isEmpty || value.length < 7) {
return 'Password must be at least 7 character long.';
}
return null;
},
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
onSaved: (newValue) {
_userPassword = newValue;
},
),
SizedBox(
height: 20,
),
RaisedButton(
onPressed: () {
_trySubmit();
},
child: Text(
_isLogin ? 'Login' : 'Signup',
),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Text(
_isLogin
? 'Create New Account.'
: 'I Already Have Account.',
),
onPressed: () {
setState(() {
_isLogin = !_isLogin;
});
},
),
],
),
),
),
),
),
),
),
);
}
}
Let me know if it works.
So I'm actually pretty new with flutter and this is my first project. Everything went well, until this problem came up.
Recently, I added a stepperBody into my page and in one of the steps there should be a datePicker integrated. Unfortunately, I can't really resolve the problem that is coming up.
I'm constantly getting an "Only static members can be accessed in initializers." error.
Help is appreciated.
I'm using the flutter_datetime_picker plugin.
Here's my code:
class StepperBody extends StatefulWidget {
#override
_StepperBodyState createState() => _StepperBodyState();
}
class _StepperBodyState extends State<StepperBody> {
String _date = "Not set";
int currStep = 0;
static var _focusNode = FocusNode();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
static MyData data = MyData();
#override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {});
print('Has focus: $_focusNode.hasFocus');
});
}
List<Step> steps = [
Step(
title: const Text('Name',
style: TextStyle(
fontFamily: 'Maax',
fontSize: 20,
),),
//subtitle: const Text('Enter your name'),
isActive: true,
//state: StepState.error,
state: StepState.indexed,
content: Form(
key: formKeys[0],
child: Column(
children: <Widget>[
TextFormField(
style: TextStyle(
fontFamily: 'Maax',
fontWeight: FontWeight.w500,
),
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (String value) {
data.title = value;
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Please enter title';
}
},
decoration: InputDecoration(
labelText: 'What is the name of the test?',
hintText: 'Enter a name',
//filled: true,
icon: const FaIcon(FontAwesomeIcons.signature),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid, fontFamily: 'Maax', fontWeight: FontWeight.w500)),
),
],
),
)),
Step(
title: const Text('Date'),
//subtitle: const Text('Subtitle'),
isActive: true,
//state: StepState.editing,
state: StepState.indexed,
content: Form(
key: formKeys[1],
child: Column(
children: <Widget>[
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
elevation: 4.0,
onPressed: () {
DatePicker.showDatePicker(context,
theme: DatePickerTheme(
containerHeight: 210.0,
),
showTitleActions: true,
minTime: DateTime(2000, 1, 1),
maxTime: DateTime(2022, 12, 31), onConfirm: (date) {
print('confirm $date');
_date = '${date.year} - ${date.month} - ${date.day}';
setState(() {});
}, currentTime: DateTime.now(), locale: LocaleType.en);
},
child: Container(
alignment: Alignment.center,
height: 50.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Icon(
Icons.date_range,
size: 18.0,
color: Colors.teal,
),
Text(
" $_date",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
],
),
)
],
),
Text(
" Change",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
],
),
),
color: Colors.white,
),
TextFormField(
keyboardType: TextInputType.phone,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please enter valid number';
}
},
onSaved: (String value) {
data.days = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your number',
hintText: 'Enter a number',
icon: const Icon(Icons.phone),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Email'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
// state: StepState.disabled,
content: Form(
key: formKeys[2],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
autocorrect: false,
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter valid email';
}
},
onSaved: (String value) {
data.words = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your email',
hintText: 'Enter a email address',
icon: const Icon(Icons.email),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Age'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
content: Form(
key: formKeys[3],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.number,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length > 2) {
return 'Please enter valid age';
}
},
maxLines: 1,
onSaved: (String value) {
data.rep = value;
},
decoration: InputDecoration(
labelText: 'Enter your age',
hintText: 'Enter age',
icon: const Icon(Icons.explicit),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
// Step(
// title: const Text('Fifth Step'),
// subtitle: const Text('Subtitle'),
// isActive: true,
// state: StepState.complete,
// content: const Text('Enjoy Step Fifth'))
];
#override
Widget build(BuildContext context) {
void showSnackBarMessage(String message,
[MaterialColor color = Colors.red]) {
Scaffold
.of(context)
.showSnackBar( SnackBar(content: Text(message)));
}
void _submitDetails() {
final FormState formState = _formKey.currentState;
if (!formState.validate()) {
showSnackBarMessage('Please enter correct data');
} else {
formState.save();
print("Name: ${data.title}");
print("Phone: ${data.days}");
print("Email: ${data.words}");
print("Age: ${data.rep}");
showDialog(
context: context,
child: AlertDialog(
title: Text("Details"),
//content: Text("Hello World"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Name : " + data.title),
Text("Phone : " + data.days),
Text("Email : " + data.words),
Text("Age : " + data.rep),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
}
}
return Container(
child: Form(
key: _formKey,
child: ListView(children: <Widget>[
Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: this.currStep,
onStepContinue: () {
setState(() {
if(formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
// else {
// Scaffold
// .of(context)
// .showSnackBar( SnackBar(content: Text('$currStep')));
// if (currStep == 1) {
// print('First Step');
// print('object' + FocusScope.of(context).toStringDeep());
// }
// }
});
},
onStepCancel: () {
setState(() {
if (currStep > 0) {
currStep = currStep - 1;
} else {
currStep = 0;
}
});
},
onStepTapped: (step) {
setState(() {
currStep = step;
});
},
),
RaisedButton(
child: Text(
'Save details',
style: TextStyle(color: Colors.white),
),
onPressed: _submitDetails,
color: Colors.blue,
),
]),
));
}
}
You can copy paste run full code below
You can move steps definition to build to avoid Only static members can be accessed in initializers and allow _date to change after setState
To avoid known issue of flutter_datetime_picker, I use this git
flutter_datetime_picker:
git:
url: https://github.com/derohimat/flutter_datetime_picker.git
code snippet
#override
Widget build(BuildContext context) {
steps = [
Step(
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';
class MyData {
String title;
String days;
String words;
String rep;
MyData({this.title, this.days, this.words, this.rep});
}
class StepperBody extends StatefulWidget {
#override
_StepperBodyState createState() => _StepperBodyState();
}
class _StepperBodyState extends State<StepperBody> {
String _date = "Not set";
int currStep = 0;
static var _focusNode = FocusNode();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
static MyData data = MyData();
List<GlobalKey<FormState>> formKeys = [
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>(),
GlobalKey<FormState>()
];
List<Step> steps = [];
#override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {});
print('Has focus: $_focusNode.hasFocus');
});
}
#override
Widget build(BuildContext context) {
steps = [
Step(
title: const Text(
'Name',
style: TextStyle(
fontFamily: 'Maax',
fontSize: 20,
),
),
//subtitle: const Text('Enter your name'),
isActive: true,
//state: StepState.error,
state: StepState.indexed,
content: Form(
key: formKeys[0],
child: Column(
children: <Widget>[
TextFormField(
style: TextStyle(
fontFamily: 'Maax',
fontWeight: FontWeight.w500,
),
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (String value) {
data.title = value;
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Please enter title';
}
},
decoration: InputDecoration(
labelText: 'What is the name of the test?',
hintText: 'Enter a name',
//filled: true,
icon: Icon(Icons.add),
labelStyle: TextStyle(
decorationStyle: TextDecorationStyle.solid,
fontFamily: 'Maax',
fontWeight: FontWeight.w500)),
),
],
),
)),
Step(
title: const Text('Date'),
//subtitle: const Text('Subtitle'),
isActive: true,
//state: StepState.editing,
state: StepState.indexed,
content: Form(
key: formKeys[1],
child: Column(
children: <Widget>[
RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
elevation: 4.0,
onPressed: () {
DatePicker.showDatePicker(context,
theme: DatePickerTheme(
containerHeight: 210.0,
),
showTitleActions: true,
minTime: DateTime(2000, 1, 1),
maxTime: DateTime(2022, 12, 31), onConfirm: (date) {
print('confirm $date');
_date = '${date.year} - ${date.month} - ${date.day}';
print('date ${_date}');
setState(() {});
}, currentTime: DateTime.now(), locale: LocaleType.en);
},
child: Container(
alignment: Alignment.center,
height: 50.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Icon(
Icons.date_range,
size: 18.0,
color: Colors.teal,
),
Text(
" $_date",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
],
),
)
],
),
Text(
" Change",
style: TextStyle(
color: Colors.teal,
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
],
),
),
color: Colors.white,
),
TextFormField(
keyboardType: TextInputType.phone,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please enter valid number';
}
},
onSaved: (String value) {
data.days = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your number',
hintText: 'Enter a number',
icon: const Icon(Icons.phone),
labelStyle: TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Email'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
// state: StepState.disabled,
content: Form(
key: formKeys[2],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
autocorrect: false,
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter valid email';
}
},
onSaved: (String value) {
data.words = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your email',
hintText: 'Enter a email address',
icon: const Icon(Icons.email),
labelStyle: TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Age'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
content: Form(
key: formKeys[3],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.number,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length > 2) {
return 'Please enter valid age';
}
},
maxLines: 1,
onSaved: (String value) {
data.rep = value;
},
decoration: InputDecoration(
labelText: 'Enter your age',
hintText: 'Enter age',
icon: const Icon(Icons.explicit),
labelStyle: TextStyle(
decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
// Step(
// title: const Text('Fifth Step'),
// subtitle: const Text('Subtitle'),
// isActive: true,
// state: StepState.complete,
// content: const Text('Enjoy Step Fifth'))
];
void showSnackBarMessage(String message,
[MaterialColor color = Colors.red]) {
Scaffold.of(context).showSnackBar(SnackBar(content: Text(message)));
}
void _submitDetails() {
final FormState formState = _formKey.currentState;
if (!formState.validate()) {
showSnackBarMessage('Please enter correct data');
} else {
formState.save();
print("Name: ${data.title}");
print("Phone: ${data.days}");
print("Email: ${data.words}");
print("Age: ${data.rep}");
showDialog(
context: context,
child: AlertDialog(
title: Text("Details"),
//content: Text("Hello World"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Name : " + data.title),
Text("Phone : " + data.days),
Text("Email : " + data.words),
Text("Age : " + data.rep),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
}
}
return Container(
child: Form(
key: _formKey,
child: ListView(children: <Widget>[
Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: this.currStep,
onStepContinue: () {
setState(() {
if (formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
// else {
// Scaffold
// .of(context)
// .showSnackBar( SnackBar(content: Text('$currStep')));
// if (currStep == 1) {
// print('First Step');
// print('object' + FocusScope.of(context).toStringDeep());
// }
// }
});
},
onStepCancel: () {
setState(() {
if (currStep > 0) {
currStep = currStep - 1;
} else {
currStep = 0;
}
});
},
onStepTapped: (step) {
setState(() {
currStep = step;
});
},
),
RaisedButton(
child: Text(
'Save details',
style: TextStyle(color: Colors.white),
),
onPressed: _submitDetails,
color: Colors.blue,
),
]),
));
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: "test",),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: StepperBody()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
I cannot validate Steps individually. I can validate fields in a Stepper, problem is that I want to validate fields before going to the next step.
Creating multiple forms is not a good option. I tried that and it is not practical. Is there any way to either:
Detect the current step and validate fields only inside that step
A simple way to validate step by step, not on submit
Use List<GlobalKey<FormState>> to control each step's validation
When user click continue do formKeys[currStep].currentState.validate()
You can copy paste run full code below
code snippet
List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];
...
return Container(
child: Form(
key: _formKey,
child: ListView(children: <Widget>[
Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: this.currStep,
onStepContinue: () {
setState(() {
if(formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
});
},
...
List<Step> steps = [
Step(
title: const Text('Name'),
//subtitle: const Text('Enter your name'),
isActive: true,
//state: StepState.error,
state: StepState.indexed,
content: Form(
key: formKeys[0],
child: Column(
children: <Widget>[
TextFormField(
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (String value) {
data.name = value;
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Please enter name';
}
},
decoration: InputDecoration(
labelText: 'Enter your name',
hintText: 'Enter a name',
//filled: true,
icon: const Icon(Icons.person),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp( MyApp());
}
List<GlobalKey<FormState>> formKeys = [GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>(), GlobalKey<FormState>()];
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppScreenMode();
}
}
class MyData {
String name = '';
String phone = '';
String email = '';
String age = '';
}
class MyAppScreenMode extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.lightGreen,
),
home: Scaffold(
appBar: AppBar(
title: Text('Steppers'),
),
body: StepperBody(),
));
}
}
class StepperBody extends StatefulWidget {
#override
_StepperBodyState createState() => _StepperBodyState();
}
class _StepperBodyState extends State<StepperBody> {
int currStep = 0;
static var _focusNode = FocusNode();
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
static MyData data = MyData();
#override
void initState() {
super.initState();
_focusNode.addListener(() {
setState(() {});
print('Has focus: $_focusNode.hasFocus');
});
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
List<Step> steps = [
Step(
title: const Text('Name'),
//subtitle: const Text('Enter your name'),
isActive: true,
//state: StepState.error,
state: StepState.indexed,
content: Form(
key: formKeys[0],
child: Column(
children: <Widget>[
TextFormField(
focusNode: _focusNode,
keyboardType: TextInputType.text,
autocorrect: false,
onSaved: (String value) {
data.name = value;
},
maxLines: 1,
//initialValue: 'Aseem Wangoo',
validator: (value) {
if (value.isEmpty || value.length < 1) {
return 'Please enter name';
}
},
decoration: InputDecoration(
labelText: 'Enter your name',
hintText: 'Enter a name',
//filled: true,
icon: const Icon(Icons.person),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Phone'),
//subtitle: const Text('Subtitle'),
isActive: true,
//state: StepState.editing,
state: StepState.indexed,
content: Form(
key: formKeys[1],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.phone,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length < 10) {
return 'Please enter valid number';
}
},
onSaved: (String value) {
data.phone = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your number',
hintText: 'Enter a number',
icon: const Icon(Icons.phone),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Email'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
// state: StepState.disabled,
content: Form(
key: formKeys[2],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.emailAddress,
autocorrect: false,
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Please enter valid email';
}
},
onSaved: (String value) {
data.email = value;
},
maxLines: 1,
decoration: InputDecoration(
labelText: 'Enter your email',
hintText: 'Enter a email address',
icon: const Icon(Icons.email),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
Step(
title: const Text('Age'),
// subtitle: const Text('Subtitle'),
isActive: true,
state: StepState.indexed,
content: Form(
key: formKeys[3],
child: Column(
children: <Widget>[
TextFormField(
keyboardType: TextInputType.number,
autocorrect: false,
validator: (value) {
if (value.isEmpty || value.length > 2) {
return 'Please enter valid age';
}
},
maxLines: 1,
onSaved: (String value) {
data.age = value;
},
decoration: InputDecoration(
labelText: 'Enter your age',
hintText: 'Enter age',
icon: const Icon(Icons.explicit),
labelStyle:
TextStyle(decorationStyle: TextDecorationStyle.solid)),
),
],
),
)),
// Step(
// title: const Text('Fifth Step'),
// subtitle: const Text('Subtitle'),
// isActive: true,
// state: StepState.complete,
// content: const Text('Enjoy Step Fifth'))
];
#override
Widget build(BuildContext context) {
void showSnackBarMessage(String message,
[MaterialColor color = Colors.red]) {
Scaffold
.of(context)
.showSnackBar( SnackBar(content: Text(message)));
}
void _submitDetails() {
final FormState formState = _formKey.currentState;
if (!formState.validate()) {
showSnackBarMessage('Please enter correct data');
} else {
formState.save();
print("Name: ${data.name}");
print("Phone: ${data.phone}");
print("Email: ${data.email}");
print("Age: ${data.age}");
showDialog(
context: context,
child: AlertDialog(
title: Text("Details"),
//content: Text("Hello World"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Name : " + data.name),
Text("Phone : " + data.phone),
Text("Email : " + data.email),
Text("Age : " + data.age),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
));
}
}
return Container(
child: ListView(children: <Widget>[
Stepper(
steps: steps,
type: StepperType.vertical,
currentStep: this.currStep,
onStepContinue: () {
setState(() {
if(formKeys[currStep].currentState.validate()) {
if (currStep < steps.length - 1) {
currStep = currStep + 1;
} else {
currStep = 0;
}
}
// else {
// Scaffold
// .of(context)
// .showSnackBar( SnackBar(content: Text('$currStep')));
// if (currStep == 1) {
// print('First Step');
// print('object' + FocusScope.of(context).toStringDeep());
// }
// }
});
},
onStepCancel: () {
setState(() {
if (currStep > 0) {
currStep = currStep - 1;
} else {
currStep = 0;
}
});
},
onStepTapped: (step) {
setState(() {
currStep = step;
});
},
),
RaisedButton(
child: Text(
'Save details',
style: TextStyle(color: Colors.white),
),
onPressed: _submitDetails,
color: Colors.blue,
),
]));
}
}