Related
is there anyway to make TextFormField with prefix have same size with normal TextFormField? Tried to wrap it with container, but I'm afraid if using different device with different width will affect it. Thank you.
This is my code
TextFormField(
textInputAction: TextInputAction.next,
controller: namaField,
focusNode: _namaFocus,
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
icon: Icon(Icons.person),
labelText: 'Nama Lengkap',
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Mohon Isikan Data';
}
return null;
},
),
SizedBox(height: 5),
TextFormField(
textInputAction: TextInputAction.done,
autovalidateMode: AutovalidateMode.always,
keyboardType: TextInputType.phone,
controller: noHpField,
focusNode: _noHpFocus,
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: true,
prefixIcon: Padding(
padding: EdgeInsets.fromLTRB(4, 6, 4, 7),
child: Text("+62",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold)),
),
prefixIconConstraints:
BoxConstraints(minWidth: 0, minHeight: 0),
icon: Icon(Icons.phone_android),
labelText: 'No HP',
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Mohon Isikan Data';
}
return null;
},
),
Remove isDense: true.
Full Code :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Welcome Flutter'),
),
body: new SafeArea(
top: true,
bottom: true,
child: Column(
children: [
SizedBox(
height: 20,
),
TextFormField(
textInputAction: TextInputAction.next,
controller: namaField,
focusNode: _namaFocus,
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
icon: Icon(Icons.person),
labelText: 'Nama Lengkap',
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Mohon Isikan Data';
}
return null;
},
),
SizedBox(height: 5),
TextFormField(
textInputAction: TextInputAction.done,
autovalidateMode: AutovalidateMode.always,
keyboardType: TextInputType.phone,
controller: noHpField,
focusNode: _noHpFocus,
decoration: const InputDecoration(
border: OutlineInputBorder(),
// isDense: true, <-- Comment this.
prefixIcon: Padding(
padding: EdgeInsets.fromLTRB(4, 6, 4, 7),
child: Text("+62", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
prefixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
icon: Icon(Icons.phone_android),
labelText: 'No HP',
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Mohon Isikan Data';
}
return null;
},
),
],
)));
}
}
Try this:
Container(
width:MediaQuery.of(context).size.width*0.90,
child: TextFormField(
textInputAction: TextInputAction.next,
controller: namaField,
focusNode: _namaFocus,
autovalidateMode: AutovalidateMode.always,
decoration: const InputDecoration(
border: OutlineInputBorder(),
icon: Icon(Icons.person),
labelText: 'Nama Lengkap',
),
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Mohon Isikan Data';
}
return null;
},
),),
The cursor is continuously moving to front while typing data in text field.
Before it is not there but once I wired onChange event, it is happening.
My issue:
My code:
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: descriptionController,
decoration: InputDecoration(
labelText: 'Any Details',
hintText: "e.g. Whatever details you want to save",
labelStyle: textStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0)
),
),
keyboardType: TextInputType.text,
onChanged: (String string){
setState(() {
if (string != null){
coinOrder.description = string;
}
});
},
),
)
you can use onFieldSubmitted
that will work too
enter image description here
You can use one string variable to store value so this issue will not accrue.
see the below code.
String? details;
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
controller: descriptionController,
decoration: InputDecoration(
labelText: 'Any Details',
hintText: "e.g. Whatever details you want to save",
labelStyle: textStyle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0)
),
),
keyboardType: TextInputType.text,
onChanged: (String string){
setState(() {
if (string != null){
//coinOrder.description = string;
details=string;
}
});
},
),
)
Not sure what I'm missing. When selecting the value of drop-down, form onVaidate() fired which hence my other fields are showing the error. How can I stop it? Here is the code
Widget build(BuildContext context) {
// return Scaffold(
// appBar: AppBar(title: Text("Registration")),
// body: Center(child: Text(widget.user.displayName)),
// );
FirebaseUser user = widget.user;
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("Registration"),
),
body: SafeArea(
top: false,
bottom: false,
child: Form(
key: _formKey,
autovalidate: true,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
children: <Widget>[
TextFormField(
validator: (value) => value.isEmpty ? 'Name is Required' : null,
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your first and last name',
labelText: 'Name',
),
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.phone),
hintText: 'Enter a phone number',
labelText: 'Phone',
),
initialValue: user.phoneNumber,
enabled: user.phoneNumber == null,
keyboardType: TextInputType.phone,
validator: (value) => value.isEmpty ? 'Phone number is Required' : null
// inputFormatters: [
// WhitelistingTextInputFormatter.digitsOnly,
// ],
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.email),
hintText: 'Enter a email address',
labelText: 'Email',
),
initialValue: user.email,
enabled: user.email == null,
validator: (value) => value.isEmpty ? 'Email is Required' : null,
keyboardType: TextInputType.emailAddress,
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.remove_red_eye),
hintText: 'Enter the Password',
labelText: 'Password',
),
keyboardType: TextInputType.text,
obscureText: true,
validator: (value) => value.isEmpty ? 'Password is Required' : null
),
FormField(
builder: (FormFieldState state) {
return InputDecorator(
decoration: InputDecoration(
icon: const Icon(Icons.card_membership),
labelText: 'ID Type',
),
isEmpty: _profile.govId == null,
child: DropdownButtonHideUnderline(
child: DropdownButton(
value: _profile.govId,
isDense: true,
onChanged: (String newValue) {
setState(() {
_profile.govId = newValue;
state.didChange(newValue);
});
},
items: _govtIds.map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
},
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.confirmation_number),
hintText: 'Enter your Governmenr ID number',
labelText: 'ID Number',
),
keyboardType: TextInputType.datetime,
validator: (value) => value.isEmpty ? 'ID Number is Required' : null
),
FormField(
builder: (FormFieldState state) {
return InputDecorator(
decoration: InputDecoration(
icon: const Icon(Icons.business),
labelText: 'Block Info',
),
isEmpty: _profile.block == null,
child:
// Column(children: [RadioListTile(title: Text("A")),RadioListTile(title: Text("B"))]),
// Radio(
// value: 0,
// groupValue: _blocks,
// onChanged: (value){}),
DropdownButtonHideUnderline(
child:
DropdownButton(
value: _profile.block,
isDense: true,
onChanged: (String newValue) {
setState(() {
_profile.block = newValue;
state.didChange(newValue);
});
},
items: _blocks.map((String value) {
return DropdownMenuItem(
value: value,
child: Text(value),
);
}).toList(),
),
),
);
},
),
TextFormField(
decoration: const InputDecoration(
icon: const Icon(Icons.home),
hintText: 'Enter your Flat number',
labelText: 'Flat number',
),
inputFormatters: [LengthLimitingTextInputFormatter(3)],
validator: (value) {
if (value.isEmpty) {
return 'Flat number is Required';
} else if (_profile.isValidHouseNumber() == false) {
return 'Invalid flat number';
} else {
return null;
}
},
keyboardType: TextInputType.number,
onChanged:(value) {
_profile.houseNo = value;
},
),
Padding(
padding: EdgeInsets.fromLTRB(38.0, 30.0, 0.0, 0.0),
child: SizedBox(
height: 50.0,
child: FlatButton(
// elevation: 5.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
color: Colors.green,
child: Text('Submit',
style: TextStyle(fontSize: 20.0, color: Colors.white)),
onPressed: _validateAndSubmit,
),
))
],
))),
);
}
Call a method to return null in the validate fucntion all the other fields. That will clear the validation. It's a fix, but doesn't solve the problem.
There seems to be nothing wrong with the code above, can you add the code for the other fields too?
EDIT:
The reason all the other fields validate is because of the autovalidate: true property of the parent Form widget. Remove it and wrap each TextFormField with a Form with different keys.
For example, your TextFormField should look as follows:
Form(
key: _formKey[0],
child: TextFormField(
validator: (value) => value.isEmpty ? 'Name is Required' : null,
decoration: const InputDecoration(
icon: const Icon(Icons.person),
hintText: 'Enter your first and last name',
labelText: 'Name',
),
),
),
Wrap it with a Form, _formKey is declared as
List<GlobalObjectKey<FormState>> _formKey = new List(number_of_keys);
Call the respective setState like so:
_formKey[position].currentState.setState((){});
And don't forget to remove the parent Form widget.
The problem I'm facing is that when my ListView is scrolled off view, the text selection handle can still be seen. Here's a photo for reference, it will explain it much better: https://imgur.com/a/Lg93cV9
I've only tested it on Android, I don't have an iOS device to test it on.
Here's my main widget
#override
Widget body() {
return StoreConnector<AppState, Step1IntroViewModel>(
converter: (store) => Step1IntroViewModel.fromStore(store),
builder: (context, viewModel) {
return StepMainTemplate.build(
this.context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_buildImageWidget(),
Text(
'Upload image',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
),
),
Expanded(
child: ListView(
children: <Widget>[
StepMainTemplate.title(
subtitle: 'Step 1 of 2',
title: 'Introduce yourself',
),
ProfileForm(
key: _profileFormKey,
initProfile: viewModel.profileState?.data,
),
Container(
padding: const EdgeInsets.fromLTRB(0, 40, 0, 20),
width: double.infinity,
child: RedOneButton.text(
text: "Next",
function: () {
var profile = _profileFormKey.currentState.save();
if (profile != null) {
viewModel.saveProfile(profile);
ExtendedNavigator.ofRouter<Router>()
.pushNamed(Routes.step2Route);
}
}),
),
],
),
),
],
),
);
},
);
}
And here's the ProfileForm
class ProfileForm extends StatefulWidget {
final Profile initProfile;
const ProfileForm({Key key, this.initProfile}) : super(key: key);
#override
ProfileFormState createState() => ProfileFormState();
}
class ProfileFormState extends State<ProfileForm> {
final _formKey = GlobalKey<FormState>();
List<String> genders = ['Male', 'Female'];
List<String> positions = ['SA', 'PSA', 'PS', 'VPS'];
String _fullName;
String _position;
String _gender;
String _number;
String _email;
String _urlName;
#override
void initState() {
if (widget.initProfile != null) {
_fullName = widget.initProfile.fullName;
_position = widget.initProfile.position;
_gender = widget.initProfile.gender;
_number = widget.initProfile.phone;
_email = widget.initProfile.email;
_urlName = widget.initProfile.urlName;
}
super.initState();
}
Profile save() {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
var profile = Profile(
fullName: _fullName,
position: _position,
gender: _gender,
phone: _number,
email: _email,
urlName: _urlName);
return profile;
}
return null;
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ProfileTextFormField(
initialValue: _fullName,
labelText: 'Full name',
hintText: 'Please enter your full name',
validator: (value) => Validator.validateFullName(value),
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
onSaved: (value) => _fullName = value,
),
ProfileDropDownField(
initialValue: _position,
labelText: 'Position',
hintText: 'Please enter your position',
items: positions,
onSelected: (value) => _position = value),
ProfileTextFormField(
initialValue: _number,
labelText: 'Phone number',
hintText: 'Please enter your phone number',
validator: (value) => Validator.validatePhone(value),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
onSaved: (value) => _number = value,
),
ProfileTextFormField(
initialValue: _email,
labelText: 'Email',
hintText: 'Please enter your email',
validator: (value) => Validator.validateEmail(value),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
onSaved: (value) => _email = value,
textCapitalization: TextCapitalization.none,
),
ProfileRadioField(
initialValue: _gender,
labelText: 'Gender',
items: genders,
onSelected: (value) => _gender = value,
),
ProfileTextFormField(
initialValue: _urlName,
labelText: 'URL name',
hintText: 'Your preferred URL name',
textInputAction: TextInputAction.done,
onSaved: (value) => _urlName = value,
textCapitalization: TextCapitalization.none,
),
])),
);
}
}
Also related to https://github.com/flutter/flutter/issues/13182
I have managed to fix this problem by directly putting the TextFormField directly inside a ListView. This is the only way I could get it to work properly.
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(8.0),
children: <Widget>[
widget.header ?? SizedBox.shrink(),
..._buildContentList(),
widget.footer ?? SizedBox.shrink(),
],
));
}
List<Widget> _buildContentList() {
var list = <Widget>[];
if (widget.hasProfile) list.addAll(_buildProfileFormList());
if (widget.hasStory) list.addAll(_buildStoryFormList());
return list;
}
List<Widget> _buildProfileFormList() {
return <Widget>[
ProfileTextFormField(
initialValue: _profile.fullName,
labelText: 'Full name',
hintText: 'Please enter your full name',
validator: (value) => Validator.validateFullName(value),
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.words,
onSaved: (value) => _profile.fullName = value,
),
ProfileDropDownField(
initialValue: _profile.position,
labelText: 'Position',
hintText: 'Please enter your position',
items: positions,
onSelected: (value) => _profile.position = value),
ProfileTextFormField(
initialValue: _profile.phone,
labelText: 'Phone number',
hintText: 'Please enter your phone number',
validator: (value) => Validator.validatePhone(value),
keyboardType: TextInputType.phone,
textInputAction: TextInputAction.next,
onSaved: (value) => _profile.phone = value,
),
ProfileTextFormField(
initialValue: _profile.email,
labelText: 'Email',
hintText: 'Please enter your email',
validator: (value) => Validator.validateEmail(value),
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
onSaved: (value) => _profile.email = value,
textCapitalization: TextCapitalization.none,
),
ProfileRadioField(
initialValue: _profile.gender,
labelText: 'Gender',
items: genders,
onSelected: (value) => _profile.gender = value,
),
ProfileTextFormField(
initialValue: _profile.urlName,
labelText: 'URL name',
hintText: 'Your preferred URL name',
textInputAction: TextInputAction.done,
onSaved: (value) => _profile.urlName = value,
textCapitalization: TextCapitalization.none,
),
];
}
List<Widget> _buildStoryFormList() {
return <Widget>[
ProfileTextFormField(
initialValue: _profile.aboutYourself,
labelText: '',
hintText:
'',
textInputAction: TextInputAction.next,
textCapitalization: TextCapitalization.sentences,
keyboardType: TextInputType.multiline,
onSaved: (value) => _profile.aboutYourself = value,
maxLines: 3,
),
ProfileTextFormField(
initialValue: _profile.sharingMessage,
labelText: '',
hintText: '',
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
maxLength: 300,
onSaved: (value) => _profile.sharingMessage = value,
maxLines: 5,
),
];
}
I have a problem when show an alert dialog. My AlertDialog too tall (many TextFields). When I click any below TextFields, keyboard makes dialog short and SingleChildScrollView (I wrap my content in it) does not scroll to the TextField which has focus. However, when I type a character on that textfield, SingleChildScrollView works fine, scrolls to that TextField automatically. So, how do SingleChildScrollView scrolls to a TextField when user click in the first time (has focus).
p/s: I tried add a listener into FocusNode, but it does not work.
For example: I will click 'Fullname' field (picture 1) and the keyboard makes dialog short and I can not see the 'Fullname' field (picture 2). When I type a character, the scrollview scroll to that correctly (picture 3).
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(
'Dialog Title',
textAlign: TextAlign.center,
),
titleTextStyle: TextStyle(
fontSize: 16.0,
color: Theme.of(context).textTheme.title.color,
fontWeight: FontWeight.w800,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'.toUpperCase()),
),
FlatButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('OK'.toUpperCase()),
),
],
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(
focusNode: _nodePhone,
maxLength: 10,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'Phone number',
),
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_nodeEmail);
},
),
TextField(
focusNode: _nodeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
),
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_nodeFullname);
},
),
TextField(
focusNode: _nodeFullname,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'Fullname',
),
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_nodePassword);
},
),
TextField(
focusNode: _nodePassword,
obscureText: true,
keyboardType: TextInputType.text,
decoration: InputDecoration(
labelText: 'Password',
),
textInputAction: TextInputAction.done,
),
],
),
),
);
}
I also have this problem, I manage to solve it by give a controller to SingleChildScrollView then when onTap on TextField. Await for few millisecond for dialog to collapse then scroll to the end of dialog
Here is some sample code
//declare first
ScrollController _scrollController = ScrollController();
//your field code
SingleChildScrollView(
// give a controller
controller: _scrollController,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: < Widget > [
TextField(
focusNode: _nodePhone,
maxLength: 10,
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'Phone number',
),
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_nodeEmail);
},
),
//suppose this is your very last textfield
TextField(
focusNode: _nodeEmail,
keyboardType: TextInputType.emailAddress,
decoration: InputDecoration(
labelText: 'Email',
),
textInputAction: TextInputAction.next,
onEditingComplete: () {
FocusScope.of(context).requestFocus(_nodeFullname);
},
onTap: () {
Future.delayed(Duration(milliseconds: 500), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 200),
curve: Curves.ease);
});
},
),
]
)
);