Flutter LinearProgressIndicator doesn't work - flutter

I've been learning flutter for a while and got stuck with a not working ProgressIndicator. The problem is that it doesn't detect any error, and progressbar doesn't update anyway when you enter a password into TextFormField. And if you have any comments about the code itself, I'd love to read it and learn something new
import 'RegisterThree.dart';
import 'package:flutter/material.dart';
class RegisterSec extends StatefulWidget {
const RegisterSec({Key? key}) : super(key: key);
#override
_RegisterPage2 createState() => _RegisterPage2();
}
class _RegisterPage2 extends State<RegisterSec> {
final _formKey = GlobalKey<FormState>();
// regular expression to check if string
RegExp pass_valid = RegExp(r"(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)");
double password_strength = 0;
bool validatePassword(String pass) {
String _password = pass.trim();
if (_password.isEmpty) {
setState(() {
password_strength = 0;
});
} else if (_password.length < 6) {
setState(() {
password_strength = 1 / 4;
});
} else if (_password.length < 8) {
setState(() {
password_strength = 2 / 4;
});
} else {
if (pass_valid.hasMatch(_password)) {
setState(() {
password_strength = 4 / 4;
});
return true;
} else {
setState(() {
password_strength = 3 / 4;
});
return false;
}
}
return false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Container(
width: double.infinity,
key: _formKey,
child: Column(
children: <Widget>[
SizedBox(height: 75),
SizedBox(
child: Text(
'Create a new password',
style: TextStyle(fontSize: 40),
textAlign: TextAlign.center,
)),
Padding(
padding: const EdgeInsets.all(20.0),
child: TextFormField(
onChanged: (value) {
_formKey.currentState!.validate();
},
validator: (value) {
if (value!.isEmpty) {
return "Password";
} else {
//call function to check password
bool result = validatePassword(value);
if (result) {
// create account event
return null;
} else {
return "Password should contain Capital, small letter & Number & Special";
}
}
},
decoration: InputDecoration(
border: UnderlineInputBorder(), hintText: "Password"),
obscureText: true,
),
),
Padding(
padding: EdgeInsets.all(20.0),
child: TextFormField(
obscureText: true,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Confirm password',
),
),
),
Padding(
padding: const EdgeInsets.all(20),
child: LinearProgressIndicator(
value: password_strength,
backgroundColor: Colors.grey[300],
minHeight: 5,
color: password_strength <= 1 / 4
? Colors.red
: password_strength == 2 / 4
? Colors.yellow
: password_strength == 3 / 4
? Colors.blue
: Colors.green,
),
),
SizedBox(
child: Text(
'Password should contain Capital, small letter & Number & Special',
style: TextStyle(
color: Color.fromARGB(255, 131, 131, 131),
fontSize: 13,
),
textAlign: TextAlign.left,
)),
SizedBox(
height: 30,
),
SizedBox(
child: ElevatedButton(
onPressed: password_strength != 1
? null
: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RegisterThree()));
},
child: Icon(Icons.navigate_next, color: Colors.white),
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(20),
primary: Colors.blue, // <-- Button color
onPrimary: Colors.white, // <-- Splash color
),
)),
SizedBox(
height: 280,
),
TextButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => HomePage()));
},
child: Text(
'Go back',
style: TextStyle(color: Colors.blue, fontSize: 15),
),
),
],
),
),
);
}
}

The reason why it does not work is that currentState is null. It is null because you have to wrap your TextFormField in a form. And to this form you have to pass the formKey, not the Container
Form(
key: _formKey,
child: Container(
width: double.infinity,
child: ...

Related

Flutter - How to only update/rebuild the child widget and not parent?

I have the following code where 'Snag' is the parent widget and 'Category Display' is the child widget. When I press 'CATEGORY' button the display updates but the entire 'SNAG' is also rebuild as a result I get a flicker on the screen.
Please see attached:
What is it that I am doing wrong here please?
My code is as follows:
class Snags extends StatefulWidget {
#override
_SnagsState createState() => _SnagsState();
}
class _SnagsState extends State<Snags> {
final _formKey = GlobalKey<FormState>();
TextEditingController _locationController = TextEditingController();
TextEditingController _conditionController = TextEditingController();
TextEditingController _recommendationController = TextEditingController();
TextEditingController _assetController = TextEditingController();
bool _newSnag = false;
bool _busy = false;
bool loaded = false;
late Snag? _snag;
late InfoHelper _info;
late StatusCodes _statusCodes;
late String _imagePath;
late String _status;
late String _siteID;
double maxImageSize = 700;
_getSnagObject() async {
if (loaded) return;
_info = ModalRoute
.of(context)!
.settings
.arguments as InfoHelper;
_siteID = _info.siteID; // Never NULL
_newSnag = _info.problemUID == null ? true : false;
if (!_newSnag) {
_snag = await MyDatabase.db.getSingleSnag(_info.problemUID!);
}
_statusCodes = await Provider.of<CP>(context,listen: false).getStatusCodes();
_imagePath = _newSnag ? '' : _snag!.imagePath;
_status = _newSnag ? '1' : _snag!.Status;
_locationController.text = _newSnag ? '' : _snag!.location;
_conditionController.text = _newSnag ? '' : _snag!.condition;
_recommendationController.text = _newSnag ? '' : _snag!.recommendation;
_assetController.text = _newSnag ? '' : _snag!.asset;
loaded = true;
}
deleteImage() async {
if (await File(_imagePath).exists()) await File(_imagePath).delete();
setState(() => _imagePath = '');
Navigator.pop(context);
}
annotatePicture() {
Navigator.pushNamed(context, '/imageMarkup', arguments: _imagePath).then((value) {
if (value != null)
setState(() {
_imagePath = value as String;
});
Navigator.pop(context);
});
}
void _openGallery() async {
XFile? picture = await ImagePicker().pickImage(
source: ImageSource.gallery,
maxHeight: maxImageSize,
maxWidth: maxImageSize,
);
await savePicture(picture);
}
void _openCamera() async {
final _picker = ImagePicker();
XFile? picture = await _picker.pickImage(
source: ImageSource.camera,
maxHeight: maxImageSize,
maxWidth: maxImageSize,
);
await savePicture(picture);
}
Future optionsDialogBoxWithDELorEDIT(BuildContext context1,
VoidCallback callBackDelete, VoidCallback? callBackEdit) {
return showDialog(
context: context1,
builder: (BuildContext context) {
return AlertDialog(
content: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
GestureDetector(
child: new Text('Delete picture'),
onTap: callBackDelete,
),
SizedBox(height: 26.0,),
GestureDetector(
child: new Text('Annotate picture'),
onTap: callBackEdit,
),
],
),
),
);
});
}
Future<void> _optionsDialogBox() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: new SingleChildScrollView(
child: new ListBody(
children: <Widget>[
GestureDetector(
child: new Text('Take a picture'),
onTap: () => _openCamera(),
),
Padding(
padding: EdgeInsets.all(8.0),
),
GestureDetector(
child: new Text('Select from gallery'),
onTap: () => _openGallery(),
),
],
),
),
);
});
}
savePicture(XFile? picture) async {
if (picture != null) {
try {
final String path = (await getApplicationDocumentsDirectory()).path;
final String completePath = p.join(path, '${DateTime.now().toIso8601String() + ".png"}');
await picture.saveTo(completePath);
setState(() => _imagePath = completePath);
if (kDebugMode) print('IMAGE SAVED TO: $_imagePath');
} on MissingPlatformDirectoryException catch (e) {
if (kDebugMode) print(e);
MyFirebaseServices.recordNonFatalError(message: 'Could not save image. Error = $e');
}
// TRY SAVE IT IN EXTERNAL STORAGE AS WELL
try {
if ((await Provider.of<CP>(context, listen: false).getProfile())!.saveExternally) {
final String path = (await getExternalStorageDirectory())!.path;
final String completePath = p.join(path, '${DateTime.now().toIso8601String() + ".png"}');
await picture.saveTo(completePath);
}
} on MissingPlatformDirectoryException catch (e) {
if (kDebugMode) print(e);
MyFirebaseServices.recordNonFatalError(message: 'Could not save image. Error = $e');
}
}
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getSnagObject(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.done){
if (snapshot.hasError){
if (kDebugMode) {
print (snapshot.stackTrace);
} else {
MyFirebaseServices.logMessage(message: 'Snag - snapshot error ${snapshot.error}');
}
return ErrorStatus(message: 'Error: Please go back and try again!');
}
return Scaffold(
appBar: AppBar(
flexibleSpace: TitleBarBackground(),
title: Text('SNAG', style: TextStyle(color: Theme.of(context).colorScheme.onPrimary)),
backgroundColor: Theme.of(context).colorScheme.primary,
elevation: 1.0,
leading: IconButton(
padding: EdgeInsets.only(left: 8.0),
icon: Icon(
Icons.arrow_back,
color: Theme.of(context).colorScheme.onPrimary,
),
onPressed: () => Navigator.pop(context),
), ),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
GestureDetector(
onTap: () async =>
_imagePath.isEmpty
? await _optionsDialogBox()
: await optionsDialogBoxWithDELorEDIT(context, deleteImage, annotatePicture),
child: MainImage(
imagePath: _imagePath,
),
),
SafeArea(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
controller: _locationController,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[a-zA-Z0-9-,. ]'))],
maxLines: 1,
maxLength: 30,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Location',
prefixIcon: Icon(
Icons.add_location,
size: 35.0,
color: Theme
.of(context)
.colorScheme
.primary,
)),
validator: (value) =>
value == null || value.isEmpty
? '*REQUIRED*'
: null,
),
TextFormField(
controller: _assetController,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[a-zA-Z0-9-,. ]'))],
maxLines: 1,
maxLength: 40,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Asset',
prefixIcon: Icon(
Icons.web_asset,
size: 35.0,
color: Theme
.of(context)
.colorScheme
.primary,
)),
validator: (value) =>
value == null || value.isEmpty
? '*REQUIRED*'
: null,
),
TextFormField(
controller: _conditionController,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[a-zA-Z0-9-,. ]'))],
maxLines: 1,
maxLength: 50,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Condition',
prefixIcon: Icon(
Icons.star,
size: 35.0,
color: Theme
.of(context)
.colorScheme
.primary,
)),
validator: (value) =>
value == null || value.isEmpty
? '*REQUIRED*'
: null,
),
TextFormField(
controller: _recommendationController,
inputFormatters: [FilteringTextInputFormatter.allow(RegExp('[a-zA-Z0-9-,. ]'))],
maxLines: 4,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Description',
prefixIcon: Icon(
Icons.description,
size: 35.0,
color: Theme
.of(context)
.colorScheme
.primary,
)),
validator: (value) =>
value == null || value.isEmpty
? '*REQUIRED*'
: null,
),
],
),
),
SizedBox(
height: 10.0,
),
CategoryDisplay(currentSelection:currentSelection(_status), statusCodes: _statusCodes, callBackFunction: changeSelection,),
SizedBox(
height: 20.0,
),
SaveButton(
label: 'SAVE',
busy: _busy,
callBackfunction: () async {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
if (!_busy) {
setState(() => _busy = true);
if (_newSnag) {
var uid = Uuid();
Snag snag = Snag(
id: uid.v4(),
siteID: _siteID,
imagePath: _imagePath,
Status: _status,
recommendation: _recommendationController.text,
condition: _conditionController.text,
asset: _assetController.text,
location: _locationController.text,
);
await MyDatabase.db.insertSnag(snag);
await Future.delayed(Duration(milliseconds: 250));
Navigator.pop(context, snag);
} else {
Snag snag = Snag(
id: _snag!.id,
siteID: _snag!.siteID,
imagePath: _imagePath,
Status: _status,
recommendation: _recommendationController.text,
condition: _conditionController.text,
asset: _assetController.text,
location: _locationController.text,
);
await MyDatabase.db.updateSnag(snag);
await Future.delayed(Duration(milliseconds: 250));
Navigator.pop(context, snag);
}
setState(() => _busy = false);
}
}
},
)
],
),
),
),
],
),
),
);
}
return BusyStatus(message: 'Loading...');
},
);
}
changeSelection(String selection){
setState(() {
_status = selection;
});
}
currentSelection(String status){
//If it is String then send INT
int? s = int.tryParse(status);
if (s!=null) return s;
// If it is old String text then send correct INT conversion
if (status == 'OK') status = "1";
if (status == 'OBS') status = "2";
if (status == 'CAT3') status = "3";
if (status == 'CAT2') status = "4";
if (status == 'CAT1') status = "5";
print (status);
return int.parse(status);
}
}
class CategoryDisplay extends StatelessWidget {
const CategoryDisplay({required this.callBackFunction, required this.currentSelection, required this.statusCodes, Key? key}) : super(key: key);
final Function callBackFunction;
final int currentSelection;
final StatusCodes statusCodes;
String _getDescription(int status) {
if (status == 1) return statusCodes.aDes;
if (status == 2) return statusCodes.bDes;
if (status == 3) return statusCodes.cDes;
if (status == 4) return statusCodes.dDes;
if (status == 5) return statusCodes.eDes;
return ('SELECT STATUS');
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text('CATEGORY', style: Theme.of(context).textTheme.subtitle1,),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () => callBackFunction("1"),
child: Container(
padding: EdgeInsets.all(8.0),
width: MediaQuery
.of(context)
.size
.width / 6,
decoration: BoxDecoration(
color: currentSelection == 1
? Theme
.of(context)
.colorScheme
.primary
: Colors.transparent,
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(16.0)),
border: Border.all(
color: Theme
.of(context)
.colorScheme
.primary,
style: BorderStyle.solid,
width: 2.0,
),
),
child: Center(
child: Text(
statusCodes.a,
style: TextStyle(
color: currentSelection == 1
? Colors.white
: Theme
.of(context)
.colorScheme
.primary),
),
),
),
),
GestureDetector(
onTap: () => callBackFunction("2"),
child: Container(
padding: EdgeInsets.all(8.0),
width: MediaQuery
.of(context)
.size
.width / 6,
decoration: BoxDecoration(
color: currentSelection == 2
? Theme
.of(context)
.colorScheme
.primary
: Colors.transparent,
//borderRadius: BorderRadius.only(topRight: Radius.circular(16.0)),
border: Border.all(
color: Theme
.of(context)
.colorScheme
.primary,
style: BorderStyle.solid,
width: 2.0,
),
),
child: Center(
child: Text(
statusCodes.b,
style: TextStyle(
color: currentSelection == 2
? Colors.white
: Theme
.of(context)
.colorScheme
.primary),
),
),
),
),
GestureDetector(
onTap: () => callBackFunction("3"),
child: Container(
padding: EdgeInsets.all(8.0),
width: MediaQuery
.of(context)
.size
.width / 6,
decoration: BoxDecoration(
color: currentSelection == 3
? Theme
.of(context)
.colorScheme
.primary
: Colors.transparent,
//borderRadius: BorderRadius.only(bottomLeft: Radius.circular(16.0)),
border: Border.all(
color: Theme
.of(context)
.colorScheme
.primary,
style: BorderStyle.solid,
width: 2.0,
),
),
child: Center(
child: Text(
statusCodes.c,
style: TextStyle(
color: currentSelection == 3
? Colors.white
: Theme
.of(context)
.colorScheme
.primary),
),
),
),
),
GestureDetector(
onTap: () => callBackFunction("4"),
child: Container(
padding: EdgeInsets.all(8.0),
width: MediaQuery
.of(context)
.size
.width / 6,
decoration: BoxDecoration(
color: currentSelection == 4
? Theme
.of(context)
.colorScheme
.primary
: Colors.transparent,
//borderRadius: BorderRadius.only(bottomLeft: Radius.circular(16.0)),
border: Border.all(
color: Theme
.of(context)
.colorScheme
.primary,
style: BorderStyle.solid,
width: 2.0,
),
),
child: Center(
child: Text(
statusCodes.d,
style: TextStyle(
color: currentSelection == 4
? Colors.white
: Theme
.of(context)
.colorScheme
.primary),
),
),
),
),
GestureDetector(
onTap: () => callBackFunction("5"),
child: Container(
padding: EdgeInsets.all(8.0),
width: MediaQuery
.of(context)
.size
.width / 6,
decoration: BoxDecoration(
color: currentSelection == 5
? Theme
.of(context)
.colorScheme
.primary
: Colors.transparent,
borderRadius: BorderRadius.only(topRight: Radius.circular(16.0)),
border: Border.all(
color: Theme
.of(context)
.colorScheme
.primary,
style: BorderStyle.solid,
width: 2.0,
),
),
child: Center(
child: Text(
statusCodes.e,
style: TextStyle(
color: currentSelection == 5
? Colors.white
: Theme
.of(context)
.colorScheme
.primary),
),
),
),
),
],
),
SizedBox(height: 4,),
Center(
child: Text(
_getDescription(currentSelection),
textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
)),
],
);
}
}
You are updating your parent, to avoid that change changeSelection to this:
changeSelection(String selection){
_status = selection;
}
then change your CategoryDisplay to StatefulWidget and define new _status inside CategoryDisplay and after that change onTap to this:
GestureDetector(
onTap: () {
setState(() {
_status = 1;
});
callBackFunction("1"),
}
repeat this for all your onTap.

Why does my code give an error of a Null Check Operator used on a null value

Here is the code
I wanted to register a user and I used a FormKey and I got the error Null
check operator use don a null value
here is what the run terminal shows
E/flutter (30851): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: Null check operator used on a null value
E/flutter (30851): #0 _RegisterScreenState.signUp (package:em_home/screens/signing/register_screen.dart:58:29)
class RegisterScreen extends StatefulWidget {
const RegisterScreen({Key? key}) : super(key: key);
#override
_RegisterScreenState createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
showSnackBar(String content, BuildContext context) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(content)));
}
bool _isLoading = false;
final formKey = GlobalKey<FormState>();
String email = "";
String password = "";
String fullName = "";
String dob = "";
String gender = "";
AuthMethods authMethods = AuthMethods();
Uint8List? image;
pickImage(ImageSource source) async {
final ImagePicker imagePicker = ImagePicker();
XFile? _file = await imagePicker.pickImage(source: source);
if (_file != null) {
return await _file.readAsBytes();
}
print("No Image selected");
}
void selectImage() async {
Uint8List im = await pickImage(ImageSource.gallery);
setState(() {
image = im;
});
}
signUp() async {
if (formKey.currentState!.validate()) {
setState(() {
_isLoading = true;
});
await authMethods.registerUser(email: email, password: password, name: fullName, gender: gender, dateOfBirth: dob,)
.then((value) async {
if (value == true) {
await HelperFunctions.saveUserLoggedInStatus(true);
await HelperFunctions.saveUserEmailSF(email);
await HelperFunctions.saveUserNameSF(fullName);
Navigator.pushReplacement(context, SlideLeftRoute(widget: const LoginScreen()));
} else {
showSnackBar(value, context);
setState(() {
_isLoading = false;
});
}
});
}
return null;
}
void navigatetoSignIn() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => const LoginScreen()));
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: backgroundColor,
body: SingleChildScrollView(
child: Container(
color: backgroundColor,
padding: const EdgeInsets.symmetric(horizontal: 15),
width: double.infinity,
child: Column(
children: [
const SizedBox(
height: 30,
),
Container(
alignment: Alignment.topLeft,
child: IconButton(
onPressed: () {
Navigator.pushReplacement(
context, SlideLeftRoute(widget: const LoginScreen()));
},
icon: const Icon(
Icons.arrow_back,
size: 40,
),
),
),
const SizedBox(
height: 20,
),
Image.asset(
'assets/logos.png',
),
const Text(
"Create your account",
style: TextStyle(color: textColor, fontWeight: FontWeight.bold),
),
const SizedBox(
height: 30,
),
Stack(
children: [
// to check if Image is not equal to null
image != null
? CircleAvatar(
radius: 64,
backgroundImage: MemoryImage(image!),
)
: const CircleAvatar(
radius: 64,
backgroundImage: AssetImage(
"assets/default_profile.jpg",
),
),
Positioned(
child: IconButton(
onPressed: selectImage,
icon: const Icon(Icons.add_a_photo),
),
left: 80,
bottom: -10,
)
],
),
const SizedBox(
height: 30,
),
TextFormField(
decoration: textInputDecoration.copyWith(
labelText: "Email",
prefixIcon: Icon(
Icons.email,
)),
onChanged: (val) {
setState(() {
email = val;
});
},
// check tha validation
validator: (val) {
return RegExp(
r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+")
.hasMatch(val!)
? null
: "Please enter a valid email";
},
),
const SizedBox(
height: 20,
),
TextFormField(
obscureText: true,
decoration: textInputDecoration.copyWith(
labelText: "Password",
prefixIcon: Icon(
Icons.lock,
color: backgroundColor,
)),
validator: (val) {
if (val!.length < 6) {
return "Password must be at least 6 characters";
} else {
return null;
}
},
onChanged: (val) {
setState(() {
password = val;
});
},
),
const SizedBox(
height: 20,
),
TextFormField(
decoration: textInputDecoration.copyWith(
labelText: "Full Name",
prefixIcon: Icon(
Icons.person,
)),
onChanged: (val) {
setState(() {
fullName = val;
});
},
validator: (val) {
if (val!.isNotEmpty) {
return null;
} else {
return "Name cannot be empty";
}
},
),
const SizedBox(
height: 20,
),
TextFormField(
decoration: textInputDecoration.copyWith(
labelText: "Date Of Birth DD/MM/YYYY",
prefixIcon: Icon(
Icons.lock,
)),
validator: (val) {},
onChanged: (val) {
setState(() {
dob = val;
});
},
),
const SizedBox(
height: 20,
),
TextFormField(
decoration: textInputDecoration.copyWith(
labelText: "Gender",
prefixIcon: Icon(
Icons.lock,
)),
validator: (val) {},
onChanged: (val) {
setState(() {
gender = val;
});
},
),
const SizedBox(
height: 20,
),
InkWell(
onTap: signUp,
child: Container(
width: double.infinity,
alignment: AlignmentDirectional.center,
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: const ShapeDecoration(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
),
color: buttonColor,
),
child: _isLoading
? const Center(
child: CircularProgressIndicator(
color: iconButtonColor,
),
)
: const Text(
"Sign up",
style: TextStyle(
color: buttonTextColor,
fontWeight: FontWeight.bold),
),
),
),
const SizedBox(
height: 50,
),
Container(
color: backgroundColor,
child: const Text("- or sign in using -"),
),
const SizedBox(
height: 25,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
Navigator.push(context, CustomRoute(widget: const Scaffold(body: Center(child: Text("Coming Soon"),))));
},
child: Container(
width: 50,
height: 45,
color: Colors.white,
child: Image.asset('assets/google.png'),
),
),
const SizedBox(
width: 30,
),
GestureDetector(
onTap: () {
Navigator.push(context, CustomRoute(widget: const Scaffold(body: Center(child: Text("Coming Soon"),))));
},
child: Container(
width: 50,
height: 45,
color: Colors.white,
child: Image.asset('assets/facebook.png'),
),
),
],
),
],
),
),
),
);
}
}
please what do I do here is the snippet to the code
Wrap your UI with the FormFields inside a Form widget and pass the GlobalKey to it.
Quick guide from Flutter docs: https://docs.flutter.dev/cookbook/forms/validation

flutter bloc doesn't update the ui?

when i navigate from NewSales screen to CreateItem screen and add item and press the add button
the item is added to sqflite database then it navigates back to new sales but the state is not updated , it's updated only if i restarted the app
the NewSales screen
class _NewSalesState extends State<NewSales> {
final controller = TextEditingController();
showAlertDialog(BuildContext context,String name) {
// Create button
// Create AlertDialog
AlertDialog alert = AlertDialog(
title: const Text("Alert"),
content: const Text("you want to delete this item?"),
actions: [
TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.green)),
child: const Text("CANCEL", style: TextStyle(color: Colors.white)),
onPressed: () {
Navigator.of(context).pop();
},
),
BlocBuilder<SalesCubit, SalesState>(
builder: (context, state) {
final bloc=BlocProvider.of<SalesCubit>(context);
return TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.red)),
child: const Text(
"DELETE",
style: TextStyle(color: Colors.white),
),
onPressed: () {
Navigator.of(context).pop();
bloc.deleteItem(name).then((value) {
bloc.getAllItems();
});
},
);
},
)
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
// #override
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery
.of(context)
.size;
return BlocConsumer<SalesCubit, SalesState>(
listener: (context, state) {},
builder: (context, state) {
final bloc = BlocProvider.of<SalesCubit>(context);
if (state is SalesInitial) {
bloc.getAllItems();
}
return Scaffold(
drawer: const navDrawer(),
appBar: AppBar(
title: const Text("Ticket"),
actions: [
IconButton(
onPressed: () async {
String barcodeScanRes;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
barcodeScanRes = await FlutterBarcodeScanner.scanBarcode(
'#ff6666', 'Cancel', true, ScanMode.BARCODE);
print('barcodeScanRes $barcodeScanRes');
print(bloc.bsResult);
} on PlatformException {
barcodeScanRes = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
bloc.toggleSearch();
controller.text = barcodeScanRes;
bloc.filterItems(barcodeScanRes);
},
icon: const Icon(Icons.scanner),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: IconButton(
onPressed: () {
Navigator.of(context).pushNamed(Routes.addCustomerRoute);
},
icon: Icon(Icons.person_add)),
),
],
),
body: Column(
children: [
// the first button
Container(
width: double.infinity,
height: deviceSize.height * .1,
color: Colors.grey,
child: GestureDetector(
onTap: ()=>displayMessage("an item was charged successfully", context),
child: Padding(
padding: const EdgeInsets.all(10),
child: Container(
color: Colors.green,
child: const Center(
child: Text("charge",style: TextStyle(color: Colors.white),),
),
),
),
),
),
// the second container
SizedBox(
width: deviceSize.width,
height: deviceSize.height * .1,
child: Row(
children: [
DecoratedBox(
decoration:
BoxDecoration(border: Border.all(color: Colors.grey)),
child: SizedBox(
width: deviceSize.width * .8,
child: bloc.isSearch
? TextFormField(
autofocus: true,
controller: controller,
decoration:
const InputDecoration(hintText: "Search"),
onChanged: (value) {
bloc.filterItems(controller.text);
},
)
: DropdownButtonFormField<String>(
// underline: Container(),
// value: "Discounts",
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text('Please choose type'),
),
items: <String>['Discounts', 'All Items']
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
),
),
),
DecoratedBox(
decoration:
BoxDecoration(border: Border.all(color: Colors.grey)),
child: SizedBox(
width: deviceSize.width * .2,
child: IconButton(
onPressed: () {
bloc.toggleSearch();
if (!bloc.isSearch) {
bloc.filterItems('');
}
},
icon: Icon(
!bloc.isSearch ? Icons.search : Icons.close)),
),
)
],
),
),
// the third container
if (state is IsLoading || state is SalesInitial)
const Center(
child: CircularProgressIndicator(
color: Colors.green,
backgroundColor: Colors.green,
),
),
if (state is Loaded || state is IsSearch || state is SearchDone)
bloc.items.isEmpty
? const Center(
child: Text("no items added yet"),
)
: Expanded(
child: bloc.filteredItems.isEmpty
? const Center(
child: Text(
"no items found with this name",
style: TextStyle(color: Colors.green),
),
)
: ListView.builder(
itemCount: bloc.filteredItems.length,
itemBuilder: (context, i) {
final item = bloc.filteredItems[i];
return Card(
child: ListTile(
leading: CircleAvatar(
backgroundColor: Colors.green,
radius: 20,
child: Text(item.price),
),
title: Text(item.name),
subtitle: Text(item.barcode),
trailing: Column(
children: [
IconButton(
onPressed: () {
showAlertDialog(context,item.name);
// bloc.deleteItem(item.name).then((value) {
// bloc.getAllItems();
// });
},
icon: const Icon(
Icons.delete,
color: Colors.red,
))
],
)),
);
}))
],
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: ()async {
Navigator.of(context).pushNamed(Routes.createItemRoute);
},
),
);
},
);
}
}
the CreateItem screen
class _CreateItemState extends State<CreateItem> {
final nameController = TextEditingController();
final priceController = TextEditingController();
final costController = TextEditingController();
final skuController = TextEditingController();
final barcodeController = TextEditingController();
final inStockController = TextEditingController();
bool each = true; // bool variable for check box
bool isColor = true;
bool switchValue = false;
File? file;
String base64File = "";
String path = "";
final _formKey = GlobalKey<FormState>();
#override
void dispose() {
nameController.dispose();
priceController.dispose();
costController.dispose();
skuController.dispose();
barcodeController.dispose();
inStockController.dispose();
super.dispose();
}
Future pickImage(ImageSource source) async {
File? image1;
XFile imageFile;
final imagePicker = ImagePicker();
final image = await imagePicker.pickImage(source: source);
imageFile = image!;
image1 = File(imageFile.path);
List<int> fileUnit8 = image1.readAsBytesSync();
setState(() {
base64File = base64.encode(fileUnit8);
});
}
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return BlocProvider(
create: (BuildContext context) => CreateItemCubit(),
child: Builder(builder: (context){
final bloc=BlocProvider.of<CreateItemCubit>(context);
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () => Navigator.of(context).pushNamedAndRemoveUntil(Routes.newSalesRoute, (route) => false),
icon: const Icon(Icons.arrow_back)),
title: const Text("Create Item"),
actions: [TextButton(onPressed: () {}, child: const Text("SAVE"))],
),
body: Padding(
padding: const EdgeInsets.all(10),
child: Form(
key: _formKey,
child: ListView(
children: [
TextFormField(
controller: nameController,
cursorColor: ColorManager.black,
decoration: const InputDecoration(hintText: "Name"),
validator: (value) {
if (value!.isEmpty || value.length < 5) {
return "name can't be less than 5 chars";
}
return null;
},
),
gapH24,
Text(
"Category",
style: TextStyle(color: ColorManager.grey),
),
SizedBox(
width: deviceSize.width,
child: DropdownButtonFormField<String>(
// value: "Categories",
hint: const Padding(
padding: EdgeInsets.only(left: 10),
child: Text('No Category'),
),
items: <String>['No Category', 'Create Category']
.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (_) {},
),
),
gapH24,
const Text(
"Sold by",
style: TextStyle(color: Colors.black),
),
gapH24,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: each,
onChanged: (inputValue) {
setState(() {
each = !each;
});
},
),
gapW4,
const Text(
"Each",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
gapH24,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: !each,
onChanged: (inputValue) {
setState(() {
each = !each;
});
},
),
gapW4,
const Text(
"Weight",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
TextFormField(
controller: priceController,
decoration: InputDecoration(
hintText: "Price",
hintStyle: TextStyle(color: ColorManager.grey)),
validator: (value) {
if (value!.isEmpty) {
return "price can,t be empty";
}
return null;
},
),
gapH4,
Text(
"leave the field blank to indicate price upon sale",
style: TextStyle(color: ColorManager.grey),
),
gapH20,
Text(
"Cost",
style: TextStyle(color: ColorManager.grey),
),
TextFormField(
controller: costController,
decoration: InputDecoration(
hintText: "cost",
hintStyle: TextStyle(color: ColorManager.black)),
validator: (value) {
if (value!.isEmpty) {
return "cost can't be empty";
}
return null;
},
),
gapH20,
Text(
"SKU",
style: TextStyle(color: ColorManager.grey),
),
TextFormField(
controller: skuController,
decoration: InputDecoration(
hintText: "Sku",
hintStyle: TextStyle(color: ColorManager.black)),
validator: (value) {
if (value!.isEmpty) {
return "Sku can't be empty";
}
return null;
},
),
gapH30,
TextFormField(
controller: barcodeController,
decoration: InputDecoration(
hintText: "Barcode",
hintStyle: TextStyle(color: ColorManager.grey)),
validator: (value) {
if (value!.isEmpty) {
return "Barcode can't be empty";
}
return null;
},
),
gapH30,
// Divider(thickness: 1,color: ColorManager.black,),
Text(
"Inventory",
style: TextStyle(
color: ColorManager.green,
fontSize: 15,
fontWeight: FontWeight.bold),
),
// ListTile(
// title: Text("TrackStock",style: TextStyle(color: ColorManager.green,fontSize: 15,fontWeight: FontWeight.bold)),
// trailing: Switch(value: switchValue, onChanged: (bool value) {
// setState(() {
// switchValue=!switchValue;
// });
// },),
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("TrackStock",
style: TextStyle(
color: ColorManager.grey,
fontSize: 15,
fontWeight: FontWeight.bold)),
Switch(
value: switchValue,
onChanged: (bool value) {
setState(() {
switchValue = !switchValue;
});
},
),
],
),
gapH10,
if (switchValue)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"in stock",
style:
TextStyle(color: ColorManager.grey, fontSize: 15),
),
TextFormField(
keyboardType: TextInputType.number,
controller: inStockController,
decoration: const InputDecoration(hintText: "0"),
validator: (value) {
if (value!.isEmpty) {
return "value can't be empty";
}
return null;
},
)
],
),
gapH20,
Text(
"Representation in POS",
style: TextStyle(
color: ColorManager.green,
fontSize: 15,
fontWeight: FontWeight.bold),
),
gapH15,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: isColor,
onChanged: (inputValue) {
setState(() {
if (!isColor) {
isColor = !isColor;
}
});
},
),
gapW4,
const Text(
"Color and Shape",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
gapH10,
Row(
children: [
Checkbox(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5.0))), // Rounded Checkbox
value: !isColor,
onChanged: (inputValue) {
setState(() {
if (isColor) {
isColor = !isColor;
}
});
},
),
gapW4,
const Text(
"Image",
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
isColor
? GridView.builder(
physics:
const NeverScrollableScrollPhysics(), // to disable GridView's scrolling
shrinkWrap: true, // You won't see infinite size error
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
crossAxisSpacing: 5.0,
mainAxisSpacing: 5.0,
),
itemCount: containers().length,
itemBuilder: (BuildContext context, int index) {
return containers()[index];
},
)
: Padding(
padding: const EdgeInsets.all(10),
child: Row(
children: [
Expanded(
flex: 1,
child: base64File == ""
? const Icon(Icons.person)
: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(5)),
child: Image.memory(
base64.decode(base64File)))),
gapW4,
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
TextButton(
onPressed: () =>
pickImage(ImageSource.camera),
child: Row(
children: [
Icon(
Icons.camera_alt,
color: ColorManager.black,
),
gapW8,
Text(
"Take a photo",
style: TextStyle(
color: ColorManager.black,
),
)
],
),
),
const Divider(
thickness: 1,
color: Colors.grey,
),
TextButton(
onPressed: () =>
pickImage(ImageSource.gallery),
child: Row(
children: [
Icon(Icons.camera_alt,
color: ColorManager.black),
gapW8,
Text(
"Choose from gallery",
style: TextStyle(
color: ColorManager.black,
),
)
],
),
)
],
),
)
],
),
)
],
),
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.save,color: Colors.white,),
onPressed: () async {
bool isValid = _formKey.currentState!.validate();
if (isValid) {
FocusScope.of(context).unfocus();
ItemModel? item=await bloc.checkItem(nameController.text);
if(item!=null){
displayMessage("this item was inserted before", context);
}else{
int? res=await bloc.saveItem(ItemModel(
nameController.text,
priceController.text,
costController.text,
skuController.text,
barcodeController.text));
if(res!=null){
displayMessage("an item was inserted successfully", context);
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>const NewSales()), (route) => false);
}
}
}
},
),
);
}
),
);
}
}
this is the SaleCubit
class SalesCubit extends Cubit<SalesState> {
SalesCubit() : super(SalesInitial());
bool isSearch=false;
ItemDBHelper db=ItemDBHelper();
List<ItemModel> items=[];
List<ItemModel> filteredItems=[];
String bsResult='Unknown'; //barcode search result
void toggleSearch(){
isSearch=!isSearch;
emit(IsSearch());
}
void setBarcodeResult(String value){
bsResult=value;
emit(SearchDone());
}
void filterItems(String query){
// emit(Searching());
query.isEmpty?
filteredItems=items:
filteredItems=items.where((item) => item.name.toLowerCase().contains(query.toLowerCase())).toList();
emit(SearchDone());
}
Future<List<ItemModel>?> getAllItems()async{
try{
emit(IsLoading());
items=await db.getAllItems();
filteredItems=items;
print(items);
return items;
}finally{
emit(Loaded());
}
}
Future<void> deleteItem(String name)async{
await db.deleteItem(name);
emit(SearchDone());
}
}
this is the SalesState
part of 'sales_cubit.dart';
#immutable
abstract class SalesState {}
class SalesInitial extends SalesState {}
class IsSearch extends SalesState {}
class IsLoading extends SalesState {}
class Loaded extends SalesState {}
class Searching extends SalesState {}
class SearchDone extends SalesState {}

Flutter: Submit TextFormField on Enter

I'm working on a desktop application for Windows with Flutter. I want to listen to keyboard keys. I made a Login page and it has two TextFormFields, (one for Username and the other for Password).
When I press the 'enter' key on the keyboard, I want the form to be submitted as I pressed on 'Login' button. So if I pressed the 'enter' key from either Username or Password text fields, I want the app to act exactly as if I pressed the 'Login' Button.
Here is a picture of the Login Page:
--> Login Page
Here is the code of Login Page:
import 'package:flutter/material.dart';
import '../../api/services/login_services.dart';
import '../../api/models/login_models.dart';
import '../../constants/constant_methods.dart';
import '../main_page/main_screen.dart';
import '../../constants/constant_variables.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController usernameController = TextEditingController();
TextEditingController passwordController = TextEditingController();
late LoginRequestModel loginRequestModel;
bool isUsernameEmpty = false;
bool isPasswordEmpty = false;
bool secure = true;
bool notSecure = false;
bool isLoading = false;
#override
void initState() {
super.initState();
loginRequestModel = LoginRequestModel(
usernameController.text,
passwordController.text,
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: const BoxDecoration(
gradient: loginPageGradient,
),
child: Center(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/cloud_soft_logo.png',
height: 90,
),
const SizedBox(height: 15),
Text(
'Hoomy\'s Real Estate',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
Container(
width: 310,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: borderRadius(10.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 20),
Text(
'Welcome',
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(
fontWeight: FontWeight.w600,
foreground: Paint()..shader = linearGradient),
),
Text(
'Please Log in to Your Account',
style: Theme.of(context).textTheme.headline2,
),
const SizedBox(height: 10),
loginTextField("Username", "Username can't be empty",
usernameController),
loginTextField("Password", "Password can't be empty",
passwordController),
forgetPasswordButton(),
loginButton(context),
const SizedBox(height: 40),
],
),
),
],
),
),
),
),
),
);
}
Widget loginTextField(
String labelText,
String errorText,
TextEditingController controller,
) {
return SizedBox(
width: 250,
child: TextFormField(
decoration: InputDecoration(
labelText: labelText,
errorText: labelText == "Username"
? isUsernameEmpty
? errorText
: null
: isPasswordEmpty
? errorText
: null,
suffixIcon: labelText == "Username"
? IconButton(
onPressed: () {}, icon: const Icon(Icons.person, size: 20))
: IconButton(
onPressed: () {
setState(() {
secure = !secure;
});
},
icon: Icon(
secure ? Icons.visibility_off : Icons.visibility,
size: 20,
),
)),
obscureText: labelText == "Username" ? notSecure : secure,
controller: controller,
),
);
}
Padding forgetPasswordButton() {
return Padding(
padding: const EdgeInsets.all(20).copyWith(right: 30),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Theme(
data: ThemeData(splashColor: Colors.transparent),
child: TextButton(
child: const Text('Forget Password'),
onPressed: () {},
),
),
],
),
);
}
DecoratedBox loginButton(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
borderRadius: borderRadius(50.0),
gradient: loginButtonGradient,
),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.transparent,
fixedSize: const Size(250, 50),
shape: RoundedRectangleBorder(
borderRadius: borderRadius(50.0),
)),
child: isLoading
? CircularProgressIndicator(
color: Colors.blue[800],
backgroundColor: Colors.grey,
)
: const Text('Login'),
onPressed: () {
setState(() {
usernameController.text.isEmpty
? isUsernameEmpty = true
: isUsernameEmpty = false;
passwordController.text.isEmpty
? isPasswordEmpty = true
: isPasswordEmpty = false;
});
setState(() {
isLoading = true;
});
LoginService.login(usernameController.text, passwordController.text)
.then((response) {
setState(() {
isLoading = false;
});
if (response) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const MainScreen(),
),
);
} else if (!response &&
usernameController.text.isNotEmpty &&
passwordController.text.isNotEmpty) {
showErrorDialog(context, "Invalid Username or Password!");
}
});
}),
);
}
Future<dynamic> showErrorDialog(BuildContext context, String dialogContent) {
return showDialog(
context: context,
builder: (BuildContext context) {
return Center(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SizedBox(
width: 350,
child: AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: borderRadius(10.0)),
title: Text("Error",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.red)),
content: Text(dialogContent,
style: Theme.of(context)
.textTheme
.headline2!
.copyWith(color: Colors.black87)),
actions: <Widget>[
TextButton(
child: Text("OK",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.blue)),
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
),
),
);
},
);
}
}
Add the field onFieldSubmitted and a FormKey:
// Widget attribute
final _formKey = GlobalKey<FormState>();
// Widget method: build()
Form(
key: _formKey,
child:
TextFormField(
onFieldSubmitted: (value) {
print('ENTER pressed');
// Will trigger validation for ALL fields in Form.
// All your TextFormFields (email, password) share
// the SAME Form and thus the SAME _formKey.
if (_formKey.currentState!.validate()) {
print('ALL FIELDS ARE VALID, GO ON ...');
}
}
)
)
Docs
https://api.flutter.dev/flutter/material/TextFormField-class.html

Bottom Navigation bar disappears on sign in, but returns when I go to another page and back?

I am trying to create an app that will show the user a generic homepage with products and give them the option to navigate to an account page where, if they are not signed in they will be shown a sign in/sign up page or if they have signed in, they will be shown their profile.
I have managed to get the sign in/sign out part working, but when I sign in and navigate to the profile page, my bottom navigation bar disappears. However, when I navigate to another page and back, it reappears.
Here is my home.dart file which controls navigation for my app:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
PageController _pageController = PageController();
List<Widget> _screens = [HomePage(), InboxPage(), AccountController()];
int _selectedIndex = 0;
void _onPageChanged(int index) {
setState(() {
_selectedIndex = index;
});
}
void _onItemTapped(int selectedIndex) {
_pageController.jumpToPage(selectedIndex);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: PageView(
controller: _pageController,
children: _screens,
onPageChanged: _onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: BottomNavigationBar(onTap: _onItemTapped, items: [
BottomNavigationBarItem(
icon: Icon(
Icons.home,
color: _selectedIndex == 0 ? Colors.blueAccent : Colors.grey,
),
title: Text(
'Home',
style: TextStyle(
color: _selectedIndex == 0 ? Colors.blueAccent : Colors.grey,
),
),
),
BottomNavigationBarItem(
icon: Icon(
Icons.email,
color: _selectedIndex == 1 ? Colors.blueAccent : Colors.grey,
),
title: Text(
'Inbox',
style: TextStyle(
color: _selectedIndex == 1 ? Colors.blueAccent : Colors.grey,
),
),
),
BottomNavigationBarItem(
icon: Icon(
Icons.account_circle,
color: _selectedIndex == 2 ? Colors.blueAccent : Colors.grey,
),
title: Text(
'Account',
style: TextStyle(
color: _selectedIndex == 2 ? Colors.blueAccent : Colors.grey,
),
),
),
]),
);
}
}
My accountcontroller.dart code which helps determine whether a user is logged in or not:
class AccountController extends StatelessWidget {
#override
Widget build(BuildContext context) {
final AuthService auth = Provider.of(context).auth;
return StreamBuilder(
stream: auth.onAuthStateChanged,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
final bool signedIn = snapshot.hasData;
return signedIn ? ProfileView() : SignUpPage();
}
return CircularProgressIndicator();
});
}
}
My profileview.dart:
class ProfileView extends StatefulWidget {
#override
_ProfileViewState createState() => _ProfileViewState();
}
class _ProfileViewState extends State<ProfileView> {
#override
Widget build(BuildContext context) {
return
Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(80),
child: MainAppBar(
text: 'Account',
)),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
Text('Account Page'),
showSignOut(context),
goToHomepage(context),
],
),
),
);
}
}
Widget showSignOut(context) {
return RaisedButton(
child: Text('Sign Out'),
onPressed: () async {
try {
await Provider.of(context).auth.signOut();
Navigator.pushNamed(context, '/homepage');
print('Signed Out');
} catch (e) {
print(e);
}
});
}
Widget goToHomepage(context) {
return RaisedButton(
child: Text('Go to homepage'),
onPressed: () async {
try {
Navigator.pushNamed(context, '/homepage');
} catch (e) {
print(e);
}
});
}
And finally my signup.dart file which contains my sign up/sign in code:
enum AuthFormType { signIn, signUp }
class SignUpPage extends StatefulWidget {
final AuthFormType authFormType;
SignUpPage({Key key, this.authFormType}) : super(key: key);
#override
_SignUpPageState createState() =>
_SignUpPageState(authFormType: this.authFormType);
}
class _SignUpPageState extends State<SignUpPage> {
AuthFormType authFormType;
_SignUpPageState({this.authFormType});
final formKey = GlobalKey<FormState>();
String _firstName,
_lastName,
_email,
_confirmEmail,
_password,
_confirmPassword,
_dateOfBirth;
bool validate() {
final form = formKey.currentState;
form.save();
if (form.validate()) {
form.save();
print('true');
return true;
} else {
print('false');
return false;
}
}
void switchFormState(String state) {
formKey.currentState.reset();
if (state == 'signIn') {
setState(() {
authFormType = AuthFormType.signIn;
});
} else {
setState(() {
authFormType = AuthFormType.signUp;
});
}
}
void submit() async {
final CollectionReference userCollection =
Firestore.instance.collection('UserData');
if (validate()) {
try {
final auth = Provider.of(context).auth;
if (authFormType == AuthFormType.signIn) {
String uid = await auth.signInWithEmailAndPassword(_email, _password);
print("Signed In with ID $uid");
Navigator.of(context).pushReplacementNamed('/home');
} else {
String uid = await auth.createUserWithEmailAndPassword(
_email,
_password,
);
userCollection.document(uid).setData({
'First Name': _firstName,
'Last Name': _lastName,
'Date of Birth': _dateOfBirth
});
print("Signed up with New ID $uid");
Navigator.of(context).pushReplacementNamed('/home');
}
} catch (e) {
print(e);
}
}
}
DateTime selectedDate = DateTime.now();
TextEditingController _date = new TextEditingController();
Future<Null> _selectDate(BuildContext context) async {
DateFormat formatter =
DateFormat('dd/MM/yyyy'); //specifies day/month/year format
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1901, 1),
builder: (BuildContext context, Widget child) {
return Theme(
data: ThemeData.light().copyWith(
colorScheme: ColorScheme.light(
primary: kPrimaryColor,
onPrimary: Colors.black,),
buttonTheme: ButtonThemeData(
colorScheme: Theme.of(context)
.colorScheme
.copyWith(primary: Colors.black),
),
),
child: child,
);
},
lastDate: DateTime(2100));
if (picked != null && picked != selectedDate)
setState(() {
selectedDate = picked;
_date.value = TextEditingValue(
text: formatter.format(
picked)); //Use formatter to format selected date and assign to text field
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(80),
child: MainAppBar(
text: buildAppBarText(),
)),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Center(
child: Container(
child: Column(
children: <Widget>[
Align(
child: Text(buildTitleText(), style: AppBarTextStyle),
),
Container(
padding: EdgeInsets.only(top: 10),
),
Padding(
padding: const EdgeInsets.all(20.0),
child: Form(
key: formKey,
child: Column(
children: buildInputs() + buildSwitchText(),
)),
),
],
),
),
),
),
));
}
buildHeaderText() {
String _headerText;
if (authFormType == AuthFormType.signUp) {
_headerText = "Don't have an account?";
} else {
_headerText = "Already have an account?";
}
return _headerText;
}
List<Widget> buildInputs() {
List<Widget> textFields = [];
DateFormat dateFormat = DateFormat("yyyy-MM-dd HH:mm:ss");
if (authFormType == AuthFormType.signIn) {
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Email'),
validator: SignInEmailValidator.validate,
onSaved: (value) => _email = value.trim()));
textFields.add(SizedBox(
height: 15,
));
textFields.add(
TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Password'),
obscureText: true,
validator: SignInPasswordValidator.validate,
onSaved: (value) => _password = value.trim(),
),
);
} else {
textFields.clear();
//if we're in the sign up state, add name
// add email & password
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('First Name'),
validator: NameValidator.validate,
onSaved: (value) => _firstName = value,
));
textFields.add(SizedBox(
height: 15,
));
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Last Name'),
onSaved: (value) => _lastName = value,
validator: NameValidator.validate,
));
textFields.add(SizedBox(
height: 15,
));
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Email Address'),
onSaved: (value) => _email = value.trim(),
validator: SignInEmailValidator.validate,
));
textFields.add(SizedBox(
height: 15,
));
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Confirm Email Address'),
onSaved: (value) => _confirmEmail = value,
validator: (confirmation) {
return confirmation == _email
? null
: "Confirm Email Address should match email address";
},
));
textFields.add(SizedBox(
height: 15,
));
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Password'),
obscureText: true,
onSaved: (value) => _password = value,
));
textFields.add(SizedBox(
height: 15,
));
textFields.add(TextFormField(
style: TextStyle(
fontSize: 12.0,
),
decoration: buildSignUpInputDecoration('Confirm Password'),
onSaved: (value) => _confirmPassword = value,
validator: (confirmation) {
return confirmation == _password
? null
: "Confirm Password should match password";
}));
textFields.add(SizedBox(
height: 15,
));
textFields.add(GestureDetector(
onTap: () => _selectDate(context),
child: AbsorbPointer(
child: TextFormField(
style: TextStyle(fontSize: 12.0),
controller: _date,
keyboardType: TextInputType.datetime,
decoration: InputDecoration(
isDense: true,
fillColor: Colors.white,
hintText: 'Date of Birth',
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 0.0),
),
contentPadding:
const EdgeInsets.only(left: 14.0, bottom: 10.0, top: 10.0),
),
onSaved: (value) => _dateOfBirth = value,
),
),
));
textFields.add(SizedBox(
height: 15,
));
}
return textFields;
}
List<Widget> buildSwitchText() {
String _switchButtonTextPart1, _switchButtonTextPart2, _newFormState;
if (authFormType == AuthFormType.signIn) {
_switchButtonTextPart1 = "Haven't got an account? ";
_switchButtonTextPart2 = 'Sign Up';
_newFormState = 'signUp';
} else {
_switchButtonTextPart1 = 'Already have an account? ';
_switchButtonTextPart2 = 'Sign In';
_newFormState = 'signIn';
}
return [
SizedBox(height: 5.0),
Container(
width: MediaQuery.of(context).size.height * 0.7,
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
),
color: kPrimaryColor,
textColor: Colors.black,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
buildTitleText(),
style: TextStyle(fontFamily: FontNameDefault, fontSize: 15.0),
),
),
onPressed: submit),
),
SizedBox(
height: 5.0,
),
RichText(
text: TextSpan(
style: TextStyle(
fontFamily: FontNameDefault,
color: Colors.black,
fontSize: 12.0,
),
children: <TextSpan>[
TextSpan(text: _switchButtonTextPart1),
TextSpan(
text: _switchButtonTextPart2,
style: TextStyle(
decoration: TextDecoration.underline,
color: Colors.black,
fontSize: 12.0),
recognizer: TapGestureRecognizer()
..onTap = () {
switchFormState(_newFormState);
})
]),
),
];
}
String buildAppBarText() {
String _switchAppBarHeader;
if (authFormType == AuthFormType.signIn) {
_switchAppBarHeader = "Already have an account?";
} else {
_switchAppBarHeader = "Don't have an account?";
}
return _switchAppBarHeader;
}
String buildTitleText() {
String _switchTextHeader;
if (authFormType == AuthFormType.signIn) {
_switchTextHeader = "Sign In";
} else {
_switchTextHeader = "Sign Up";
}
return _switchTextHeader;
}
}
InputDecoration buildSignUpInputDecoration(String hint) {
return InputDecoration(
isDense: true,
fillColor: Colors.white,
hintText: hint,
filled: true,
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 0.0),
),
contentPadding: const EdgeInsets.only(left: 14.0, bottom: 10.0, top: 10.0),
);
}
Any ideas?
The HomePage(), InboxPage() and AccountController() are in your home-widget, which contains the bottomNavigationBar.
Your profile and signup pages are different pages with its own scaffold.
You have to create one central "index" widget wich holds every sub-page and the navigationbar.