TextFormField selection handle is overlapping when scrolled offscreen - flutter

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,
),
];
}

Related

how to create a TextFormField function in flutter

TextFormField function in flutter
A FormField that contains a TextField.
This is a convenience widget that wraps a TextField widget in a FormField.
static formField({
required String label,
String? initialValue,
FormFieldSetter? onSaved,
ValueChanged? onChanged,
Icon? icon,
int? maxLines,
TextEditingController? controller,
TextInputType? keyboard,
}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: TextFormField(
validator: (value) {
if (value!.isEmpty) {
return 'please type the $label above';
}
},
initialValue: initialValue,
controller: controller,
maxLines: maxLines,
onChanged: onChanged,
onSaved: onSaved,
keyboardType: keyboard,
decoration: InputDecoration(
label: Text(label),
prefixIcon: icon,
hintText: 'Type $label here',
border: const OutlineInputBorder(),
),
),
);
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: Container(
child: TextFormField(
validator: (value) {
if (value!.isEmpty) {
return 'please type the $label above';
}
},
initialValue: initialValue,
controller: controller,
maxLines: maxLines,
onChanged: onChanged,
onSaved: onSaved,
keyboardType: keyboard,
decoration: InputDecoration(
label: Text(label),
prefixIcon: icon,
hintText: 'Type $label here',
border: const OutlineInputBorder(),
),
),
),
);
i think this will work... wrap the TextFormField inside the container.. if there is any wrong syntax change it

How to redirect my app to the add contact section

I'm creating a project where I bring in data from a customer model that has a phone number and I need to be able to add that phone number as a new contact, but I haven't found a way to redirect my app to Add Contact. I have only seen how to redirect calls.
Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Container(
width: 50,
child: FlatButton(
child: Icon(Icons.call, color: Color(0xFF7AAF00),),
onPressed:() async{
String url = 'tel:${model.cliente.telefono}';
if (await canLaunch(url)) {
await launch(url);
}else{
throw 'No se pudo $url';
}
}
),
),),
Container(
width: 50,
child: FlatButton(
child: Icon(Icons.contact_page_outlined, color: Color(0xFF7AAF00),),
onPressed: (){
},
),),
to add a new Contact to your Phone with Flutter I would suggest using a package from pub dev
Klick here to view flutter_contact package
the package has a function called:
await Contacts.addContact(newContact);
Depending on what is your goal etc.
Here is a full example from the Package:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_contact/contacts.dart';
import 'package:flutter_contact/flutter_contact.dart';
class AddContactPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => _AddContactPageState();
}
class _AddContactPageState extends State<AddContactPage> {
Contact contact = Contact();
PostalAddress address = PostalAddress(label: 'Home');
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add a contact'),
actions: <Widget>[
TextButton(
onPressed: () {
_formKey.currentState!.save();
contact.postalAddresses = [address];
Contacts.addContact(contact);
Navigator.of(context).pop();
},
child: Icon(Icons.save, color: Colors.white),
)
],
),
body: Container(
padding: EdgeInsets.all(12.0),
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(labelText: 'First name'),
onSaved: (v) => contact.givenName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Middle name'),
onSaved: (v) => contact.middleName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Last name'),
onSaved: (v) => contact.familyName = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Prefix'),
onSaved: (v) => contact.prefix = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Suffix'),
onSaved: (v) => contact.suffix = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Phone'),
onSaved: (v) => contact.phones = [
if (v != null && v.isNotEmpty) Item(label: 'mobile', value: v)
],
keyboardType: TextInputType.phone,
),
TextFormField(
decoration: const InputDecoration(labelText: 'E-mail'),
onSaved: (v) => contact.emails = [
if (v != null && v.isNotEmpty) Item(label: 'work', value: v)
],
keyboardType: TextInputType.emailAddress,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Company'),
onSaved: (v) => contact.company = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Job'),
onSaved: (v) => contact.jobTitle = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Street'),
onSaved: (v) => address.street = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'City'),
onSaved: (v) => address.city = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Region'),
onSaved: (v) => address.region = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Postal code'),
onSaved: (v) => address.postcode = v,
),
TextFormField(
decoration: const InputDecoration(labelText: 'Country'),
onSaved: (v) => address.country = v,
),
],
),
),
),
);
}
}

Set width size of TextFormField with prefix to match normal TextFormField

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;
},
),),

DropdownButton selection calls the onValidate functions of other fields in Flutter

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.

How to aline the textfield and counter on the same line on flutter using maxLength?

How to make the counter and text on the same line or same row? when i use maxlength the counter appear below the textfield.
Widget _nameTextField(String hintText) {
return TextFormField(
controller: TextEditingController(
text: _template.name,
),
decoration: InputDecoration(
hintText: hintText,
contentPadding: EdgeInsets.all(15)
),
cursorRadius: Radius.circular(10),
keyboardType: TextInputType.text,
autofocus: true,
maxLength: 40,
// maxLengthEnforced: true,
validator: (val) {
if (val.isEmpty) {
return 'Please enter text.';
}
return null;
},
onChanged: (val) {
_template.name = val;
print(val);
},
);
}
Add suffix text and suffixstyle to your input decoration. Like this
Widget _nameTextField(String hintText) {
return TextFormField(
controller: TextEditingController(
text: _template.name,
),
decoration: InputDecoration(
hintText: hintText,
contentPadding: EdgeInsets.all(15),
suffixText: '11/40',
suffixStyle: TextStyle(color: Colors.white)
),
cursorRadius: Radius.circular(10),
keyboardType: TextInputType.text,
autofocus: true,
maxLength: 40,
// maxLengthEnforced: true,
validator: (val) {
if (val.isEmpty) {
return 'Please enter text.';
}
return null;
},
onChanged: (val) {
_template.name = val;
print(val);
},
);
}
Fixed the problem. by adding suffix and setstate to update the value when tying.
Widget _nameTextField(String hintText) {
int nameLength = _template.name.length;
return TextFormField(
controller: TextEditingController(
text: _template.name,
),
decoration: InputDecoration(
hintText: hintText,
contentPadding: EdgeInsets.all(15),
suffixText: nameLength.toString() + "/ 40",
suffixStyle: TextStyle(color: Colors.grey),
counterText: "",
),
cursorRadius: Radius.circular(10),
keyboardType: TextInputType.text,
autofocus: true,
maxLength: 40,
validator: (val) {
if (val.isEmpty) {
return 'Please enter text.';
}
return null;
},
onChanged: (val) {
_template.name = val;
setState(() {
nameLength = _template.name.length;
});
print(val);
},
);
}