FORMS, no further idea how to implement a TextField with a CupertinoApp - forms

This drives me nuts. I am trying to get a CupertinoTextField into a Form. But no matter what I try, everything turns out as a dead end.
The complication of my case is, the form in embedded into the Flushbar package, which provides kind of a snackbar to my CupertinoApp. And this is a stateless widget ;-(
return Flushbar(
backgroundColor: Common.inputBlue,
userInputForm : Form(
key: _formKey,
child: Column(
children: <Widget> [
_DelayStatusFormField(
initialValue: task.delayStatus,
onSaved: (value) => otherFormDataSubmit.delayStatus = value,
//onSaved: (value) => this.delayStatus = value,
),
CupertinoTextField(
controller: _delayTec,
onChanged: (text) {
state.didChange(text);
},
),
]),
),
);
In the onChanged section I intend to implement some validation, so I need kind of a rebuild. But setState obviously doesn't work without a proper state.
Using TextFormField also looks kind of dodgy, because I do not only have to embed this in Material( but in loads of further localization Widgets.
The most desirable solution, is this one where I tried several variants: embed the CupertinoTextField in a FormField class
class _DelayFormField extends FormField<String> {
_DelayFormField({
FormFieldSetter<String> onSaved,
String initialValue,
bool enabled,
GlobalKey key,
}) : super(
onSaved: onSaved,
initialValue: initialValue,
key: key,
builder: (FormFieldState<String> state) {
//TextEditingController inputTec = TextEditingController(text: initialValue);
return Column(children: [
SizedBox(
width: 100, height: 30,
child: CupertinoTextField(
//controller: inputTec,
enabled: true,
onChanged: (text) {
initialValue = text;
state.didChange(text);
},
)),
]);
}
);
}
The problem here, I the TextEditingController is reinitialized on any rebuild with initialVale, loosing my input. I have got around this by assigning the input to initialVale, but the cursor is then always set leading to the prefilled digits. So you type the number backwards. Since everything is in the class' initialization, I cannot find a spot where to initialize the TextEditingController outside of the build method.
Any other idea on how to solve this? Getting such a class working would be great, since one could then simply re-use this class, frequently.

Related

Validation event for custom widget

In flutter, a TextFormField for an example, has a validator which can be used for validation:
TextFormField(validator: validator ...
Then in your form, you can call validate on the Form widget to make all children with these validators validate:
_formKey.currentState!.validate()
If a validation fails, the TextFormField will display an error text along with a position and color transition.
I have my own custom photo widget, and I would like to make it able to support the same validation functionality. That is, give it a validator, hook it up to the validate() event, and if the user hasn´t added any photo, the validation fails and shows the error text the validator returns.
But I cannot figure out how to implement the validate eventlistener on a custom widget. So how would you go around this?
Update:
#user18309290 pointed me in the direction of extending my widget from FormField. But the problem is that my widget has internal functions and properties I need to access in the instance/layout tree. But I can´t figure out the right way to do it. I could put all the stuff in the build method, but that means that all of my "heavy" logic and properties would be reinstantiated every time the widget rebuilds if I understand correctly. So how do I extend from FormField to have validation support (validation fails if image list is empty), but still have access to my methods and properties?
This is my simplified widget:
class MyPhotoComponent extends FormField<List<File>> {
late String title;
List<File> images = [];
openCamera() {
print('This and other methods, has alot of logic');
images.add(File('filepath'));
}
String internalTitle = 'Internal title';
MyPhotoComponent({required String title, required FormFieldSetter<List<File>> onSaved, FormFieldValidator<List<File>>? validator, required List<File> initialValue, Key? key})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
key: key,
builder: (FormFieldState<List<File>> state) {
return Column(
children: [
Builder(builder: (BuildContext context) {
return Column(
children: [
Text(internalTitle), //Error: The instance member 'internalTitle' can't be accessed in an initializer.
MyOtherPhotoGalleryComponent(images: images), //Error: The instance member 'images' can't be accessed in an initializer.
ElevatedButton.icon(
onPressed: openCamera, //Error: The instance member 'openCamera' can't be accessed in an initializer.
icon: Icon(Icons.add_a_photo),
label: Text('Take photo'),
),
],
);
}),
if (state.hasError) Builder(builder: (BuildContext context) => Text('Validation error'))
],
);
},
);
}
Inherit a custom widget from FormField. Each individual form field should be wrapped in a FormField widget like TextFormField.
Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
CustomFormField(
validator: (String? value) {
if (value == null || value.isEmpty) {
return 'Please select something';
}
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {}
},
child: const Text('Submit'),
),
],
),
);
There are three ways to accomplish this
State management which will be complicated for this scenario
SetState() which will update your whole UI and will be expensive
ValueNotifier and ValueListableBuilder which I recommend
First define a valuenotifier
late ValueNotifier<bool> _isValid;
Then in initState initialize it and add a listener to it which will be your validator
...
_isValid = ValueNotifier(true);
_isValid.addListener((){
If(){ //your validation
_isValid.value = true;
} else {
_isValid.value = false;
}
})
Then in your UI add ValueListableBuilder and put your widget inside it to listen to its changes and change accordingly
ValueListableBuilder(
listenableValue: _isValid,
builder: (context, bool yourValue, child){
// return your widget and use yourValue to update your UI
}
)
Sorry if there is any misspelling. I wrote it on my mobile

Flutter Call Listener from another class

I am pretty new to Flutter (coming from Java) and making my way through the first Android application.
In my project, I am using a different class to create a reusable widget (from the example here), that works perfectly with all methods, but I can't figure out a way to define and reuse the method onEditingComplete.
Here is my code:
class AppTextField extends StatelessWidget {
//
AppTextField({
this.controller,
this.textInputType,
this.pwValidator,
this.editingComplete, // this the method that is causing the problem
});
final TextEditingController controller;
final TextInputType textInputType;
final FormFieldValidator pwValidator;
final Listener editingComplete; // This doesn't work. Am I using the wrong listener?
#override
Widget build(BuildContext context) {
return Container(
child: Theme(
data: ThemeData(
primaryColorDark: Colors.blue,
),
child: Padding(
padding: EdgeInsets.fromLTRB(25, 15, 25, 0),
child: TextFormField(
controller: controller,
keyboardType: null == textInputType ? textInputType : textInputType,
validator: null == pwValidator ? pwValidator : pwValidator,
// I am facing problems with this line of code
onEditingComplete: null == editingComplete ? editingComplete : editingComplete,
),
),
),
);
}
}
This is the class where I want to implement and reuse the widget :
Container(
child: AppTextField(
controller: _controllerPassword,
pwValidator: (value) { },
onEditingComplete: // here is where I am facing difficulties
),
The property onEditingComplete is a VoidCallback. Which is a function without parameter and does not return any data.
In AppTextField define onEditingComplete as
final VoidCallback onEditingComplete;
Then assign it to the onEditingComplete property of the TextFormField. Also, get rid of the ternary operators.
onEditingComplete: onEditingComplete
And when using the widget, pass the callback like so:
Container(
child: AppTextField(
controller: _controllerPassword,
pwValidator: (value) { },
onEditingComplete: (){
//Do what you want to do here.
}
),

FLUSHBAR: anyone familiar with this package, how to create a form within?

maybe I am overstressing this package, but the last example shows a form. However, my implementation shows no sign of life. Not even an error or warning.
My approach is a little different. I want to create a form with multiple, different input widgets (text, button, selector) and then return those with a bloc event. The returning/saving part is not yet implemented.
If someone has implemented a form with flushbar, I would appreciate any advice.
This is my flushbar code
Widget otherActionsSlider() {
bool paused;
int priority;
bool supply;
return Flushbar(
userInputForm : Form(
key: _formKey,
child: Container(
child: Column(
children: <Widget> [
Text("progress"),
_DelayStatusFormField(
onSaved: (value) => this.delayStatus = value,
),
Row(
children: [
_DelayFormField(
onSaved: (value) => this.delay = value,
enabled: this.delayStatus == DelayStatus.unrecoverable ? true : false
)
],
),
RaisedButton(
child: Text('Submit'),
onPressed: () {
if (this._formKey.currentState.validate()) {
setState(() {
this._formKey.currentState.save();
});
}
},
),
],),)),
);
}
and the form field classes
class _DelayStatusFormField extends FormField<DelayStatus> {
_DelayStatusFormField({
FormFieldSetter<DelayStatus> onSaved,
FormFieldValidator<DelayStatus> validator,
DelayStatus initialValue,
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<DelayStatus> state) {
return
CupertinoSlidingSegmentedControl(
groupValue: initialValue == DelayStatus.recoverable ? 1 :
initialValue == DelayStatus.unrecoverable ? 2 : 0,
children: {
0: Text('on time'),
1: Text('recoverable'),
2: Text('unrecoverable')
},
onValueChanged: (int val) => state.didChange(val == 0 ? DelayStatus.onTime :
val == 1 ? DelayStatus.recoverable :
DelayStatus.unrecoverable)
);
}
);
}
class _DelayFormField extends FormField<int> {
_DelayFormField({
FormFieldSetter<int> onSaved,
FormFieldValidator<int> validator,
int initialValue,
bool enabled
}) : super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
builder: (FormFieldState<int> state) {
TextEditingController inputTec = TextEditingController(text: initialValue.toString());
return
CupertinoTextField(
controller: inputTec,
placeholder: 'delay in days',
enabled: enabled
);
}
);
}
While I haven't yet fully implemented my solution, I finally received some signs of life. My issue was, the function returning the Flushbar has to be of type Flushbar<List<String>> given the example of the package readme. I returned Widget instead.
If someone tries to work with this example, there quite a few brackets and more importantly, it is not (as written in the readme) Flushbar(userInputForm = Form( but Flushbar(userInputForm: Form(
Though the quality of the example given leaves room for improvement, the widget itself is quite nice, especially when implementing more simple use cases (I have a CupertinoApp, so I cannot use snackbar)

Why is my flutter TextField initialState not working

Here is the code on how i call my custom Widget called BodyInput
BodyInput(
label: 'Name',
readOnly: readOnly,
initialValue: name,
placeholder: name,
handleChange: (value) {
print(value);
},
),
both placeholder and initialState has the same value but for some reason instead of displaying the initialState my TextField is displaying the placeholder. results
and here is the BodyInput itself *for some reason it wont let me copy paste the code so here is an image instead i'm really sorry for the trouble, for some reason when i paste the code only first line is copied as code the rest is normal text
Well turnsout my widget.initialState on initState value was null :/, so i had to set the _controller value in the Widget and my code become like this
Widget build(BuildContext context) {
//Had to move this here instead of in the initState method.
_controller = new TextEditingController(text: widget.initialValue);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.label),
TextField(
onChanged: (value) {
widget.handleChange(value);
},
controller: _controller,
readOnly: widget.readOnly,
decoration: InputDecoration(
hintText: widget.placeholder, contentPadding: EdgeInsets.all(2)),
),
],
);
}
please let me know if there is a better answer

How to call a method once when text editing is completed fully in flutter TextFormField?

In Flutter TextFormField, I want to call a method once editing is completed fully,I don't want to call the method while the user is changing the text in text form field(By using listener or by onchanged function), By using OnEditingComplete Function I can achieve this requirement, but the issue is oneditingcomplete is called only when the done button in the keyboard is tapped, when changing the focus after editing from one text field to another textfield onEditingComplete function is not working, So what is the best way to detect once editing is complete fully by the user.
`TextField(
onEditingComplete: () {//this is called only when done or ok is button is tapped in keyboard
print('test');
},
Using the FocusScopeWidget helps me resolve this issue https://api.flutter.dev/flutter/widgets/FocusNode-class.html,
When user press done button in the keyboard onsubmitted function is called and when user change focus focus scope widget is used.
FocusScope(
onFocusChange: (value) {
if (!value) {
//here checkAndUpdate();
}
},
child: TextFormField(
key: Key('productSet${DateTime.now().toIso8601String()}'),
getImmediateSuggestions: true,
textFieldConfiguration: TextFieldConfiguration(
onSubmitted: (cal) {
//here checkAndUpdate();
},
Use TextFormField and some sort of trigger like button pressed.
Use Global key to keep state of form, when save the form
...
class _Form extends State<Initialize> {
final _formKey = GlobalKey<FormState>();//create global key with FormState type
String _variableValue; //to hold data after save the form
#override
Widget build(BuildContext context) {
return form(
key: _formkey,
child: Column(
children: [
TextFormField(
onSaved: (value) {
_variableValue= value;
}
),
OutlineButton.icon(
onPressed: () {
_formKey.currentState.save();//should call this method to save value on _variableValue
},
icon: Icon(Icons.plus)
),
],
),
),
}
}
good luck
You can use TextFormField instead of TextField:
TextFormField(
decoration: InputDecoration(labelText: 'First Name'),
onSaved: (val) => setState(() => _firstName = val),
)
Realistic Forms in Flutter