Ok so I've spent the last 3 days watching singlechildscroll videos and reading forums and everyone says use singlechildscroll. For the life of me I can not get this widget to work with my signup page. I've placed this widget at almost every level and wrapping it in expanded and outside expanded amongst other widgets like containers or paddings with special viewinset.bottom settings and its just been a nightmare the amount of variances I've tried and still cant figure this out so I'm reaching out cause I'm out of ideas now.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:vext/controllers/auth_controller.dart';
import 'package:vext/helpers/constants.dart';
import 'package:vext/screens/login_screen.dart';
import 'package:vext/widgets/decorativeWidgets/rounded_text_form_field.dart';
import 'package:vext/widgets/decorativeWidgets/vext_elevated_button.dart';
import 'login_title.dart';
class SignUp extends StatefulWidget {
const SignUp({Key? key}) : super(key: key);
#override
State<SignUp> createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final _signUpFormKey = GlobalKey<FormState>();
final TextEditingController _nameController = TextEditingController();
final TextEditingController _emailController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final _authController = Get.find<AuthController>();
final FocusNode _nameFocus = FocusNode();
final FocusNode _emailFocus = FocusNode();
final FocusNode _passwordFocus = FocusNode();
final FocusNode _passwordConfirmFocus = FocusNode();
bool _isLoading = false;
#override
void dispose() {
_nameController.dispose();
_emailController.dispose();
_passwordController.dispose();
_authController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
bool keyboardIsOpen = MediaQuery.of(context).viewInsets.bottom != 0;
debugPrint('New user Sign Up Initiated');
return SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: kPrimaryColor,
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 40.h,
),
LoginTitle(
title: 'Sign Up',
subtitle: 'Create an account...',
titleFontSize: 75.sp,
subFontSize: 25.sp,
),
SizedBox(height: 15.h),
buildSignUpForm(),
SizedBox(height: 30.h),
Text(
'Already have an account?',
style: TextStyle(
fontSize: 20.sp,
),
),
TextButton(
onPressed: () {
FocusScope.of(context).unfocus();
Get.to(() => LoginScreen());
},
child: Text(
'Sign In',
style: TextStyle(
color: kSecondaryColor,
fontSize: 20.sp,
),
),
style: ButtonStyle(
overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
),
),
// Padding(
// padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
// ),
],
),
),
);
}
// Sign-up form Section
Form buildSignUpForm() {
return Form(
key: _signUpFormKey,
child: Column(
children: <Widget>[
RoundedTextFormField(
autoFocus: true,
focusNode: _nameFocus,
onFieldSubmitted: (term) {
_fieldFocusChange(context, _nameFocus, _emailFocus);
},
keyboardType: TextInputType.name,
keyboardAction: TextInputAction.next,
controller: _nameController,
hintText: 'Name',
validator: (value) {
if (value.toString().length <= 2 || value!.isEmpty) {
return 'Enter a valid Name';
}
return '';
},
),
SizedBox(height: 10.h),
RoundedTextFormField(
focusNode: _emailFocus,
onFieldSubmitted: (term) {
_fieldFocusChange(context, _emailFocus, _passwordFocus);
},
keyboardType: TextInputType.emailAddress,
keyboardAction: TextInputAction.next,
controller: _emailController,
hintText: 'Email',
validator: (value) {
bool _isEmailValid =
RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+#[a-zA-Z0-9]+\.[a-zA-Z]+").hasMatch(value!);
if (!_isEmailValid || value.isEmpty) {
return 'Invalid Email';
}
return '';
},
),
SizedBox(height: 10.h),
RoundedTextFormField(
focusNode: _passwordFocus,
onFieldSubmitted: (term) {
_fieldFocusChange(context, _passwordFocus, _passwordConfirmFocus);
},
keyboardType: TextInputType.visiblePassword,
keyboardAction: TextInputAction.next,
obsecureText: true,
controller: _passwordController,
hintText: 'Password',
validator: (value) {
if (value.toString().length < 7 || value!.isEmpty) {
return 'Password should be longer or equal to 7 characters.';
}
return '';
},
),
SizedBox(height: 10.h),
RoundedTextFormField(
focusNode: _passwordConfirmFocus,
keyboardAction: TextInputAction.done,
onFieldSubmitted: (term) {
_passwordConfirmFocus.unfocus();
//Get.to(() => LoginScreen());
},
keyboardType: TextInputType.visiblePassword,
obsecureText: true,
hintText: 'Confirm Password',
validator: (value) {
if (value!.trim() != _passwordController.text.trim() || value.isEmpty) {
return 'Passwords do not match!';
}
return '';
},
),
SizedBox(height: 30.h),
_isLoading
? const CircularProgressIndicator() // TODO custom progress indicator
: VextElevatedButton(
buttonText: 'Sign Up',
onPressed: () {
debugPrint('Signup Submit button Pressed');
if (_signUpFormKey.currentState!.validate()) {
_signUpFormKey.currentState!.save();
setState(() {
_isLoading = true;
});
FocusScope.of(context).unfocus();
String name = _nameController.text.trim();
String email = _emailController.text.trim();
String password = _passwordController.text.trim();
debugPrint('Attempting Signup with Firebase');
_authController.signUp(name, email, password);
setState(() {
_isLoading = false;
});
}
},
),
],
),
);
}
}
_fieldFocusChange(BuildContext context, FocusNode currentFocus, FocusNode nextFocus) {
currentFocus.unfocus();
FocusScope.of(context).requestFocus(nextFocus);
}
The few times I got the screen to scroll at all the keyboard would push everything up but when the keyboard would close the page contents would stay at the raised level but scrollable.
Any help would be most appreciated
EDIT*
Here are the 2 custom Widgets that I can think of that are used in the signup screen
roundedTextFormfield
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:vext/helpers/constants.dart';
class RoundedTextFormField extends StatelessWidget {
const RoundedTextFormField({
Key? key,
this.controller,
required this.hintText,
this.obsecureText = false,
required this.validator,
this.keyboardType = TextInputType.text,
this.keyboardAction = TextInputAction.next,
this.focusNode,
this.onFieldSubmitted,
this.autoFocus = false,
this.errorText,
this.onChanged,
this.initialValue,
}) : super(key: key);
final TextEditingController? controller;
final bool? obsecureText;
final String? hintText;
final String? Function(String?) validator;
final TextInputType? keyboardType;
final TextInputAction keyboardAction;
final FocusNode? focusNode;
final Function(String)? onFieldSubmitted;
final bool? autoFocus;
final String? errorText;
final Function(String)? onChanged;
final String? initialValue;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 18.0.w),
child: TextFormField(
initialValue: initialValue,
onChanged: onChanged,
cursorColor: kSecondaryColor,
autofocus: autoFocus!,
keyboardType: keyboardType,
textInputAction: keyboardAction,
focusNode: focusNode,
onFieldSubmitted: onFieldSubmitted,
style: TextStyle(color: Theme.of(context).colorScheme.secondary),
controller: controller,
obscureText: obsecureText!,
decoration: InputDecoration(
errorStyle: TextStyle(
color: Colors.orange,
fontSize: 10.sp,
fontWeight: FontWeight.bold,
),
hintText: hintText,
hintStyle: TextStyle(color: Theme.of(context).colorScheme.secondary),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 2.w,
),
borderRadius: BorderRadius.all(
Radius.circular(30.0.r),
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.secondary,
width: 2.w,
),
borderRadius: BorderRadius.all(
Radius.circular(30.0.r),
),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).errorColor,
width: 2.w,
),
borderRadius: BorderRadius.all(
Radius.circular(30.0.r),
),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).errorColor,
width: 2.w,
),
borderRadius: BorderRadius.all(
Radius.circular(30.0.r),
),
),
),
validator: validator,
),
);
}
}
and the other of an elevated button
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
class VextElevatedButton extends StatelessWidget {
const VextElevatedButton({
Key? key,
required this.buttonText,
required this.onPressed,
this.fontSize = 20,
}) : super(key: key);
final String? buttonText;
final double? fontSize;
final VoidCallback onPressed;
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(
buttonText!,
style: TextStyle(
fontSize: 20.sp,
),
),
style: ElevatedButton.styleFrom(
elevation: 20,
shadowColor: Theme.of(context).colorScheme.secondary,
minimumSize: Size(context.width * 0.4.w, context.height * 0.065.h),
onPrimary: Theme.of(context).colorScheme.secondary,
textStyle: TextStyle(color: Theme.of(context).colorScheme.secondary),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.r),
side: BorderSide(
width: 2.w,
color: Theme.of(context).colorScheme.secondary,
),
),
),
);
}
}
but these are just the flutter widgets with set options for reusability
Wrap your SignUp class column with SingleChildScrollview after that wrap it up with Container which have property Like Below
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child:SingleChildScrollView(child:Column(your widgets))
)
Related
I'm working on a flutter app and having trouble dismissing a modal (fullscreendialog) view using Navigator.pop. I've also tried Navigator.of(context).pop and no luck there either. I'm sure it's something simple, but I can't seem to figure out what it is. If you scroll the code all the way to the bottom using the drag handle, the Navigator.pop call should be just about in the middle of the window; it's about 25 lines from the bottom. Thanks in advance for your help.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import '../misc/custom_colors.dart';
import '../misc/validator.dart';
import '../widgets/app_bar_title.dart';
import '../widgets/custom_form_field.dart';
class AddRecord extends StatefulWidget {
// final FocusNode titleFocusNode;
// final FocusNode descriptionFocusNode;
const AddRecord({
Key? key,
}) : super(key: key);
#override
State<AddRecord> createState() => _AddRecordState();
}
class _AddRecordState extends State<AddRecord> {
// overhead
final _formKey = GlobalKey<FormState>();
bool isProcessing = false;
// focusnodes
final FocusNode fnDate = FocusNode();
final FocusNode fnAmount = FocusNode();
final FocusNode fnPayee = FocusNode();
final FocusNode fnAccount = FocusNode();
final FocusNode fnJob = FocusNode();
final FocusNode fnCostCode = FocusNode();
final FocusNode fnMemo = FocusNode();
// text controllers
final TextEditingController _dateController = TextEditingController();
final TextEditingController _amountController = TextEditingController();
final TextEditingController _payeeController = TextEditingController();
final TextEditingController _accountController = TextEditingController();
final TextEditingController _jobController = TextEditingController();
final TextEditingController _costCodeController = TextEditingController();
final TextEditingController _memoController = TextEditingController();
Future recordTransaction(
final String date,
final String amount,
final String payee,
final String account,
final String job,
final String costCode,
final String memo,
) async {
await FirebaseFirestore.instance.collection('transactions').add({
'date': date,
'amount': amount,
'payee': payee,
'account': account,
'job': job,
'cost code': costCode,
'memo': memo,
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
// modify appbar to remove back button before release!
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: const AppBarTitle(first: 'New', second: 'Record'),
),
backgroundColor: CustomColors.fbNavy,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.only(
left: 16,
right: 16,
bottom: 20,
),
child: Form(
key: _formKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
left: 8,
right: 8,
bottom: 24,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
CustomFormField(
controller: _dateController,
focusNode: fnDate,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
label: 'Transaction Date',
hint: 'Date',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
controller: _amountController,
focusNode: fnAmount,
keyboardType: const TextInputType.numberWithOptions(
decimal: true),
inputAction: TextInputAction.next,
label: 'Transaction Amount',
hint: 'Amount',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
controller: _payeeController,
focusNode: fnPayee,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
label: 'Payee',
hint: 'Payee name',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
controller: _accountController,
focusNode: fnAccount,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
label: 'Paid-from Account',
hint: 'Account',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
controller: _jobController,
focusNode: fnJob,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
label: 'Job',
hint: 'Job (optional)',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
controller: _costCodeController,
focusNode: fnCostCode,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
label: 'Cost Code',
hint: 'Cost Code',
validator: (value) =>
Validator.validateField(value: value),
),
const SizedBox(height: 16),
CustomFormField(
maxLines: 5,
controller: _memoController,
focusNode: fnMemo,
keyboardType: TextInputType.text,
inputAction: TextInputAction.done,
label: 'Memo',
hint: 'Memo (Optional)',
validator: (value) =>
Validator.validateField(value: value),
),
],
),
),
isProcessing
? const Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
CustomColors.fbOrange,
),
),
)
: SizedBox(
width: double.maxFinite,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(
CustomColors.fbOrange,
),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
onPressed: () {
fnDate.unfocus();
fnAmount.unfocus();
fnPayee.unfocus();
fnAccount.unfocus();
fnJob.unfocus();
fnCostCode.unfocus();
fnMemo.unfocus();
if (_formKey.currentState!.validate()) {
setState(() {
isProcessing = true;
});
recordTransaction(
_dateController.text.trim(),
_amountController.text.trim(),
_payeeController.text.trim(),
_accountController.text.trim(),
_jobController.text.trim(),
_costCodeController.text.trim(),
_memoController.text.trim(),
);
setState(() {
isProcessing = false;
});
Navigator.of(context).pop;
}
},
child: const Padding(
padding: EdgeInsets.only(top: 16, bottom: 16),
child: Text(
'Add Transaction',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: CustomColors.fbGrey,
letterSpacing: 2,
),
),
),
),
),
],
),
),
),
),
);
}
}
You missed () try
onPressed: () {
Navigator.pop(context);
}
i'm trying to use customTextField
this the file which contain the Widget
import 'package:flutter/material.dart';
Widget defaultFormField({
required TextEditingController controller,
required TextInputType type,
required Function validate,
Function? onSubmitted,
Function? onChange,
Function? onTap,
required String label,
required IconData prefix,
}) =>
Form(
child: TextFormField(
controller: controller,
validator: (value) => validate(value),
// validator: validate(),
onFieldSubmitted: onSubmitted!(),
onChanged: onChange!(),
onTap: onTap!(),
keyboardType: type,
decoration: InputDecoration(
labelText:label,
prefixIcon: Icon(prefix),
border: const OutlineInputBorder(),
),
),
);
and this how i call it
in my home page
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
backgroundColor: Colors.red,
title: Text(titles[currentIndex]),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.redAccent,
onPressed: () {
if (isBottomSheet) {
Navigator.pop(context);
isBottomSheet = false;
setState(() {
fabIcon = Icons.edit;
});
} else {
scaffoldKey.currentState?.showBottomSheet(
(context) => Column(mainAxisSize: MainAxisSize.min, children: [
Form(
key: formdKey,
child: defaultFormField(
controller: titleController,
type: TextInputType.text,
validate: (String value) {
if (value.isEmpty) {
return 'title must not be empty';
}
return null;
},
label: 'Title Task',
prefix: Icons.title),
),
]));
isBottomSheet = true;
setState(() {
fabIcon = Icons.add_task;
});
}
},
child: Icon(fabIcon),
),
bottomNavigationBar: BottomNavigationBar(
selectedItemColor: Colors.white,
unselectedItemColor: Colors.black54,
backgroundColor: Colors.red,
type: BottomNavigationBarType.fixed,
currentIndex: currentIndex,
onTap: (value) {
setState(() {
currentIndex = value;
});
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.new_label), label: 'Tasks'),
BottomNavigationBarItem(
icon: Icon(Icons.check_circle), label: 'Done'),
BottomNavigationBarItem(
icon: Icon(Icons.archive), label: 'Archive'),
]),
body: screens[currentIndex],
);
}
You have to use a class instead of that code, your code is wrong.
This is one of my custom TextFormField.
This contains own another class but anyway you can use this:
import 'package:flutter/material.dart';
import 'package:plasco/config/textfield_validator.dart';
import 'package:plasco/utils/colors.dart';
class PlascoTextFormFild extends StatefulWidget {
final String? text;
final FontWeight? fontWeight;
final double? fontSize;
final double? counterFontSize;
final TextAlign? textAlign;
final TextInputType? keyboardType;
final int? maxLength;
final String? hint;
final int? maxLines;
final Color? color;
final TextEditingController? controller;
final bool? enabled;
final Validator? validator;
final bool publicValidator;
const PlascoTextFormFild(
{Key? key,
this.text,
this.fontWeight,
this.publicValidator = false,
this.fontSize,
this.color = Colors.transparent,
this.counterFontSize,
required this.textAlign,
this.keyboardType,
this.maxLength = 800,
this.hint,
this.maxLines,
this.controller,
this.enabled = true,
this.validator})
: super(key: key);
#override
_PlascoTextFormFildState createState() => _PlascoTextFormFildState();
}
class _PlascoTextFormFildState extends State<PlascoTextFormFild> {
bool _focused = false;
bool _hasError = false;
String _error = '';
late FocusNode? _keyboardFocus;
#override
void initState() {
_keyboardFocus = FocusNode();
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
_keyboardFocus!.addListener(() {
setState(() {
if (!_keyboardFocus!.hasPrimaryFocus) {
_focused = false;
}
});
});
return Directionality(
textDirection: TextDirection.rtl,
child: Container(
decoration: BoxDecoration(
color: widget.color,
borderRadius: BorderRadius.circular(5),
),
child: TextFormField(
controller: widget.controller,
enabled: widget.enabled,
cursorHeight: 20,
textDirection: TextDirection.rtl,
focusNode: _keyboardFocus,
maxLines: widget.maxLines,
maxLength: widget.maxLength,
textAlign: widget.textAlign!,
keyboardType: widget.keyboardType,
validator: (text) {
if (widget.validator == null) {
if (widget.publicValidator) {
var a = PublicValidator(text!, widget.text!);
if (a) {
return "";
} else {
return null;
}
} else {
return null;
}
} else {
if (widget.validator!.hasError(text!)) {
setState(() => _hasError = true);
_error = widget.validator!.getError();
return '';
} else {
setState(() => _hasError = false);
return null;
}
}
},
style: TextStyle(
color: PlascoColor.Black1,
fontFamily: 'IRANSansMobile',
fontSize: widget.fontSize,
letterSpacing: 0,
fontWeight: widget.fontWeight,
height: widget.maxLines != null ? 2 : 1),
decoration: InputDecoration(
counterText: "",
labelStyle: TextStyle(
color: _hasError
? PlascoColor.Red1
: _focused
? PlascoColor.Yellow1
: PlascoColor.Gray1,
fontFamily: 'IRANSansMobile',
fontSize: widget.fontSize,
letterSpacing: 0,
fontWeight: widget.fontWeight,
height: 1,
),
hintStyle: TextStyle(
color: PlascoColor.Gray2,
fontFamily: 'IRANSansMobile',
fontSize: widget.fontSize,
letterSpacing:
0 /*percentages not used in flutter. defaulting to zero*/,
fontWeight: widget.fontWeight,
height: 1),
counterStyle: TextStyle(
color: PlascoColor.Gray1,
fontFamily: 'IRANSansMobile',
fontSize: widget.counterFontSize,
letterSpacing: 0,
fontWeight: widget.fontWeight,
height: 1),
floatingLabelBehavior: FloatingLabelBehavior.always,
// border: OutlineInputBorder(),
disabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: PlascoColor.Gray2,
width: 1.0,
)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: PlascoColor.Gray2,
width: 1.0,
)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: PlascoColor.Yellow1,
width: 1.0,
)),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: PlascoColor.Red1,
width: 1.0,
)),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0),
borderSide: const BorderSide(
color: PlascoColor.Red1,
width: 1.0,
)),
labelText: _hasError ? _error : widget.text,
hintText: widget.hint,
contentPadding: EdgeInsets.fromLTRB(8, 8, 8, 8),
),
// autofocus: false,
),
),
);
}
}
I want to align the price text as it is in the first image, I tried to do it as in the second image, but I could not.
I want to do:
The problem:
This is the code I wrote to try to build the design.
TextFormField(
controller: startController,
keyboardType: TextInputType.number,
textAlign: TextAlign.end,
decoration: const InputDecoration().copyWith(
border: const UnderlineInputBorder(borderSide: BorderSide(color: kPinCodeColor)),
enabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: kPinCodeColor)),
disabledBorder: const UnderlineInputBorder(borderSide: BorderSide(color: kPinCodeColor)),
suffix: Text('JOD', style:
Theme.of(context).textTheme.headline6!.copyWith(fontSize:
Sizes.textSize_22)),
),
style: Theme.of(context).textTheme.headline6!.copyWith(fontSize:
Sizes.textSize_34),
)
You have make custom widget for this purpose. I made StatefulWidget I hope it make sense for you.
FocusNode is for handling focus change and animate the line underneath the Text.
MouseRagion is for handling hovering events.
IntrinsicWidth is for making TextFormInput expandable while typing.
This is the code:
class TextCustom extends StatefulWidget {
const TextCustom({Key? key, this.textController, this.suffix})
: super(key: key);
final TextEditingController? textController;
final String? suffix;
#override
State<TextCustom> createState() => _TextCustomState();
}
class _TextCustomState extends State<TextCustom> {
bool _isHovering = false;
bool _isFocased = false;
FocusNode textFocus = FocusNode();
#override
void initState() {
textFocus.addListener(() {
setState(() {
_isFocased = textFocus.hasFocus;
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: MouseRegion(
onEnter: (event) {
setState(() {
_isHovering = true;
});
},
onExit: (event) {
setState(() {
_isHovering = false;
});
},
child: GestureDetector(
onTap: () {
textFocus.requestFocus();
},
child: InputDecorator(
expands: false,
isFocused: _isFocased,
isHovering: _isHovering,
decoration: const InputDecoration(),
child: Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
IntrinsicWidth(
child: TextFormField(
focusNode: textFocus,
controller: widget.textController,
decoration: const InputDecoration(
constraints: BoxConstraints(minWidth: 20),
isCollapsed: true,
border: UnderlineInputBorder(borderSide: BorderSide.none),
),
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(fontSize: 34),
),
),
const SizedBox(width: 8),
(widget.suffix != null)
? Text(
'JOD',
style: Theme.of(context)
.textTheme
.headline6!
.copyWith(fontSize: 22),
)
: Container(),
],
),
),
),
),
);
}
}
I have a custom Textfield, In my application, the user enters an amount in the textfield with the label quantity, the amount entered should be multiplied with value in another textfield(the value in this textfield is from list on the previous page called price). The total should be automatically calculated in another textfield called total. Below is code that I have tried.
import 'package:flutter/material.dart';
import 'package:kingstar/res/custom_colors.dart';
class CustomFormField extends StatelessWidget {
const CustomFormField({
Key? key,
required TextEditingController controller,
// required FocusNode focusNode,
required TextInputType keyboardType,
required TextInputAction inputAction,
required String label,
required String hint,
required Function(String value) validator,
required Function(String value) onChanged,
this.isObscure = false,
this.isCapitalized = false,
this.maxLines = 1,
this.isLabelEnabled = true,
this.readOnly = false,
}) : _emailController = controller,
// _emailFocusNode = focusNode,
_keyboardtype = keyboardType,
_inputAction = inputAction,
_label = label,
_hint = hint,
_validator = validator,
super(key: key);
final TextEditingController _emailController;
// final FocusNode _emailFocusNode;
final TextInputType _keyboardtype;
final TextInputAction _inputAction;
final String _label;
final String _hint;
final bool isObscure;
final bool isCapitalized;
final int maxLines;
final bool isLabelEnabled;
final Function(String) _validator;
final bool readOnly;
#override
Widget build(BuildContext context) {
return TextFormField(
maxLines: maxLines,
controller: _emailController,
readOnly: readOnly,
// focusNode: _emailFocusNode,
keyboardType: _keyboardtype,
obscureText: isObscure,
textCapitalization:
isCapitalized ? TextCapitalization.words : TextCapitalization.none,
textInputAction: _inputAction,
style: TextStyle(color: Colors.white),
cursorColor: CustomColors.firebaseYellow,
validator: (value) => _validator(value!),
decoration: InputDecoration(
labelText: isLabelEnabled ? _label : null,
labelStyle: TextStyle(color: CustomColors.firebaseYellow),
hintText: _hint,
hintStyle: TextStyle(
color: Colors.white,
),
errorStyle: TextStyle(
color: Colors.redAccent,
fontWeight: FontWeight.bold,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(
color: CustomColors.firebaseAmber,
width: 2,
),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(
color: CustomColors.firebaseGrey.withOpacity(0.5),
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(
color: Colors.redAccent,
width: 2,
),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide(
color: Colors.redAccent,
width: 2,
),
),
),
);
}
}
class EditItemForm extends StatefulWidget {
final String currentName;
final String currentPrice;
final String currentQuantity;
final String documentId;
const EditItemForm({
required this.currentName,
required this.currentPrice,
required this.currentQuantity,
required this.documentId,
});
#override
_EditItemFormState createState() => _EditItemFormState();
}
class _EditItemFormState extends State<EditItemForm> {
final _editItemFormKey = GlobalKey<FormState>();
final DocumentReference docId = _salesCollection.doc();
bool _isProcessing = false;
late TextEditingController _nameController;
late TextEditingController _priceController;
late TextEditingController _quantityAvailableController;
late TextEditingController _quantityController;
late TextEditingController _totalController;
int? _total = 0;
late SharedPreferences sharedPreferences;
late String documentId = "", buyer_name = "", buyer_phonenumber="", buyer_email="", buyer_address="", seller_name="", seller_location = "";
late int total;
void _onChange() {
setState(() {
_total = (int.parse( widget.currentPrice) * int.parse(_quantityController.text));
print(int.parse( widget.currentPrice) * int.parse(_quantityController.text));
});
}
textListener() {
_total = (int.parse(_priceController.text) * int.parse(_quantityController.text));
print("Current Text is ${_total.toString()}");
}
#override
void initState() {
_nameController = TextEditingController(
text: widget.currentName,
);
_priceController = TextEditingController(
text: widget.currentPrice,
);
_quantityAvailableController = TextEditingController(
text: widget.currentQuantity,
);
_quantityController = TextEditingController(
text: widget.currentQuantity,
);
_totalController = TextEditingController(
text: widget.currentQuantity,
);
super.initState();
_onChange();
_quantityController.addListener(textListener);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Form(
key: _editItemFormKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
bottom: 24.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24.0),
Text(
'Feed Name',
style: TextStyle(
color: CustomColors.firebaseGrey,
fontSize: 22.0,
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
CustomFormField(
readOnly: true,
isLabelEnabled: false,
controller: _nameController,
keyboardType: TextInputType.text,
inputAction: TextInputAction.next,
validator: (value) => Validator.validateField(
value: value,
),
label: 'Feed',
hint: 'Feed Name', onChanged: (String value) { },
),
SizedBox(height: 24.0),
Text(
'Price',
style: TextStyle(
color: CustomColors.firebaseGrey,
fontSize: 22.0,
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
CustomFormField(
// readOnly: true,
isLabelEnabled: false,
controller: _priceController,
keyboardType: TextInputType.text,
inputAction: TextInputAction.done,
validator: (value) => Validator.validateField(
value: value,
),
label: 'Price',
hint: 'Price for' + widget.currentName,
onChanged: (value) => _onChange,
),
SizedBox(height: 24,),
Text(
'Quanity Available',
style: TextStyle(
color: CustomColors.firebaseGrey,
fontSize: 22.0,
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
CustomFormField(
readOnly: true,
isLabelEnabled: false,
controller: _quantityAvailableController,
keyboardType: TextInputType.number,
inputAction: TextInputAction.done,
validator: (value) => Validator.validateField(
value: value,
),
label: 'Quantity',
hint: 'Quantity available for' + widget.currentName,
onChanged: (value) => _onChange,
),
SizedBox(height: 24,),
Text(
'Quanity ',
style: TextStyle(
color: CustomColors.firebaseGrey,
fontSize: 22.0,
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
CustomFormField(
isLabelEnabled: false,
controller: _quantityController,
keyboardType: TextInputType.number,
inputAction: TextInputAction.done,
validator: (value) => Validator.validateField(
value: value,
),
label: 'Quantity',
hint: 'Enter your quantity',
onChanged: (value) => _onChange,
),
SizedBox(height: 24,),
Text(
'Total ',
style: TextStyle(
color: CustomColors.firebaseGrey,
fontSize: 22.0,
letterSpacing: 1,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.0),
CustomFormField(
readOnly: true,
isLabelEnabled: false,
controller: _totalController,
keyboardType: TextInputType.number,
inputAction: TextInputAction.done,
validator: (value) => Validator.validateField(
value: value,
),
label: 'Total',
hint: 'Total',
onChanged: (String value) { },
),
Text(
_total!.toString()
)
],
),
),
_isProcessing
? Padding(
padding: const EdgeInsets.all(16.0),
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
CustomColors.firebaseOrange,
),
),
) ,
}
}
I have tried using the onChange both inside the customFormField but I am still not having the having the value changed in the last CustomFormField with the label total or in the text widget below it.
Use TextEditingController
here is working demo:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
// value "passed" from other screen
child: MyWidget(price: 12),
),
),
);
}
}
class MyWidget extends StatelessWidget {
double price;
MyWidget({required this.price}) {
controllerPrice.text = this.price.toString();
}
// price "from other screen"
TextEditingController controllerPrice = TextEditingController();
// controller to update Total value
TextEditingController controllerTotal = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(children: [
Text("Quantity:"),
TextField(onChanged: (String t) {
controllerTotal.text =
(double.parse(t) * double.parse(controllerPrice.text)).toString();
}),
Text("Price:"),
TextField(readOnly: true, controller: controllerPrice),
Text("Total:"),
TextField(readOnly: true, controller: controllerTotal),
]);
}
}
}
And dartpad:
https://dartpad.dev/?id=051b6df715ecfa9b54e5a771c3927057&null_safety=true
Of course you need to handle input when you delete everything from Quantity field.
Minimum 1 alphabetic
Minimum 1 number
Allow only Character (_)
its failed
Pattern pattern = r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[_]).{8,}$';
RegExp regex = new RegExp(pattern);
Just check out this example that i have created :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
TextEditingController userName = TextEditingController();
FocusNode usernameFocus = new FocusNode();
String errorText;
bool _isValid = false;
#override
void initState() {
super.initState();
userName.addListener(() {
String pattern = r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[_]).{8,}$';
RegExp regExp = new RegExp(pattern);
if (userName.text.isEmpty) {
setState(() {
errorText = 'Field cannot be empty';
});
} else {
if (!regExp.hasMatch(userName.text)) {
print('The does not matches the requirement');
setState(() {
// here you can add you text
errorText =
'Minimum 1 Capital letter, 1 small letter and 1 number and _';
_isValid = false;
});
} else {
print('the value matches');
setState(() {
errorText = null;
_isValid = true;
});
}
}
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(left: 20, right: 20),
child: TextField(
focusNode: usernameFocus,
decoration: new InputDecoration(
errorText: errorText,
prefixIcon: Icon(
Icons.supervised_user_circle,
color: Color(0xFF282858),
),
labelText: "UserName",
labelStyle: TextStyle(
fontFamily: "Poppins-Medium",
color: usernameFocus.hasFocus
? Colors.grey[600]
: Colors.grey[600]),
fillColor: Colors.white,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.grey[400], width: 2.0),
borderRadius: BorderRadius.circular(10.0),
),
border: new OutlineInputBorder(
borderRadius: new BorderRadius.circular(10.0),
borderSide:
new BorderSide(color: Colors.grey[400], width: 2.0),
),
//fillColor: Colors.green
),
controller: userName,
keyboardType: TextInputType.text,
),
),
RaisedButton(
child: Text('Login'),
onPressed: () {
print(_isValid);
if (!_isValid) {
return;
}
print('validation sucess');
},
)
],
)),
),
);
}
}
let me know if it works.
You would need to use a validator on your Textfield. You would add a condtion in the validator function to check if it's false and returns the error meessage that you would like the user to see.
You can either validate by using autovalidate: true, or you can do it manualy by using .currentState.validate() on your form before saving.
so your code could look something like this
validator: (value) {
final alphanumeric = RegExp(YOUR_REGEX_HERE);
if (!alphanumeric.hasMatch(value)) return 'YOUR_ERROR_MESSAGE';
}
You can use this library
pubspec.yaml
flutter_form_builder: ^3.7.3
Code will be like this:
GlobalKey _addextKey = GlobalKey();
FormBuilder(
key: _addextKey,
child: ListView(
children: <Widget>[
TextFormField(
textInputAction: TextInputAction.next,
style: textStyle,
controller: _NameauthController,
// ignore: missing_return
validator: FormBuilderValidators.required(),
decoration: InputDecoration(
hintText: 'name',
hintStyle: TextStyle(fontSize: 12.0),
labelStyle: textStyle,
errorStyle:
TextStyle(color: Colors.red, fontSize: 12.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0))),
keyboardType: TextInputType.text,
),
]);),