Checkbox validation with native animation - flutter

A TextFormField has a native error animation if a validation occurs. The labelText turns red and slightly wiggles from left to right. Furthermore, a red validation text slides down underneath the text area.
But Checkbox validation in flutter is unfortunately not natively supported.
I would like the same kind of animation that Vuetify supports: https://vuetifyjs.com/en/components/forms/#misc (click on the validate button, and notice how the Checkbox behaves) which is similar to how flutter does it.
So is it possible to extract/copy these error animations/transitions from the flutter framework and use them on a Checkbox/Text (or other places for that matter)?
This is my wrapped checkbox that shows an error text underneath the checkbox if it does not validate (the value is false):
class MyCheckbox extends FormField<bool> {
MyCheckbox({required String title, required FormFieldSetter<bool> onSaved, FormFieldValidator<bool>? validator, bool initialValue = false})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<bool> state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Builder(
builder: (BuildContext context) => CheckboxListTile(
contentPadding: EdgeInsets.only(left: 0),
activeColor: Color(0xff00aeef),
side: BorderSide(color: state.hasError ? Theme.of(context).errorColor : Theme.of(context).primaryColor),
title: Text(title, style: TextStyle(color: state.hasError ? Theme.of(context).errorColor : Theme.of(context).primaryColor)),
value: state.value,
onChanged: state.didChange,
controlAffinity: ListTileControlAffinity.leading,
),
),
if (state.hasError)
Builder(
builder: (BuildContext context) => Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
state.errorText!,
style: TextStyle(color: Theme.of(context).errorColor),
),
),
)
],
);
},
);
}

Related

Underlines of TextFormFields in form not uniform no matter what I do. How to keep a uniform thickness for all underlines of TextFormFields?

I'm brand new to flutter and building a form in Flutter containing multiple TextFormFields, and a radio button. But, something weird is happening where the textformfields above the radio buttons are not uniform and keep changing randomly based on other changes I make. In my screenshot 1, you can see the first 3 text fields are uniform, but the one after the radio button is lighter. This is the closest I've come to achieving uniform text field underlines. I've tried adding and removing a lot of things such as padding, sized boxes, containers, etc, and they affect the underlines in different ways.
For instance, changing the mainAxisAlignment under Form->Column->Padding to 'start' instead of spaceEvenly causes the 1st 3 underlines to have thickness in increasing order and the last text box is fine Check screenshot 2. How can I make it so that all the underlines of the TextFormFields are equally thick and uniform irrespective of any other changes I make? Is there any better way of doing radio buttons than what I have done? Is there a better element in flutter than radio buttons for what I have done? I would also love it if I could make the radio buttons horizontal instead of vertical. The underlines are working fine and not fading in chrome, but they are in android emulator (Pixel 5 API30). I want this to work regardless of platform.
main.dart:
import 'package:flutter/material.dart';
import 'package:hospital_finance_app/pages/OPD.dart';
import 'package:hospital_finance_app/pages/login_page.dart';
import 'package:hospital_finance_app/utils/routes.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(const hospApp());
}
class hospApp extends StatelessWidget {
const hospApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login Demo',
home: OPD(),
);
}
}
OPD.dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hospital_finance_app/utils/routes.dart';
enum paymentMethod { Cash, Other }
class OPD extends StatefulWidget {
#override
State<OPD> createState() => _OPDState();
}
class _OPDState extends State<OPD> {
paymentMethod? _method = paymentMethod.Cash;
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: AppBar(backgroundColor: Colors.black),
body: Form(
child:Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextField(
decoration: InputDecoration(
labelText: "Name",
hintText: "Name",
),
),
TextFormField(
decoration: InputDecoration(
hintText: "Mobile number",
labelText: "Mobile Number"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
),
TextFormField(
decoration: InputDecoration(
labelText: "Amount",
hintText: "Amount"),
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[FilteringTextInputFormatter.digitsOnly],
),
Container(
child: Text("Mode of Payment", style: TextStyle(fontSize: 16, color: Colors.black54, fontFamily: "OpenSans", fontWeight: FontWeight.w400 ), textAlign: TextAlign.left),alignment: Alignment.centerLeft,),
Column(
children: <Widget>[
ListTile(
title: const Text('Cash'),
leading: Radio<paymentMethod>(
value: paymentMethod.Cash,
groupValue: _method,
onChanged: (paymentMethod? value) {
setState(() {
_method = value;
});
},
),
),
ListTile(
title: const Text('Other'),
leading: Radio<paymentMethod>(
value: paymentMethod.Other,
groupValue: _method,
onChanged: (paymentMethod? value) {
setState(() {
_method = value;
});
},
),
),
],
),
TextFormField(
decoration: InputDecoration(labelText: "Comments/Notes",hintText: "Comments/Notes"),
),
ElevatedButton(
onPressed:(){ Navigator.pushNamed(context, MyRoutes.opdRoute); },
child: Text("Submit"),
style: TextButton.styleFrom(minimumSize: Size(100,30)),)
]
),
)
),
));
}
}
Screen shot of my application on android emulator with mainAxisAlignment spaceEvenly
Screen shot of my application on android emulator with mainAxisAlignment start.
this is a bug from the android emulator. try running your app on a real device.
When you use mainAxisAlignment as 'start', all the TextFormFields are placed one after another without any spacing between them, so the line thickness is increased (two lines overlap).
Instead, when you spaceEvenly, TextFormFields are separated and the line appears thin, the original thickness.

TextFormField retrieves the focus and reassigns the initial value when the keyboard is closed

I find myself working with TextFormField hand in hand with DropdownButton. There is a strange process going on inside the view. I put the steps below:
To the TexFormField, I'm assigning an initial value: controller: _nameController..text = datumAdministrative.name.
I enter a new value to the TextFormField.
When the DropdownButton is deployed, the keyboard is closed the whole Widget is redrawn, which causes the TextFormField to recover its initial value and does not keep the new value entered.
Is there any way to avoid, that the TextFormField returns to its initial value when selecting DropdownButton? I would appreciate if you could help me with a post or feedback.
Code Scaffold:
return Scaffold(
resizeToAvoidBottomPadding: false,
resizeToAvoidBottomInset: true,
appBar: AppBar(
backgroundColor: ColorsTheme.primary,
leading: GestureDetector(
onTap: () {
Navigator.pushReplacementNamed(context, 'administrativeListData');
},
child: Container(
padding: EdgeInsets.all(16),
child: FaIcon(FontAwesomeIcons.arrowLeft)),
),
centerTitle: true,
title: Text(datumAdministrative.name +
' ' +
datumAdministrative.lastNameFather),
automaticallyImplyLeading: false),
body: Stack(
children: <Widget>[
ImageBackground(),
_sizeBoxHeight(),
Container(
child: Form(
key: formValidator.formKey,
child: ListView(
children: <Widget>[
_containerImageUser(context, datumAdministrative),
_sizeBoxHeight(),
CustomCardExpansionTile(
title: Constants.administrativeData,
icon: FontAwesomeIcons.addressBook,
widget: Container(
padding: EdgeInsets.all(15),
child: responsiveDataAdministrative(context)),
),
CustomCardExpansionTile(
title: Constants.addressInformationAdministrative,
icon: FontAwesomeIcons.houseUser,
widget: Container(
padding: EdgeInsets.all(15),
child: responsiveAddressInformationAdministrative(
context))),
CustomCardExpansionTile(
title: Constants.userDataSystem,
icon: FontAwesomeIcons.idCard,
widget: Container(
padding: EdgeInsets.all(15),
child: responsiveInformationSystemAdministrative(
context))),
],
),
),
),
],
),
);
Code TextFormField:
TextFormField(
controller: _nameController..text = datumAdministrative.name,
keyboardType: TextInputType.text,
textInputAction: null,
textCapitalization: TextCapitalization.sentences,
onChanged: (value) => formValidator.name = value,
validator: (value) {
if (formValidator.fieldValidText(value)) {
return null;
} else {
return Constants.nameAdministrativeMessage;
}
});
I see that you set the value for the text property of TextEditingController inside the build method. So, it will be invoked whenever the widget rebuilds, causing the value for the field back to the initial one.
The docs actually tells about it:
text property
Setting this will notify all the listeners of this
TextEditingController that they need to update (it calls
notifyListeners). For this reason, this value should only be set
between frames, e.g. in response to user actions, not during the
build, layout, or paint phases.
To fix it, you should remove the cascade notation here:
controller: _nameController..text = datumAdministrative.name,
Do it inside initState() instead:
#override
void initState() {
_nameController.text = datumAdministrative.name;
}

Control position of validation label

When using Form validator the default position of the message label is on the bottom, is there a way to put it on the top of the Text field?
You can not change position on validation label in TextFormField
But you can build your own TextFormField by using FormField widget and custumize its children to look like a normal TextFormField this is a working example made with FormField
FormField(
// initialValue: 0,
autovalidate: true,
validator: normalValidator,
builder: (state) {
state.validate();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
state.errorText!=null? Text(state.errorText, style: TextStyle(color: Colors.red, fontSize: 12),):Container(),
DropdownButton(
isExpanded: true,
value: state.value,
items: _choices,
onChanged: (v) {
state.didChange(v);
setState((){});
}),
],
);
}),
You can now customize it to sweet your needs. Find more about FormField here on official doc

How to fix "BoxConstraints forces an infinite width" and how to delete an item from a list?

I am a beginner in Flutter and I can not get the result I want because I have a "layout problem". I tried several things for hours...
Technical problem:
When I click on "add player button" to add a new TextField to set more play name, I have an error: "BoxConstraints forces an infinite width". So, the TextField isn't displayed.
Other features that I do not know how to realize:
- I have a "dropdownmenu' to choose difficulty game. Why the default text isn't displayed (Difficulty: normal)?
- How to do this: When the user clicks on the icon "remove" (see suffixIcon on TextField), then the corresponding fieldText is deleted?
- How to do this: When user click to add player the default HintText is incremented (Player 2, Player 3, Player 4, ....)?
- Why my background gradient doesn't fix when user scroll page? (like in HTML/CSS 'background-attachment: fixed') ?
import 'package:flutter/material.dart';
/// Styles
///
class Homepage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<Homepage> {
String dropdownValue = 'Normal';
List<TextField> _playerList = new List(); //_playerList is my List name
int playerListIndex = 0; //Count the number of times "Add" button clicked
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(children: <Widget>[
// To have a gradient background
new Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
decoration: new BoxDecoration(
gradient: new LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
stops: [0.1, 1],
colors: [
Colors.redAccent[200],
Colors.red[300],
],
tileMode:
TileMode.repeated,
),
),
//I need SingleChildScrollView to haven't "Overflow yellow bar on bottom screen"
child: SingleChildScrollView(
child: Container(
padding: EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// Box to add big image
SizedBox(
height: 450,
),
// DROP DOWN MENU to choose difficulty modes
InputDecorator(
decoration: InputDecoration(
prefixIcon: const Icon(Icons.games),
filled: true,
),
child: DropdownButtonHideUnderline(
child: new DropdownButton<String>(
value: dropdownValue,
hint: Text('Difficulty normal'),
isDense: true,
items: <String>['Normal', 'Easy', 'Hard']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String newValue) {
setState(() {
dropdownValue = newValue;
});
},
),
),
),
//Field player name 1. At least I need 1 player
new TextField(
maxLength: 20,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person),
filled: true,
hintText:"Player 1",
counterText: "",
),
),
//Field for other player name 2, 3, 4, ...
new ListView.builder(
padding: EdgeInsets.all(0),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _playerList.length,
itemBuilder: (context, index) {
TextField widget = _playerList.elementAt(index);
return widget;
}),
//Button to add other field text to set player name. The user can delete a player on click on "clear icon button"
new Align(
alignment: Alignment.centerRight,
child: FlatButton.icon(
color: Colors.black,
icon: Icon(
Icons.add,
color: Colors.white,
size: 18,
),
label: Text('Add player',
style:
TextStyle(fontSize: 12, color: Colors.white)),
onPressed: () {
setState(() {
playerListIndex++;
_playerList.add(
new TextField(
maxLength: 20,
decoration: InputDecoration(
prefixIcon: const Icon(Icons.person),
suffixIcon: new IconButton(
icon: Icon(Icons.clear),
onPressed: () {
_playerList.removeAt(playerListIndex); //Doesn't work
}),
filled: true,
hintText: "Player $playerListIndex", //Doesn't work i would like Player 2, Player 3, ...
counterText: "",
),
),
);
print(playerListIndex);
print(_playerList);
print(_playerList[0]);
});
}),
),
new ButtonTheme(
minWidth: 350,
height: 45,
child: FlatButton(
color: Colors.black,
child: Text('Play',
style:
TextStyle(fontSize: 18, color: Colors.white)),
onPressed: () {
// Perform some action
},
),
),
],
),
),
),
),
),
]),
);
}
}
I think that I understood what's going on. Let's begin:
First. Your TextField is not showing up and you're getting an error because you didn't set a width and a height for it, so, it's trying to get the maximum size as it can get. Solution for this? Wrap it in a Container widget (or SizedBox) and give it a width and a height. That should fix the first problem.
Second. Your DropDownMenu's hint text (Difficulty Normal) is not showing because you're not passing the parameter "hint" in the DropDown widget, See the hint propierty below.
Third. You want to clear the TextField string when click on a suffix, right? To achieve this, pass a GestureDetector or an IconButton as your suffix component, create a TextEditingController and pass it in the TextField, in the controller section, then, on the onPressed/onTap method (depending on which Widget you'll set) set this: _textController.text.clear() method, inside the setState function. This should fix the problem.
Forth. You want the list of player's string to increase as you add more players right? You have many ways to achieve this, one of them is creating an empty list, you can mock to test it, and then, everytime you click the "Add player" button, you'll add a String corresponding to the value inside a for loop passing the for's index as int value in the "Player $index". Again, the for method will receive a setState method, and inside the setState, your logic to add one more Player.
And finally, your gradient does not keep fixed? Hmm, I think you can pass a proprierty in the Scaffold widget called resizeToAvoidBottomInset and set it to false.
If something did not work as you expected, reply it and I'll help you out.

White Box Obscures View When Keyboard Appears

For some reason after updating flutter, one of the sections of my app has been broken. I have a list of text form widgets set in a SingleChildScrollView. Whenever I press one of the text forms, the keyboard appears and an empty white box pushes itself up into the field of view, obscuring the text entry boxes.
After having some trouble with text entry in a list view before I followed the advice of this link: https://www.didierboelens.com/2018/04/hint-4-ensure-a-textfield-or-textformfield-is-visible-in-the-viewport-when-has-the-focus/
It effectively solved the problems I had beforehand where the text entry boxes were not remaining visible when the keyboard appears. But now the while box is appearing and obscuring the view. I also made sure to use:
resizeToAvoidBottomPadding: false
as is regularly advised.
Here is the code in question with one of the text field widget's code too:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new SafeArea(
child: new Form(
key: _formKey,
child: new SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
circumstances(),
divider(),
description(),
divider(),
externalHappenings(),
divider(),
internalHappenings(),
divider(),
reflectionsAndCorrections(),
divider(),
abatement(),
divider(),
footerButtons()
],
),
)
)
),
resizeToAvoidBottomPadding: false,
);
}
Widget circumstances() {
return new EnsureVisibleWhenFocused(
focusNode: _focusNodeCircumstances,
child: new TextFormField(
controller: _circumstancesController,
maxLines: maxLines,
decoration: const InputDecoration(
border: const OutlineInputBorder(
borderRadius: const BorderRadius.all(const Radius.circular(0.0)),
borderSide: const BorderSide(color: Colors.black, width: 1.0)
),
filled: true,
labelText: "Circumstances",
hintText: "Who was there? Where were you?",
),
autofocus: true,
validator: (value) {
_activeJournalEntry.setCircumstances(value);
if(value == null || value.isEmpty) {
return "Please enter some circumstances.";
}
},
focusNode: _focusNodeCircumstances,
),
);
}
The replies to this thread pointed me in the right direction to solve the issue.
I had three deep nested scaffolds where the innermost two had this set:
resizeToAvoidBottomPadding: false
However, my outermost Scaffold did not, causing this issue. So it turns out that it is fine to have nested Scaffolds but you must ensure that each of them have that property set to avoid this issue.
See also,
https://github.com/flutter/flutter/issues/7036
As jared-nelsen stated, one of your Scaffold does not have the property specified. Note that this can also be caused by an external library, in my case it was the DevicePreview library.