flutter update value in another file widget - flutter

I'm new in flutter and I try to update a variable in my main.dart from a customWidget in another file.
I search on internet but I'm a little lost.
How to update my variable userDistance with the value that the user enter ?
Is my widget structure correct? or does it have to be stateless or stateful?
Main.dart :
class HomeController extends StatefulWidget {
HomeController({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeControllerState createState() => _HomeControllerState();
}
class _HomeControllerState extends State<HomeController> {
// ************************************
// Variable
// ************************************
int userDistance = null;
final _formKey = GlobalKey<FormState>();
// ************************************
// Initialisation
// ************************************
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: loadJson(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return SingleChildScrollView(
padding: EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FormValidatorWidgets.buildDistance(), // => Here I call my external widget
...
And my customWidget :
import 'package:flutter/material.dart';
class FormValidatorWidgets{
static buildDistance() {
return TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: "Distance",
hintText: "Enter a distance",
),
keyboardType: TextInputType.number,
validator: (String value) {
int distance = int.tryParse(value);
if (distance == null) {
return "Distance is required";
}
if (distance <= 0) {
return "Distance must be greater than zero";
}
},
onSaved: (String value) {
userDistance = int.tryParse(value);
},
);
}
}
How to update my variable userDistance ?

For your FormValidatorWidgets add callback function
class FormValidatorWidgets{
static buildDistance(FormFieldSetter<String> onSaved) { // onSaved is a callback
return TextFormField(
...
onSaved: onSaved,
);
}
}
And then in your form:
Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[
FormValidatorWidgets.buildDistance(
(String value) {
userDistance = int.tryParse(value);
},
),
...

Related

Access data from custom widget created on different class in flutter

I new to Flutter and i was trying to find a solution for the below issue for several hours. I have searched and every solution provided does not work form me.
I have page where one of the widgets is the autocomplete text input. I have created this autocomplete widget on different class. I have added this widget as StatefulBuilder within my main widget. it is working fine however, i am not able to access its value so I can store it with other fields.
My code look like
class ItemDetails extends StatefulWidget {
const ItemDetails({Key? key}) : super(key: key);
static const routeName = '/item_details';
#override
State<ItemDetails> createState() => _ItemDetails();
}
class _ItemDetails extends State<ItemDetails> {
late TextEditingController labelController;
late TextEditingController valueController;
late TextEditingController notesController;
bool _submitted = false;
late var args;
String _itemLabel2 = "";
// var labelAutoComp = LabelSugg();
#override
void initState() {
super.initState();
labelController = TextEditingController();
valueController = TextEditingController();
notesController = TextEditingController();
}
#override
void dispose() {
labelController.dispose();
valueController.dispose();
notesController.dispose();
// Hive.close();
super.dispose();
}
String? _labelErrorText(context) {
final text = labelController.value.text;
if (text.isEmpty) {
// return 'Can\'t be empty';
return AppLocalizations.of(context)!.noEmpty;
}
}
String? _valueErrorText(context) {
final text = valueController.value.text;
if (text.isEmpty) {
// return 'Can\'t be empty';
return AppLocalizations.of(context)!.noEmpty;
}
}
#override
Widget build(BuildContext context) {
try {
args = ModalRoute.of(context)!.settings.arguments as Map;
} on Exception catch (e) {
// print(e);
}
// print(args);
return Scaffold(
appBar: AppBar(
title: Text(args['title']),
),
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(20),
child: Column(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LabelSugg(getLabelText: (String val) {
print(val);
_itemLabel2 = val;
}),
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.label,
hintText: AppLocalizations.of(context)!.labelHint,
errorText:
_submitted ? _labelErrorText(context) : null,
),
controller: labelController,
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 5),
TextField(
autofocus: false,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.value,
hintText: AppLocalizations.of(context)!.valueHint,
errorText:
_submitted ? _valueErrorText(context) : null,
),
controller: valueController,
keyboardType: const TextInputType.numberWithOptions(
decimal: true, signed: false),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r"[0-9.]")),
TextInputFormatter.withFunction(
(oldValue, newValue) {
try {
final text = newValue.text;
if (text.isNotEmpty) double.parse(text);
return newValue;
} catch (e) {}
return oldValue;
}),
], // Only numbers can be entered
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 5),
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.notes,
hintText: AppLocalizations.of(context)!.noteHint,
),
controller: notesController,
onChanged: (_) => setState(() {}),
),
]),
// ],
),
Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => _submitted = true);
if (_labelErrorText(context) == null &&
_valueErrorText(context) == null) {
//insert
var localLabel = labelController.value.text;
var _localValue = 0.0;
if (valueController.value.text != '') {
_localValue =
double.parse(valueController.value.text);
} else {
_localValue = 0.0;
}
var localNotes = notesController.value.text;
addItemToList(
localLabel, _localValue, localNotes);
Navigator.of(context).pop();
labelController.clear();
valueController.clear();
notesController.clear();
}
},
label: Text(AppLocalizations.of(context)!.add),
icon: const Icon(Icons.save, size: 18),
),
const SizedBox(width: 10),
ElevatedButton.icon(
onPressed: () => {Navigator.pop(context)},
label: Text(AppLocalizations.of(context)!.cancel),
icon: const Icon(Icons.cancel, size: 18),
),
],
)),
),
// )
],
)));
}
void addItemToList(String localLabel, double localValue, String localNotes) {
var _item = YearItems()..yearID = args['year'];
_item.itemLabel = localLabel;
_item.itemValue = localValue;
_item.itemNote = localNotes;
print(_itemLabel2);
final itemsBox = ItemsBoxes.getTransactions();
itemsBox.add(_item);
}
}
my labelAutoComp widget code look like
class LabelSugg extends StatefulWidget {
final ValueChanged<String> getLabelText;
const LabelSugg({Key? key, required this.getLabelText}) : super(key: key);
#override
State<LabelSugg> createState() => _LabelSugg();
}
class _LabelSugg extends State<LabelSugg> {
late TextEditingController fieldTextEditingController2;
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
getLabel() {
return widget.getLabelText(fieldTextEditingController2.text);
}
#override
Widget build(BuildContext context) {
List<LabelsAc> labelOptions = <LabelsAc>[
LabelsAc(label: AppLocalizations.of(context)!.labelClothes),
LabelsAc(label: AppLocalizations.of(context)!.labelFood),
LabelsAc(label: AppLocalizations.of(context)!.labelPerfumes),
LabelsAc(label: AppLocalizations.of(context)!.labelCapital),
];
return Autocomplete<LabelsAc>(
optionsBuilder: (TextEditingValue textEditingValue) {
return labelOptions
.where((LabelsAc _label) => _label.label
.toLowerCase()
.startsWith(textEditingValue.text.toLowerCase()))
.toList();
},
displayStringForOption: (LabelsAc option) => option.label,
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
// fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) {
return TextField(
controller: fieldTextEditingController,
focusNode: fieldFocusNode,
style: const TextStyle(fontWeight: FontWeight.bold),
// onChanged: getLabel(),
onChanged: (String val) {
fieldTextEditingController2 = fieldTextEditingController;
getLabel();
});
},
onSelected: (LabelsAc selection) {
fieldTextEditingController2 =
TextEditingController(text: selection.label);
getLabel();
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<LabelsAc> onSelected,
Iterable<LabelsAc> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
child: Container(
// width: 350,
// color: Colors.cyan,
child: ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final LabelsAc option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option.label,
style: const TextStyle(color: Colors.black)),
),
);
},
),
),
),
);
},
);
// ),
// );
}
}
class LabelsAc {
LabelsAc({required this.label});
String label;
}
first is redundant when you wrap your class that extend StatefullWidget with StatefullBuilder. LabelSugg is a component Widget. you can use it like other widget.
benefit to separate widget with StatefullWidget class is, we can update the value inside the class without re-build the current page. which is good for performance. that's why developer recomend to separete with class insted compared to make local method.
as you see, when you create LabelSugg extend StatefullWidget class , we will have _LabelSugg . underscore means that: all variable only accessible on current file.
thats why we can't call getLabel() or other variable from different file.
its used for handle the State in 'LabelSugg` widget.
now how to pass the value from LabelSugg is by created variable outside the state. here you are:
class LabelSugg extends StatefulWidget {
// use this to pass any changes when we use LabelSugg
final ValueChanged<String> getLabelText;
const LabelSugg({Key? key, required this.getLabelText}) : super(key: key);
#override
State<LabelSugg> createState() => _LabelSugg();
}
then we can call the onChaged inside _LabelSugg state. because its Statefull widget, we can acces by : widget.getLabelText()
class _LabelSugg extends State<LabelSugg> {
late TextEditingController fieldTextEditingController;
.....
getLabel() {
return widget.getLabelText(fieldTextEditingController.text);
}
then in other class we call LabelSugg like common widget
import 'package:../labelsug.dart';
class ItemDetails extends StatefulWidget {
.....
return Scaffold(
appBar: AppBar(
title: Text(args['title']),
),
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
// now use it like a widget
LabelSug(
getLabelText: (String val){
print(val);
}
:)

Flutter - form validate and focus problem

I am trying to do website form validation and focus on the first error field for users automatically. I found a helpful solution to get to the first error field from [here][1].
However, the focus manager solution does not work properly as I expected when there are dynamic fields or conditional fields wrapped with if...[]. When the form validates, the focus goes to the last field and skipped both dynamic and conditional fields.
I solve the conditional field problem by declaring each field in sequence before the return statement, but the dynamic fields still encounter the same problem on validation. Can anyone tell me what is happening within Widget build and is there any possible solutions to this problem? The following is the code I tested.
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: const Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
final _formKey = GlobalKey<FormState>();
final _formValidationManager = FormValidationManager();
int count = 3;
bool isChecked = false;
#override
Widget build(BuildContext context) {
Widget widget1 = Column(
children: [
Text('field 1'),
TextFormField(
autovalidateMode: AutovalidateMode.onUserInteraction,
focusNode: _formValidationManager.getFocusNodeForField('field1'),
validator: _formValidationManager.wrapValidator('field1', (value) {
if (value!.isEmpty) {
return 'Please enter a value';
}
return null;
}))
],
);
Widget widget2 = Column(
children: [
Text('field 2'),
TextFormField(
focusNode: _formValidationManager.getFocusNodeForField('field2'),
validator: _formValidationManager.wrapValidator('field2', (value) {
if (value!.isEmpty) {
return 'Please enter a value';
}
return null;
}))
],
);
Widget dynamicField = ListView.separated(
itemCount: count,
separatorBuilder: (BuildContext context, int index) => Divider(height: 1),
itemBuilder: (BuildContext context, int index) {
return DynamicTextField(
formValidationManager: _formValidationManager, index: index);
},
shrinkWrap: true,
physics: const ScrollPhysics(),
);
Widget widget3 = Row(
children: [
Checkbox(
value: isChecked,
onChanged: (value) {
isChecked = !isChecked;
setState(() {});
}),
SizedBox(
width: 16,
),
Text("Open conditional Field"),
],
);
Widget conditionalWidget = Column(
children: [
Text('conditional field'),
TextFormField(
focusNode: _formValidationManager.getFocusNodeForField('field4'),
validator: _formValidationManager.wrapValidator('field4', (value) {
if (value!.isEmpty) {
return 'Please enter a value';
}
return null;
}))
],
);
Widget widget4 = Column(
children: [
Text("field 3"),
TextFormField(
focusNode: _formValidationManager.getFocusNodeForField('field5'),
validator: _formValidationManager.wrapValidator('field5', (value) {
if (value!.isEmpty) {
return 'Please enter a value';
}
return null;
}))
],
);
return Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: [
widget1,
widget2,
widget3,
if (isChecked) ...[conditionalWidget],
dynamicField,
widget4,
ElevatedButton(
onPressed: () {
if (!_formKey.currentState!.validate()) {
_formValidationManager.erroredFields.first.focusNode
.requestFocus();
}
},
child: Text('SUBMIT'))
],
),
),
);
}
#override
void dispose() {
_formValidationManager.dispose();
super.dispose();
}
}
class FormValidationManager {
final _fieldStates = Map<String, FormFieldValidationState>();
FocusNode getFocusNodeForField(key) {
_ensureExists(key);
return _fieldStates[key]!.focusNode;
}
FormFieldValidator<T> wrapValidator<T>(
String key, FormFieldValidator<T> validator) {
_ensureExists(key);
return (input) {
final result = validator(input);
_fieldStates[key]!.hasError = (result?.isNotEmpty ?? false);
return result;
};
}
List<FormFieldValidationState> get erroredFields => _fieldStates.entries
.where((s) => s.value.hasError)
.map((s) => s.value)
.toList();
void _ensureExists(String key) {
_fieldStates[key] ??= FormFieldValidationState(key: key);
}
void dispose() {
_fieldStates.entries.forEach((s) {
s.value.focusNode.dispose();
});
}
}
class FormFieldValidationState {
final String key;
bool hasError;
FocusNode focusNode;
FormFieldValidationState({required this.key})
: hasError = false,
focusNode = FocusNode();
}
class DynamicTextField extends StatelessWidget {
final FormValidationManager formValidationManager;
final int index;
const DynamicTextField(
{Key? key, required this.formValidationManager, required this.index})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextFormField(
focusNode: formValidationManager.getFocusNodeForField('node$index'),
validator: formValidationManager.wrapValidator('node$index', (value) {
if (value!.isEmpty) {
return 'Please enter a value';
}
return null;
}));
}
}```
Thank you very much!
[1]: https://stackoverflow.com/questions/63833619/flutter-forms-get-the-list-of-fields-in-error

"onSaved()" function in TextFormField not being reached (dart/flutter)

I have built a very simple form in flutter and I am trying to save the value of whatever is typed in the form fields to variables. This way, I can push these variables to firebase. However, nothing in the onSaved() block of the TextFormFields is being run. I have called save() on the current state of the form, but it still doesn't seem to work. Any ideas?
I have attached the code for the page below:
import 'package:flutter/material.dart';
class AddJobPage extends StatefulWidget {
const AddJobPage({Key? key}) : super(key: key);
static Future<void> show(BuildContext context) async {
await Navigator.of(context).push(MaterialPageRoute(
builder: (context) => const AddJobPage(),
fullscreenDialog: true
));
}
#override
_AddJobPageState createState() => _AddJobPageState();
}
class _AddJobPageState extends State<AddJobPage> {
final _formKey = GlobalKey<FormState>();
//These two variables are where we will store the values of the text form fields
//before we push to firestore.
String? _name = '';
int _ratePerHour = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 2,
title: const Text("New Job"),
backgroundColor: Colors.teal.shade700,
actions: [
TextButton(
onPressed: _submit,
child: const Text(
'Save',
style: TextStyle(fontSize: 18, color: Colors.white),
)
)
],
),
body: _buildContents(),
backgroundColor: Colors.grey.shade200,
);
}
void _submit() {
if(_validateAndSave()) {
print("form saved, name: $_name, ratePerHour: $_ratePerHour");
}
}
bool _validateAndSave() {
final form = _formKey.currentState;
if(form!.validate()) {
print("the form was saved here");
form.save;
return true;
}
return false;
}
Widget _buildContents() {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: _buildForm(),
),
),
),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _buildFormChildren(),
)
);
}
List<Widget> _buildFormChildren() {
return [
TextFormField(
decoration: const InputDecoration(labelText: 'Job name'),
onSaved: (value) {
print("code doesn't reach here");
_name = value; //save the value of the text field to _name
}
),
TextFormField(
decoration: const InputDecoration(labelText: 'Rate Per Hour'),
keyboardType: const TextInputType.numberWithOptions(
signed: false,
decimal: false
),
onSaved: (value) {
_ratePerHour = int.tryParse(value!) ?? 0;
}
),
];
}
}
You need to call the save function.
So replace form.save; in your code with form.save();
replace form.save with form.save() and you're done. No need to worry about text controllers since you're using a text form field
you need to add a TextEditingController to get the text from a TextFormField. Then you need to call setstate inside OnChanged, not on Onsaved so you can transfer the text to a variable. This is what your code should look like:
First you initialize a controller like this
final TextEditingController textController = TextEditingController();
String _name = "not set";
then you add the controller to your textformfield like this
TextFormField(
controller: textController ,
onChanged:(value)
{
setState(() {
_name = textController.text;
});
} ,
)
This is a complete example:
class TextFormFieldExample extends StatefulWidget {
const TextFormFieldExample({Key? key}) : super(key: key);
#override
_TextFormFieldExampleState createState() => _TextFormFieldExampleState();
}
class _TextFormFieldExampleState extends State<TextFormFieldExample> {
//Create the controller here
final TextEditingController textController = TextEditingController();
String _name = "not set";
#override
Widget build(BuildContext context) {
print(_name);
return Scaffold(
body: Material(
child: Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child:TextFormField(
controller: textController ,//Attach the controller to the text form here
onChanged:(value)
{
setState(() {
_name = textController.text;//Save the text from the controller to a variable
});
} ,
),
),
),
);
}
}

Flutter form with provider

I'm new with Flutter and provider.
I'm trying to make a form with provider in order to separate my logic in my code but I'm struggling ...
My form in the screen :
class CalculatorScreen extends StatefulWidget{
CalculatorScreen({Key key}) : super(key: key);
#override
_CalculatorScreenState createState() => _CalculatorScreenState();
}
class _CalculatorScreenState extends State<CalculatorScreen> {
final TextEditingController _controllerDistance = TextEditingController();
#override
void dispose(){
_controllerDistance.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
body : _buildBody(context)
),
);
}
Widget _buildBody(BuildContext context)
{
var _formCalculatorProvider = Provider.of<FormCalculatorNotifier>(context);
return SingleChildScrollView(
child: Column(
children: [
ContainerComponent(
background: AppColors.colorBgLight,
children: [
Form(
key : _formCalculatorProvider.globalFormKey,
autovalidate: _formCalculatorProvider.autovalidate,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Distance",
),
controller: _controllerDistance,
keyboardType : TextInputType.number,
validator: (String value){
return FormValidatorService.isDistanceValid(value);
},
onSaved: (var value) {
_formCalculatorProvider.saveDistance(value);
}
),
],
),
),
ButtonComponent.primary(
context: context,
text: "Send",
onPressed: _formCalculatorProvider.submit,
),
],
)
],
),
);
}
}
And my notifier :
enum FormCalculatorState{
READY,
SUCCESS,
ERROR
}
class FormCalculatorNotifier with ChangeNotifier {
final GlobalKey<FormState> globalFormKey = GlobalKey<FormState>();
FormCalculatorState formState = FormCalculatorState.READY;
bool autovalidate = false;
FormCalculatorModel formData = FormCalculatorModel();
void saveDistance(String value){
print("save");
formData.distance = num.tryParse(value).round();
notifyListeners();
}
void submit(){
if (!globalFormKey.currentState.validate()) {
print("submit");
print(formData);
autovalidate = true;
formState = FormCalculatorState.ERROR;
return;
}
else{
globalFormKey.currentState.save();
}
notifyListeners();
}
Future showErrorNotification(){
// Here I need to know the context
return InfoBarComponent.error(title: AppTextInfobar.ERROR_TITLE, description: AppTextInfobar.ERROR_DESCRIPTION, context: context);
}
How to use my showErrorNotification because I need the context to show my notificationBar ? When I try to add context in the scrren on the submit function I have an error.
Is this the right method?
Did not go through your entire code. But I immediately noticed that notifyListeners is missing in FormCalculatorNotifier class.

Flutter make a form with provider and riverpod

I'm new with Flutter and I want to upgrade my code. I have a form that uses multiple textformfields and I want to convert this code using provider and riverpod to improve readability but I'm not sure how to do it.
For the example I simplified my code to only one distance field but there are many others.
This is my CalculatorScreen :
import 'dart:async' show Future;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:app/core/models/model_form_calculator.dart';
import 'package:app/core/services/service_form_validator.dart';
import 'package:app/core/utils/utils_app_color.dart';
class CalculatorScreen extends StatefulWidget
{
CalculatorScreen({Key key}) : super(key: key);
#override
_CalculatorScreenState createState() => _CalculatorScreenState();
}
class _CalculatorScreenState extends State<CalculatorScreen>
{
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final _formKey = GlobalKey<FormState>();
FormCalculatorModel _formData = FormCalculatorModel();
bool _autoValidateForm = false;
final TextEditingController _controllerDistance = TextEditingController();
#override
void initState() {
super.initState();
}
#override
void dispose()
{
_controllerDistance.dispose();
super.dispose();
}
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
key: _scaffoldKey,
backgroundColor: AppColors.colorBgDark,
body : _buildBody()
),
);
}
Widget _buildBody()
{
return SingleChildScrollView(
child: Column(
children: [
Form(
key: _formKey,
autovalidate: _autoValidateForm,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: _controllerDistance,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "Enter a value",
),
validator: (value){
return FormValidatorService.isDistanceValid(value);
},
onSaved: (var value) {
_formData.distance = num.tryParse(value).round();
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: FlatButton(
child: Text("Erase"),
onPressed: _buttonResetAction
),
),
Expanded(
child: FlatButton(
child: Text("Send"),
onPressed: _buttonSubmitAction
),
),
],
),
]
),
),
],
),
);
}
void _buttonResetAction()
{
_eraseForm();
}
void _eraseForm(){
setState(() {
_formKey.currentState.reset();
_formData = FormCalculatorModel();
_autoValidateForm = false;
_controllerDistance.clear();
});
}
void _buttonSubmitAction() async
{
if (!_formKey.currentState.validate()) {
setState(() {
_autoValidateForm = true;
});
return;
}
_formKey.currentState.save();
try{
// some actions
}catch(e){
_eraseForm();
print(e.toString());
}
}
}
This is my formModel (This model contains all the fields that I can fill in my form and allows me to store the values ​​of the form once validated to then make calculations with these values
):
class FormCalculatorModel{
int distance;
FormCalculatorModel({
this.distance,
});
#override
String toString() {
return '{ '
'${this.distance}, '
'}';
}
}
And my FormValidatorService :
class FormValidatorService{
static String isDistanceValid(String value)
{
num _distance = num.tryParse(value);
if (_distance == null) {
return "is required";
}
if (_distance < 200) {
return "Min distance is 200";
}
if (_distance > 1000) {
return "Max dist is 1000";
}
return null;
}
}
Now I want to convert this with riverpod. I'm a little lost, there are few examples on the internet and I don't really see how to manage my form
At first I'm just trying to handle the validation of the form but it doesn't work.
My calculatorScreen :
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class CalculatorScreen extends HookWidget{
final _formKey = GlobalKey<FormState>();
bool _autoValidateForm = false;
FormCalculatorModel _formData = FormCalculatorModel();
final TextEditingController _controllerDistance = TextEditingController();
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
body : _buildBody(context)
),
);
}
Widget _buildBody(BuildContext context){
final _formModel = useProvider(formCalculatorProvider.state);
return SingleChildScrollView(
child: Column(
children: [
TitleComponent(
title: "Calcul",
description: "Parametrer",
),
ContainerComponent(
background: AppColors.colorBgLight,
children: [
Form(
key : _formKey,
autovalidate: _autoValidateForm,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Distance",
//errorText: _formModel.distance.error,
),
controller: _controllerDistance,
validator: (String value){
return FormValidatorService.isDistanceValid(value);
},
onSaved: (var value) {_formData.distance = num.tryParse(value).round();}
),
],
),
),
ButtonComponent.primary(
text: "Calculer",
context: context,
onPressed : context.read(formCalculatorProvider).submitData(key: _formKey),
),
],
)
],
),
);
}
}
And my FormCalculatorNotifier :
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
enum FormState
{
EMPTY,
SUCCESS,
ERROR
}
class FormCalculatorModelNew {
const FormCalculatorModelNew({this.formState, this.autoValidate, this.distance});
final FormState formState;
final bool autoValidate;
final String distance;
}
class FormCalculatorNotifier extends StateNotifier<FormCalculatorModelNew>
{
FormCalculatorNotifier() : super(_initial);
static const FormState _initialState = FormState.EMPTY;
static const _initial = FormCalculatorModelNew(
formState : _initialState,
autoValidate: false,
distance: null
);
submitData({key}){
print(key);
if (!key.currentState.validate()) {
state = FormCalculatorModelNew(
autoValidate: true,
);
return;
}
key.currentState.save();
}
}
The provider :
final formCalculatorProvider = StateNotifierProvider((ref) => FormCalculatorNotifier());
It does not really make sense to use Provider in your example code because I don't see anywhere listen to the state of formCalculatorProvider. Also, the form itself should be managed in the form widget itself.
I assume you want to share the distance value with other widgets. Here are what I will do:
_autoValidate: leave it inside the widget and handle it by Hook
add copyWith inside FormCalculatorModelNew (can easily update partial value)
formCalculatorProvider part:
final formCalculatorProvider = StateNotifierProvider((ref) => FormCalculatorNotifier());
enum MyFormState { EMPTY, SUCCESS, ERROR }
class FormCalculatorModelNew {
const FormCalculatorModelNew({this.formState, this.distance});
final MyFormState formState;
final int distance;
FormCalculatorModelNew copyWith({
MyFormState formState,
int distance,
}) {
return FormCalculatorModelNew(
formState: formState ?? this.formState,
distance: distance ?? this.distance,
);
}
}
class FormCalculatorNotifier extends StateNotifier<FormCalculatorModelNew> {
FormCalculatorNotifier() : super(_initial);
static const MyFormState _initialState = MyFormState.EMPTY;
static const _initial =
FormCalculatorModelNew(formState: _initialState, distance: null);
void update(int distance) {
state = state.copyWith(distance: distance, formState: MyFormState.SUCCESS);
}
void error() {
state = state.copyWith(distance: null, formState: MyFormState.ERROR);
}
void clear() {
state = state.copyWith(distance: null, formState: MyFormState.EMPTY);
}
}
CalculatorScreen part: (simplify)
class CalculatorScreen extends HookWidget {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
final _autoValidate = useState<bool>(false);
final _controller = useTextEditingController();
return Scaffold(
body: Form(
key: _formKey,
autovalidate: _autoValidate.value,
child: Column(
children: [
TextFormField(
controller: _controller,
keyboardType: TextInputType.number,
validator: (value) {
return FormValidatorService.isDistanceValid(value);
},
onSaved: (value) {
context.read(formCalculatorProvider).update(num.tryParse(value).round());
},
),
Row(
children: [
FlatButton(
child: Text('Erase'),
onPressed: () {
_formKey.currentState.reset();
_controller.clear();
_autoValidate.value = false;
context.read(formCalculatorProvider).clear();
},
),
FlatButton(
child: Text('Send'),
onPressed: () {
if(_formKey.currentState.validate()){
_formKey.currentState.save();
}else{
_autoValidate.value = true;
context.read(formCalculatorProvider).error();
}
},
),
],
),
],
),
),
);
}
}
You can use TextEditingController.
Further create a provider like so, and you may now listen to text changes and store them where desired using the same provider
final formControllerProvider =
StateProvider<TextEditingController>((ref) => TextEditingController());