Je suis assez nouveau dans Flutter, et j'ai un peu de mal à créer un champ de formulaire personnalisé. Le problème est que ni le validateur ni la méthode onSaved de mon FormField personnalisé ne sont appelés. Je ne sais vraiment pas pourquoi ils sont ignorés lorsque je déclenche un formKey.currentState.validate () ou formKey.currentState.save () .
C'est un joli widget simple pour l'instant, avec un texte d'entrée et un bouton. Le bouton récupère l'emplacement actuel de l'utilisateur et met à jour le champ de texte avec l'adresse actuelle. Lorsque l'utilisateur saisit une adresse dans le champ de texte, il récupérera l'emplacement de cette adresse sur le focus perdu (j'ai également l'intégration avec Google Maps, mais je l'ai simplifiée pour isoler le problème).
Voici le constructeur de mon champ de formulaire:
void _submitForm() {
print('formdata : $_formData');
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
print('formdata : $_formData');
}
Comme j'ai besoin de gérer l'état dans mon FormField personnalisé, je le construis dans l'objet FormFieldState. L'état de l'emplacement est mis à jour lorsque vous appuyez sur le bouton:
Widget _buildLocationField() {
return LocationFormField(
initialValue: null,
validator: (LocationData value) {
print('validator location');
if (value.address == null || value.address.isEmpty) {
return 'No valid location found';
}
},
onSaved: (LocationData value) {
print('location saved: $value');
_formData['location'] = value;
},
); // LocationFormField
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Container(
margin: EdgeInsets.all(10.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: targetPadding / 2),
child: Column(
children: <Widget>[
_buildTitleTextField(),
SizedBox(
height: 10.0,
),
_buildLocationField(),
SizedBox(
height: 10.0,
),
_buildSubmitButton(),
],
),
),
),
),
),
);
}
Voici comment je l'instancie dans mon application. Rien de spécial ici; Je l'ai mis dans un formulaire avec une clé de formulaire. Il existe un autre TextFormField pour vérifier que celui-ci fonctionne correctement:
main.dart
class _LocationFormFieldState extends FormFieldState<LocationData> {
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
TextField(
focusNode: _addressInputFocusNode,
controller: _addressInputController,
decoration: InputDecoration(labelText: 'Address'),
),
SizedBox(height: 10.0),
FlatButton(
color: Colors.deepPurpleAccent,
textColor: Colors.white,
child: Text('Locate me !'),
onPressed: _updateLocation,
),
],
);
}
void _updateLocation() async {
print('current value: ${this.value}');
final double latitude = 45.632;
final double longitude = 17.457;
final String formattedAddress = await _getAddress(latitude, longitude);
print(formattedAddress);
if (formattedAddress != null) {
final LocationData locationData = LocationData(
address: formattedAddress,
latitude: latitude,
longitude: longitude);
_addressInputController.text = locationData.address;
// save data in form
this.didChange(locationData);
print('New location: ' + locationData.toString());
print('current value: ${this.value}');
}
}
La méthode d'envoi déclenchée par le bouton d'envoi du formulaire essaiera simplement de valider puis d'enregistrer le formulaire.
Il suffit d'imprimer les données enregistrées sous le formulaire:
class LocationFormField extends FormField<LocationData> {
LocationFormField(
{FormFieldSetter<LocationData> onSaved,
FormFieldValidator<LocationData> validator,
LocationData initialValue,
bool autovalidate = false})
: super(
onSaved: onSaved,
validator: validator,
initialValue: initialValue,
autovalidate: autovalidate,
builder: (FormFieldState<LocationData> state) {
return state.build(state.context);
});
@override
FormFieldState<LocationData> createState() {
return _LocationFormFieldState();
}
}
Mais _formData ['location'] renvoie toujours null, et le validateur n'est jamais appelé (aucun 'emplacement du validateur' ou 'emplacement enregistré' n'est imprimé dans les journaux).
J'ai créé un exemple de dépôt pour reproduire ceci problème. Vous pouvez essayer d'exécuter le projet, cliquez d'abord sur le Localisez-moi! bouton, puis le bouton Enregistrer à https://github.com/manumura/flutter-location- champ de formulaire
3 Réponses :
J'ai eu le même problème. Pour moi, cela a fonctionné lorsque j'ai remplacé
return state.build(state.context);
Par le code réel de la méthode de construction et supprimé le remplacement de la méthode de construction de l'état.
Merci, cela fonctionne comme vous l'avez suggéré. Ce n'est pas la façon la plus propre de le faire à mon avis, mais de toute façon, cela fera l'affaire pour l'instant!
Veuillez préciser les modifications exactes, ou appliquez ensuite au projet Github, j'ai exactement le même problème où le validateur et onSave ne sont pas appelés sur un FormField personnalisé
Pas la meilleure façon de le faire. Déplacez le contenu de la fonction de génération dans une fonction personnalisée et supprimez complètement la fonction de génération, au lieu de renvoyer la fonction state.build (), appelez la fonction personnalisée. Exemple: stackoverflow.com/a/60895078/4131351
Réponse 1: Mettez la méthode de construction pour le générateur
Remplacez le générateur de FormField
@override
Widget build(BuildContext context) {
super.build(context); //leads to StackOverflow!
return _buildFormField(); //anything you want
}
par votre fonction de générateur personnalisée p >
/// Saves every [FormField] that is a descendant of this [Form].
void save() {
for (FormFieldState<dynamic> field in _fields)
field.save();
}
Réponse 2: Pseudo CustomFormFieldState
Vous ne pouvez pas étendre le FormFieldState car le remplacement de la fonction "build" provoque des erreurs (expliquées ci-dessous )
Cependant, vous pouvez créer un widget qui prend le FormFieldState comme paramètre pour en faire une classe séparée pour agir comme s'il étend le FormFieldState (ce qui me semble un peu plus propre que la méthode ci-dessus) p >
void _register(FormFieldState<dynamic> field) {
_fields.add(field);
}
Explication
La raison pour laquelle l'extension de FormFieldState ne fonctionne pas est que le remplacement de la méthode de construction dans l'objet FormFieldState provoque le FormFieldState ne pas être enregistré avec le formulaire lui-même.
Voici une liste de fonctions que j'ai suivies pour obtenir mon explication
1) Votre _LocationFormFieldState remplace la méthode de construction, ce qui signifie que la méthode de construction de FormFieldState ne s'exécute jamais
XXX
2) La méthode de construction du FormFieldState s'enregistre dans le FormState actuel
///function in FormFieldState
Widget build(BuildContext context) {
// Only autovalidate if the widget is also enabled
if (widget.autovalidate && widget.enabled)
_validate();
Form.of(context)?._register(this);
return widget.builder(this);
}
3) Le FormState enregistre ensuite le FormFieldState dans une liste
@override Widget build(BuildContext context)
4) Ensuite, lorsque le FormState enregistre / valide, il parcourt la liste des FormFieldStates
class CustomFormField extends FormField<List<String>> {
CustomFormField({
List<String> initialValue,
FormFieldSetter<List<String>> onSaved,
FormFieldValidator<List<String>> validator,
}) : super(
autovalidate: false,
onSaved: onSaved,
validator: validator,
initialValue: initialValue ?? List(),
builder: (state) {
return CustomFormFieldState(state);
});
}
class CustomFormFieldState extends StatelessWidget {
FormFieldState<List<String>> state;
CustomFormFieldState(this.state);
@override
Widget build(BuildContext context) {
return Container(), //The Widget(s) to build your form field
}
}
En remplaçant la méthode de construction vous empêchez le FormField d'être enregistré avec le Form, c'est pourquoi l'enregistrement et le chargement du Form n'appellent pas les méthodes de votre FormField personnalisé.
Si la méthode FormState._register () était publique au lieu de private, vous pouvez appeler cette méthode dans votre méthode _LocationFormFieldState.build pour enregistrer votre application dans le formulaire, mais malheureusement, comme il s'agit d'une fonction privée, vous ne pouvez pas.
Notez également que si vous appelez le super.build () fonction en toi r La méthode de construction de CustomFormFieldState conduit à un StackOverflow
builder: (FormFieldState<LocationData> state) {
return Column(
children: <Widget>[
TextField(
focusNode: _addressInputFocusNode,
controller: _addressInputController,
decoration: InputDecoration(labelText: 'Address'),
),
SizedBox(height: 10.0),
FlatButton(
color: Colors.deepPurpleAccent,
textColor: Colors.white,
child: Text('Locate me !'),
onPressed: _updateLocation,
),
],
});
Cela se produit parce que vous avez remplacé la méthode build () dans _LocationFormFieldState . Lorsque vous remplacez cette méthode, le mécanisme prêt à l'emploi pour enregistrer le champ de formulaire personnalisé et la validation avec le formulaire est également remplacé. Par conséquent, le champ n'est pas enregistré et les méthodes onSaved () et validate () ne sont pas appelées automatiquement.
_LocationFormFieldState , copiez le contenu des méthodes Widget build (contexte BuildContext) dans une nouvelle méthode. Appelons-le Widget _constructWidget () . Puisque nous sommes dans une classe avec état, l'objet context sera implicitement présent. Widget build (contexte BuildContext) de la classe _LocationFormFieldState . Depuis que nous avons supprimé le remplacement, la méthode de la superclasse build sera appelée, qui effectuera l'enregistrement de ce champ de formulaire personnalisé avec le formulaire parent pour nous. LocationFormFieldState , remplacez: builder: (FormFieldState<LocationData> state) {
return (state as _LocationFormFieldState)._constructWidget();
});
Avec
builder: (FormFieldState<LocationData> state) {
return state.build(state.context);
});
Ici, nous avons typé le FormFieldState en _LocationFormFieldState en utilisant l'opérateur as et en appelant notre méthode personnalisée _constructWidget () qui renvoie l'arborescence des widgets (qui était auparavant dans le code build () remplacé >)