I have a text field on the page which is located at the very bottom. resizeToAvoidBottomInset value: false. I added a focuseNode so that when the field is clicked, it will be in focus. But when I click on this text field, the keyboard overlaps and nothing is visible because the text field is at the very bottom and the page does not scroll when the keyboard opens. How can I make the text field visible when opening the keyboard? If I use resizeToAvoidBottomInset: true, then the page scrolls, but between the keyboard and the widget there is a large padding that is set at the bottom of the page and I cannot remove this padding.
main page
const Scaffold(
resizeToAvoidBottomInset: false,
body: FormPage(),
),
body
class FormPage extends StatefulWidget {
final int? paid;
final bool? init;
final Function(bool) parking;
final Function(int) valueChange;
const PaidParking({
Key? key,
required this.parking,
required this.paid,
required this.init,
required this.valueChange,
}) : super(key: key);
#override
State<FormPage > createState() => _FormPageState();
}
class _FormPageState extends State<FormPage > {
FocusNode myFocusNodeName = FocusNode();
final TextEditingController controller =
TextEditingController(text: '€59 per h');
#override
void initState() {
setState(() {
paidParking = widget.parkingInit ?? false;
controller.text =
'€${widget.paidInit != null ? (widget.paidInit! / 100) : 2} per h';
});
super.initState();
}
#override
void dispose() {
super.dispose();
myFocusNodeName.dispose();
}
#override
Widget build(BuildContext context) {
myFocusNodeName.addListener(() {
setState(() {});
});
return Padding(
padding: const EdgeInsets.only(top: 15),
child: Column(
children: [
Row(
children: [
Opacity(
opacity: paidParking ? 1 : 0.7,
child: const Text(
'Form Page',
style: TextStyle(
fontSize: 14,
fontFamily: constants.FontFamily.AvenirLtStd,
fontWeight: FontWeight.w700,
),
),
),
const SizedBox(width: 8),
switcher,
],
),
const SizedBox(height: 15),
paidParking ? parkingValueGet() : const SizedBox(),
],
),
);
}
void changeValue(bool operation, {bool editing = false}) {
final String value = controller.text;
double num = double.parse(
value
.replaceAll('€', '')
.replaceAll('per', '')
.replaceAll('h', '')
.replaceAll(' ', ''),
);
if (!editing) {
if (num <= 0 && !operation) return;
operation ? num += 0.05 : num -= 0.05;
controller.text = '€${num.toStringAsFixed(2)} per h';
widget.paidValueChange((num * 100).round());
} else {
num = double.parse(controller.text);
controller.text = '€${num.toStringAsFixed(2)} per h';
num = double.parse(
value
.replaceAll('€', '')
.replaceAll('per', '')
.replaceAll('h', '')
.replaceAll(' ', ''),
);
widget.valueChange((num * 100).round());
}
}
Widget parkingValueGet() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
InkWell(
onTap: () => changeValue(false),
child: _circleBox(false),
),
Column(
children: [
SizedBox(
height: 40,
width: 120,
child: TextField(
controller: controller,
focusNode: myFocusNodeName,
style: const TextStyle(
fontSize: 16,
fontFamily: FontFamily.AvenirBook,
color: constants.Colors.white,
decoration: TextDecoration.none,
),
onSubmitted: (value) => changeValue(true, editing: true),
onEditingComplete: () => changeValue(true, editing: true),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
signed: true,
),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r"[0-9.]")),
],
textAlign: TextAlign.center,
decoration: const InputDecoration(
border: InputBorder.none,
),
),
),
Container(color: Colors.white, height: 1, width: 115),
],
),
InkWell(
onTap: () => changeValue(true),
child: _circleBox(true),
),
],
);
}
Widget _circleBox(bool operation) {
return Container(
height: 23,
width: 23,
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: constants.Colors.white,
),
),
child: Text(
operation ? '+' : '-',
style: constants.Styles.smallBoldTextStyleWhite,
),
);
}
Widget get switcher => Opacity(
opacity: paidParking ? 1 : 0.7,
child: GestureDetector(
onTap: () {
setState(() {
paidParking = !paidParking;
});
widget.paidParking(paidParking);
},
child: Container(
height: 20,
alignment:
paidParking ? Alignment.centerRight : Alignment.centerLeft,
width: 40,
decoration: BoxDecoration(
color: paidParking ? constants.Colors.purpleMain : Colors.white,
borderRadius: BorderRadius.circular(14.5),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Container(
height: 17,
width: 17,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: paidParking ? Colors.white : const Color(0xff484452),
),
),
),
),
),
);
}
First I can recommend you to do some changes.
Remove setState() method calling inside initState()(have no
effect).
Remove totally the part of the below code. When you are using TexField
inside a StatefulWidget, every time the keyboard opened it will
recall your build() method and in this case, will add multiple focus
listeners on each build() method call, also each focus listener is
calling the setState() method which is triggering build() method
again. This is kind of an infinity loop. I wonder when you open the
keyboard, does your app stuck or stop working smoothly?
myFocusNodeName.addListener(() {
setState(() {});
});
Related
I want to implement OTP verification screen without any package.
when i entered the number it should move to next input field
I m using this code in my current project take a refrence this will help
class Otp extends StatefulWidget {
final String? phnNumber;
final String ? code;
String? from;
Otp({Key ?key, this.phnNumber, this.from, this.code}) : super(key:
key);
#override
_OtpState createState() => _OtpState();
}
class _OtpState extends State<Otp> {
double ? height ;
double ? width;
TextEditingController ? contrller1;
TextEditingController ? contrller2;
TextEditingController ? contrller3;
TextEditingController ? contrller4;
SendOtpRequest resend = SendOtpRequest();
SharedPreferences ? prefs;
getSharedPreferences () async
{
prefs = await SharedPreferences.getInstance();
}
String Code = "";
#override
void initState() {
// TODO: implement initState
super.initState();
contrller1 = TextEditingController();
contrller2 = TextEditingController();
contrller3 = TextEditingController();
contrller4 = TextEditingController();
getSharedPreferences();
}
#override
Widget build(BuildContext context) {
height= MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.height;
final verifyprovider = Provider.of<PostDataProvider>(context);
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
toolbarHeight:height! * 0.07802345,
titleSpacing: 0,
backgroundColor: HexColor("#18263d"),
automaticallyImplyLeading: false,
leading: Padding(
padding: const EdgeInsets.only(left: 8.0,),
child: GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Container(
color: Colors.transparent,
child: Image.asset("assets/images/back_ic-1.png")),
),
),
// SizedBox(width: width!*0.001234,),
title:Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: height!/15,
width: height!/15,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
width: 2,
color:HexColor("#fc4f00"),
)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
height: height!/11,
width: height!/11,
decoration: BoxDecoration(
image: const DecorationImage(
image:
AssetImage("assets/images/home_logo.png"),
fit: BoxFit.fill
),
shape: BoxShape.circle,
border: Border.all(
width: 1,
color:HexColor("#fc4f00"),
)),
),
),
),
SizedBox(width: width! * 0.04234,),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text("Verification",
style: GoogleFonts.oswald(fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: width! * 0.03345
),),
),
],
) ,
),
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 32),
child: Column(
children: [
Text("We have send verification code on your mobile number",
style: GoogleFonts.oswald(fontStyle: FontStyle.normal,
fontSize: width!*0.0234,
color: HexColor("#8b8b8b")),
),
SizedBox(height: height!/38,),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_textFieldOTP(first: true, last: false,controllerr:
contrller1),
_textFieldOTP(first: false, last: false,controllerr:
contrller2),
_textFieldOTP(first: false, last: false,controllerr:
contrller3),
_textFieldOTP(first: false, last: true, controllerr:
contrller4),
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap:() {
resend.phoneNumber= widget.phnNumber;
resend.countryCode = widget.code;
verifyprovider.resendOtp(context,
jsonEncode(resend));
},
child: Text("Resend OTP?",
style: GoogleFonts.oswald(fontStyle:
FontStyle.normal,
fontSize: width!*0.0234,
color: HexColor("#fc4f00")),
),
),
),
SizedBox(height: height!/28,),
GestureDetector(
onTap: (){
if(contrller1!.text.isNotEmpty&&
contrller2!.text.isNotEmpty&&contrller3!.
text.isNotEmpty&&contrller4!.text.isNotEmpty){
verifyOtpRequest verify = verifyOtpRequest();
verify.phoneNumber = widget.phnNumber;
verify.otp=
contrller1!.text+contrller2!.
text+contrller3!.text+contrller4!.text;
verifyprovider.otpVerification(context,
jsonEncode(verify), widget.from);
}else{
CommonUtils.showToast(msg: "Please fill all the
fields ");
}
},
child: Container(
height: height!/18,
width: width,
decoration: BoxDecoration(
color: HexColor("#fc4f00"),
borderRadius: BorderRadius.circular(10)
),
child: Center(
child: Text("Verify",style: TextStyle(
color: Colors.white,
fontSize: width!*0.02345
),),
)
),
),
],
),
],
),
),
),
);
}
Widget _textFieldOTP({bool ? first, last,
TextEditingController ?
controllerr}) {
return Container(
height:height!/12 ,
child: AspectRatio(
aspectRatio: 1.0,
child: TextField(
controller: controllerr,
autofocus: true,
onChanged: (value) {
if (value.length == 1 && last == false) {
FocusScope.of(context).nextFocus();
}
if (value.length == 0 && first == false) {
FocusScope.of(context).previousFocus();
}
},
showCursor: false,
readOnly: false,
textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
keyboardType: TextInputType.number,
maxLength: 1,
decoration: InputDecoration(
counter: Offstage(),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.black54),
borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(width: 2, color: Colors.black54),
borderRadius: BorderRadius.circular(12)),
),
),
),
);
}
}
When the length of the input data reaches one, you will have to change the text field focus node.
For Example
If you are in the first field, and you enter a number field one focus should be lost, and field two should be in focus. This can be done, by requestFocus.
This article will of help for you: Flutter Focus
I have an issue with my textformfield. Whenever the error message shows, it shifted down the next widget below...
I try to search how to give a placement for the error text to no take a placement that does not exist when it is not shown, but I didn't find the solution.
Here are the screenshot and the code of the issue.
class AuthForm extends StatefulWidget {
final bool isPassword;
final IconData prefixIcon;
final String hintText;
late bool isPasswordVisible = isPassword;
final bool isCalendar;
final TextEditingController controller;
final bool isDropDown;
final bool isPhone;
final String? Function(String?)? validator;
AuthForm({Key? key, this.isPassword = false, required this.prefixIcon, required this.hintText,
this.isCalendar = false, required this.controller, this.isDropDown = false, this.isPhone = false, required this.validator}) : super(key: key);
#override
State<AuthForm> createState() => _AuthFormState();
}
class _AuthFormState extends State<AuthForm> {
#override
void initState() {
super.initState();
if (widget.isPhone){
getCountryCode();
}
}
start () async {
await CountryCodes.init();
}
Locale? getCountryCode () {
start();
final Locale? deviceLocale = CountryCodes.getDeviceLocale();
final CountryDetails details = CountryCodes.detailsForLocale();
return deviceLocale;
}
DateTime selectedDate = DateTime(2000,1);
Future<void> _selectDate(BuildContext context) async {
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(1950, 1),
lastDate: DateTime.now());
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
}
}
#override
Widget build(BuildContext context) {
return widget.isDropDown ? const DropDownBar() :
SizedBox(
width: 70.w,
child: TextFormField(
validator: widget.validator,
keyboardType: widget.isPhone ? TextInputType.phone : TextInputType.text,
inputFormatters: [DialCodeFormatter()],
controller: widget.controller,
textAlign: TextAlign.center,
obscureText: widget.isPasswordVisible,
style: Theme.of(context).textTheme.bodyText2,
decoration: InputDecoration(
contentPadding: EdgeInsets.fromLTRB(0, 2.3.h, 0, 0),
hintText : widget.hintText,
hintStyle: Theme.of(context).textTheme.bodyText1,
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).splashColor,
width: 0.13.w,
),
),
errorStyle: Theme.of(context).textTheme.headline6,
prefixIcon: Container(
width: 0,
alignment: const Alignment(-0.99, 0.5),
child: Icon(
widget.prefixIcon,
color: Theme.of(context).primaryColor,
size: 6.w,
),
),
suffixIcon: Visibility(
visible: widget.isPassword,
//Maintain the space where the widget is even if it is hid
maintainAnimation: true,
maintainState: true,
maintainSize: true,
child: InkWell(
highlightColor : Colors.transparent,
splashColor: Colors.transparent,
child: Container(
width: 0,
alignment: const Alignment(0.99, 0.5),
child: Icon(
widget.isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Theme.of(context).primaryColor,
size: 6.w,
),
),
onTap: () {
setState(() {
widget.isPasswordVisible = !widget.isPasswordVisible;
});
},
),
),
),
onTap: () async {
if (widget.isCalendar){
//Dismiss the keyboard
FocusScope.of(context).requestFocus(FocusNode());
//Call the calendar
await _selectDate(context);
widget.controller.text = DateFormat('dd-MM-yyyy').format(selectedDate);
}
}
),
);
}
}
Login Page
#override
Widget build(BuildContext context) {
return BlocListener<InternetCubit, InternetState>(
listener: (context, state) {
if (state is InternetDisconnected) {
showAlertBox(context);
}
},
child: Form(
key: _formkey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 6.h,
),
Text(
"Flexmes",
style: Theme.of(context).textTheme.headline1,
),
SizedBox(
height: 8.h,
),
AuthForm(
prefixIcon: Icons.email_outlined,
hintText: "Email",
controller: emailController,
nextFocusNode: passwordNode,
validator: MultiValidator([
RequiredValidator(errorText: 'Email is required'),
EmailValidator(errorText: 'Enter a valid email address'),
]),
),
SizedBox(
height: 3.h,
),
AuthForm(
isPassword: true,
prefixIcon: Icons.lock_rounded,
hintText: "Password",
controller: passwordController,
currentFocusNode: passwordNode,
validator: MultiValidator([
RequiredValidator(errorText: 'Password is required'),
MinLengthValidator(6, errorText: 'Password must be at least 6 digits long'),
PatternValidator(r'(?=.*?[#?!#$%^&*-])', errorText: 'Passwords must have at least one special character')
]),
),
SizedBox(
height: 4.5.h,
),
SizedBox(
width: 70.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomCheckbox(
iconColor: Colors.black,
activeColor: const Color.fromARGB(255, 3, 218, 197),
),
SizedBox(
width: 3.w,
),
Text(
"Remember me",
style: Theme.of(context).textTheme.bodyText2,
)
],
),
),
SizedBox(
height: 4.5.h,
),
AuthButton(
text: "Log In",
onPressed: (){
if (isInternetDisconnected(context)){
showAlertBox(context);
} else{
if (_formkey.currentState!.validate()){
AuthenticationAPI(auth: FirebaseAuth.instance).signInWithEmail(emailController.text, passwordController.text);
//return navigation
}
}
}
),
SizedBox(
height: 3.2.h,
),
ClickableText(
text: "Forgot Password ?",
onPressed: () {
if (isInternetDisconnected(context)){
showAlertBox(context);
} else{
//return navigation
}
},
),
SizedBox(
height: 3.2.h,
),
const AuthDivider(
text: "OR",
),
SizedBox(
height: 2.h,
),
SizedBox(
width: 70.w,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClickableImage(
imagePath: "assets/images/icon/Facebook.png",
width: 23.w,
onPressed: () {
null;
},
),
ClickableImage(
imagePath: "assets/images/icon/Instagram.png",
width: 23.w,
onPressed: () {
null;
},
),
ClickableImage(
imagePath: "assets/images/icon/Tiktok.png",
width: 23.w,
onPressed: () {
null;
},
),
],
),
),
SizedBox(
height: 4.h,
),
SizedBox(
width: 70.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Don't have an account ? ",
style: Theme.of(context).textTheme.bodyText2,
),
ClickableText(
text: 'Sign up Now !',
onPressed: () {
if (isInternetDisconnected(context)){
showAlertBox(context);
} else{
Navigator.of(context).pushNamed("/signup1");
}
},
),
],
),
),
],
),
),
);
}
}
Thanks for your suggestion,
Chris
try wrapping textformfield with container and giving it height and width
Try wrapping TextFormField with container and give it height and width.
I am fetching category list from another class into DetailCategory and try to filtered it, list viewed perfectly but unable to filterd it because it shows list length 0, I don't know why this is happening.
Here is my code:-
class DetailCategory extends StatefulWidget{
List catList; bool isCat; String catId;
DetailCategory(this.isCat, this.catList, this.catId);
#override
DetailCategoryState createState() => DetailCategoryState();
}
class DetailCategoryState extends State<DetailCategory>{
TextEditingController searchController = TextEditingController();
List catList, filteredList; bool isCat; String catId;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text(Messages.appName),
),
body: bodyWidget(),
);
}
Widget bodyWidget(){
Size size = MediaQuery.of(context).size;
return Container(
child: Column(
children: [
SizedBox(height: 8.0),
Container(
width: size.width*0.96,
height: 50,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(side: BorderSide(width: 0.5),
borderRadius:BorderRadius.all(Radius.circular(7.0)),),
),
child: TextField(
onChanged: (value){
filterdCategory(value);
},
keyboardType: TextInputType.text,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: 'Search in Category',
border: InputBorder.none,
prefixIcon: Icon(Icons.search)
),
),
),
SizedBox(height: 8.0),
Expanded(child: Padding(padding: EdgeInsets.only(left: 8.0, right: 8.0),
child: Stack(
children: [
if(filteredList==null) Container()
else GridView.count(
crossAxisSpacing: 8.0,
mainAxisSpacing: 8.0,
crossAxisCount: 3,
children: filteredList.map((item) => Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
border: Border.all(color: Colors.grey, width: 0.6)
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(5.0),
child: Image.network(NetworkApi.categoryImagePath + item['image'], width: 90,
height: 90, fit: BoxFit.cover,),
),
SizedBox(height: 4.0,),
Text(item['category_name'], style: TextStyle(fontSize: 12.0, fontWeight:
FontWeight.bold, color: Colors.grey),)
],
),
)).toList(),
)
],
),))
],
),
);
}
#override
void initState(){
catList = widget.catList;
filteredList = catList;
isCat = widget.isCat;
catId = widget.catId;
print('list length: ${catList.length}'); // here list length is 6
if(!isCat){
fetchSubCategory();
}
super.initState();
}
fetchSubCategory(){
// it will update later
}
filterdCategory(String query){
if(query.length > 2){
this.filteredList.clear();
print('${this.catList.length} ${query.length}'); //here it prints catList length 0
for(int i =0; i< this.catList.length; i++){
if(this.catList[i]['category_name'].contains(query)){
setState(() {
this.filteredList.add(this.catList[i]);
});
}
}
if(this.filteredList.length == 0) {
print('empty list');
}
}
}
}
In initState it prints length 6 but in filterdCategory it prints length 0, I am new in Flutter and don't know what is happening here, Please correct me if I am doing anything wrong
Update your initState with the below one and then try to filter the list.
#override
void initState(){
catList = widget.catList;
filteredList.addAll(catList);
isCat = widget.isCat;
catId = widget.catId;
print('list length: ${catList.length}'); // here list length is 6
if(!isCat){
fetchSubCategory();
}
super.initState();
}
First of all, here is my condition. I'm trying to pass all the data that were filled in Dropdownbutton and Date & Time picker at the first screen (left picture) to the second screen(right picture). The problem is, I extracted the DropDownButton widget to another class, and I don't understand how to implement it.
Before that, this is the first screen Code :
class InformationDetail extends StatefulWidget {
static const String id = 'InformationDetail';
#override
_InformationDetailState createState() => _InformationDetailState();
}
class _InformationDetailState extends State<InformationDetail> {
String addressText, addNotes;
DateTime selectedDate = DateTime.now();
TimeOfDay selectedTime = TimeOfDay.now();
Future<Null> _selectDate(BuildContext context) async {
final DateTime picked = await showDatePicker(
context: context,
initialDate: selectedDate,
firstDate: DateTime(2015, 8),
lastDate: DateTime(2101));
if (picked != null && picked != selectedDate)
setState(() {
selectedDate = picked;
});
}
Future<Null> _selectTime(BuildContext context) async {
final TimeOfDay picked = await showTimePicker(
context: context,
initialTime: selectedTime,
);
if (picked != null && picked != selectedTime)
setState(() {
selectedTime = picked;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
Container(
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.fromLTRB(25.0, 68.0, 70.0, 26.0),
child: Text(
'Information Detail',
style: TextStyle(fontSize: 35.0),
),
),
Column(
// Wrap Column
children: <Widget>[
Column(
children: <Widget>[
TitleName(
titleText: 'Grooming Type',
infoIcon: Icons.info,
),
MenuDropDown(
dropdownText: 'Grooming Type...',
type: "groomingType",
),
TitleName(
titleText: 'Cat Breeds',
),
MenuDropDown(
dropdownText: 'Cat Breeds...',
type: "catBreeds",
),
TitleName(
titleText: 'Cat Size',
infoIcon: Icons.info,
),
MenuDropDown(
dropdownText: 'Cat Size...',
type: "catSize",
),
TitleName(
titleText: 'Add-On Services',
),
MenuDropDown(
dropdownText: 'Add - On Services...',
type: "addOnServices",
),
TitleName(
titleText: 'Reservation Date',
),
Row(
children: <Widget>[
Container(
width: 130,
height: 30,
margin: EdgeInsets.fromLTRB(50.0, 0, 62, 0),
child: RaisedButton(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 6,
child: Text(
'Choose Date',
style: TextStyle(
fontSize: 12.0,
),
),
onPressed: () => _selectDate(context),
),
),
Text("${selectedDate.toLocal()}".split(' ')[0]),
],
),
TitleName(
titleText: 'Reservation Time',
),
Row(
children: <Widget>[
Container(
width: 130,
height: 30,
margin: EdgeInsets.fromLTRB(50.0, 0, 62, 0),
decoration: BoxDecoration(),
child: RaisedButton(
color: Colors.white,
child: Text(
'Choose Time',
style: TextStyle(
fontSize: 12.0,
),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
elevation: 6,
onPressed: () => _selectTime(context),
),
),
Text("${selectedTime.toString()}".split(' ')[0]),
],
),
TitleName(
titleText: 'Pick Up Address',
),
Container(
width: 320,
height: 40,
child: TextFormField(
maxLines: null,
minLines: null,
expands: true,
decoration: InputDecoration(
contentPadding:
EdgeInsets.fromLTRB(35.0, 10.0, 0, 10.0),
hintText: 'Address Here...',
hintStyle: TextStyle(
fontSize: 15.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
onChanged: (value) {
addressText = value;
},
),
),
TitleName(
titleText: 'Additional Notes',
infoIcon: Icons.info,
),
Container(
width: 320,
child: TextFormField(
maxLines: 4,
decoration: InputDecoration(
contentPadding:
EdgeInsets.fromLTRB(35.0, 10.0, 0, 10.0),
hintText: 'E.g. ',
hintStyle: TextStyle(
fontSize: 15.0,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15.0),
),
),
onChanged: (value) {
addNotes = value;
},
),
),
Container(
margin: EdgeInsets.fromLTRB(0, 15.0, 0, 0),
width: 75.0,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.rectangle,
border: Border.all(
color: Colors.black,
),
borderRadius: BorderRadius.circular(12.0),
),
child: IconButton(
icon: Icon(Icons.arrow_forward),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ConfirmationOrder(
addressText: addressText,
addNotes: addNotes,
)));
}),
),
],
),
],
),
],
),
),
],
)),
);
}
}
Below the first picture, there's a button to navigate to the second screen.
And here is the class where I extracted DropDownButton :
class MenuDropDown extends StatefulWidget {
final String dropdownText;
final String type;
MenuDropDown({this.dropdownText, this.type});
#override
_MenuDropDownState createState() => _MenuDropDownState();
}
class _MenuDropDownState extends State<MenuDropDown> {
String selectedItem;
List<String> dropdownItems = [];
List<String> groomingTypeList = ['Basic Grooming', 'Full Grooming'];
List<String> catBreedsList = [
'Persia',
'Anggora',
'Domestic',
'Maine Coon',
'Russian Blue',
'Slamese',
'Munchkin',
'Ragdoll',
'Scottish Fold',
];
List<String> catSizeList = [
'Small Size',
'Medium Size',
'Large Size',
'Extra Large Size',
];
List<String> addOnServicesList = [
'Spa & Massage',
'Shaving Hair / Styling',
'Injection Vitamis Skin & Coat',
'Cleaning Pet House and Environment',
'Fur Tangled Treatment',
];
List<String> getListBasedOnName(String value) {
print(value);
switch (value) {
case "groomingType":
return groomingTypeList;
break;
case "catBreeds":
return catBreedsList;
break;
case "catSize":
return catSizeList;
break;
case "addOnServices":
return addOnServicesList;
break;
}
return null;
}
#override
void initState() {
super.initState();
print(widget.type);
dropdownItems = getListBasedOnName(widget.type);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 10.0),
child: Container(
width: 325.0,
height: 50.0,
decoration: BoxDecoration(
boxShadow: [
BoxShadow(
color: Colors.black45,
offset: Offset(2.5, 5.5),
blurRadius: 5.0,
)
],
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: selectedItem,
hint: Padding(
padding: const EdgeInsets.fromLTRB(22.0, 0, 0, 0),
child: Text(
widget.dropdownText,
style: TextStyle(),
),
),
items: dropdownItems.map((String value) {
return new DropdownMenuItem<String>(
value: value,
child: new Text(value),
);
}).toList(),
onChanged: (value) {
setState(() {
selectedItem = value;
});
}),
),
),
);
}
}
I really got confused because the onChanged function in DropDownButton already used. I barely manage to do the normal pass data from the text widget. But from the Dropdownbutton and the date & time picker, I have no idea how to do it.
Is there any way to get the data from the first screen, because at the moment I still haven't learned about state management or Bloc. And the code still messy I haven't done the refactoring yet. I really Hope you can help with the solution, Thank you!
First, for the MenuDropDown, you're going to want to do a sort of extension for the onChanged method. Add a VoidCallback parameter to the widget's constructor like so:
typedef OnChangeCallback = void Function(dynamic value);
class MenuDropDown extends StatefulWidget {
final String dropdownText;
final String type;
final OnChangeCallback onChanged;
MenuDropDown({this.dropdownText, this.type, this.onChanged});
#override
_MenuDropDownState createState() => _MenuDropDownState();
}
and in the state, call that method as a part of DropdownButton's native onChanged callback:
onChanged: (value) {
setState(() {
selectedItem = value;
});
widget.onChanged(value);
}
And in _InformationDetailState you'll store the currently selected item for each input field and pass an onChanged function that updates the respective field for each input:
String catSize; //Declare at the top of _InformationDetailState
...
MenuDropDown(
dropdownText: 'Cat Size...',
type: "catSize",
onChanged: (value) {
catSize = value;
}
),
Now to pass the data to the next screen. It's never really absolutely necessary to use any kind of state management in your app and I've found that many people use it unnecessarily. In your case, it's absolutely not necessary for just passing data to a single other widget. You're already passing the addressText and addNotes correctly. Just extend this for each parameter you need to show on the confirmation screen. Alternatively, you could store all of the fields in a single Map instead of having a variable for each field and pass that Map to the confirmation page.
I have a RaisedButton widget and an AnimatedContainer widget in a screen, and the idea is that upon pressing the RaisedButton the width of the AnimatedContainer would then decrease in a given duration. The documentation of the AnimatedContainer states that all I would need to do is declare the width of the widget as a variable, and then setState(() {}) after changing the value and it will automatically change to that value during the duration. I have tried to implement this and upon pressing the RaisedButton the variables value definitely changes (based on printing the value of it after pressing it), however the widget's width does not change with it. Am I missing something obvious?
My Widgets are within a container in a PageView and my code for the RaisedButton and AnimatedContainer is as follows:
RaisedButton (
onPressed: () {
setState(() {
loginWidth = 70.0;
});
},
),
AnimatedContainer (
duration: new Duration (seconds: 2),
width: loginWidth,
height: 40,
color: Colors.red,
)
Here is my widget tree:
pages.add(
Container(
color: chSecondary,
child: Stack(
children: <Widget>[
Container (
child: Align (
child: Image(image: AssetImage("graphics/signin.png")),
alignment: Alignment.bottomCenter,
),
),
Form(
key: _formKey,
child: new Container(
padding: EdgeInsetsDirectional.only(top: 100, start: 15, end: 15, bottom: 15),
child: new Column(
children: <Widget>[
Container (
child: Image(image: AssetImage("graphics/login.png"), height: 200, width: 200,),
margin: EdgeInsetsDirectional.only(bottom: 20),
),
Container (
padding: EdgeInsets.all(25.0),
decoration: new BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.white,
),
child: Column (
children: <Widget>[
Align(
child: new Text("Email:", style: TextStyle(fontSize: tonSubTitle, color: Colors.black)),
alignment: Alignment.centerLeft,
),
new Container(
child: new TextFormField(
keyboardType: TextInputType.emailAddress,
controller: _email,
style: TextStyle(fontSize: tonText, color: Colors.black),
decoration: InputDecoration(
border: OutlineInputBorder(borderRadius: new BorderRadius.circular(tonRadius)),
contentPadding: EdgeInsetsDirectional.only(top: 15, start: 7.5),
focusedBorder: OutlineInputBorder(borderSide: new BorderSide(color: Colors.grey)),
hintText: "Email Address",
hintStyle: TextStyle(color: Colors.black),
),
validator: (value) {
if (value.isEmpty) {
return "Please enter an email";
}
if (!value.contains("#tonbridge-school.org")) {
return "Please enter a valid email address";
}
},
),
padding: const EdgeInsets.only(top: 10, bottom: 10)
),
Align (
child: new Text("Password:", style: TextStyle(fontSize: tonSubTitle, color: Colors.black)),
alignment: Alignment.centerLeft,
),
new Container(
child: new TextFormField(
obscureText: true,
controller: _password,
style: TextStyle(color: Colors.black, fontSize: tonText),
decoration: InputDecoration(
contentPadding: EdgeInsetsDirectional.only(top: 15, start: 7.5),
border: OutlineInputBorder(borderRadius: new BorderRadius.circular(tonRadius)),
focusedBorder: OutlineInputBorder(borderSide: new BorderSide(color: Colors.grey)),
hintText: "Password",
hintStyle: TextStyle(color: Colors.black),
),
validator: (value) {
if (value.isEmpty) {
return "Please enter a password";
}
},
),
padding: const EdgeInsets.only(top: 10, bottom: 10)
),
RaisedButton (
onPressed: () {
setState(() {
loginWidth = 70.0;
});
},
),
AnimatedContainer (
duration: new Duration (seconds: 2),
width: loginWidth,
height: 40,
color: Colors.red,
)
],
),
)
],
),
)
),
],
),
),
);
The code snippet you've posted is already correct.
Make sure that:
loginWidth is initialized
the new loginWidth value is actually different from the default value
I've copied it and built a minimal example so you can double check the rest of your code. This example also include a surrounding PageView:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyBody(),
),
);
}
}
class MyBody extends StatefulWidget {
#override
_MyBodyState createState() => _MyBodyState();
}
class _MyBodyState extends State<MyBody> {
double loginWidth = 40.0;
#override
Widget build(BuildContext context) {
return Center(
child: PageView(
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
RaisedButton (
child: Text('Animate!'),
onPressed: () {
setState(() {
loginWidth = 250.0;
});
},
),
AnimatedContainer (
duration: Duration (seconds: 1),
width: loginWidth,
height: 40,
color: Colors.red,
),
],
)
],
),
);
}
}
do you initialised loginWidth ?
var loginWidth =0.0;
Curve _curve = Curves.fastOutSlowIn;
_doanimation(){
setState(() {
loginWidth ==0.0? loginWidth =100: loginWidth =0.0;
});
}
change function with your case
Column(
children: <Widget>[
Text("welcome"),
RaisedButton(
onPressed: (){
_doanimation();
},
),
AnimatedContainer(
curve: _curve,
duration: Duration(seconds: 1),
width: loginWidth,
height:100
)
],
),
Where do you have loginWidth declared? It needs to outside the scope of the builder function or its value will get reinitialized on every build.
Can you update your example to show where you declare?
Correct:
class _WidgetState extends State<Widget> {
double loginWidth = 0;
#override
Widget build(BuildContext context) {
// return the new widget tree
}
}
Incorrect:
class _WidgetState extends State<Widget> {
#override
Widget build(BuildContext context) {
double loginWidth = 0;
//return the new widget tree
}
}