Récupérer le résultat d'une fonction async dans le validator

Bonjour à tous,

J’ai une petite galère que je n’arrive pas à résoudre.

Dans un formulaire, je souhaite vérifier que le code postal saisi par l’utilisateur soit bien valide.
J’ai créé une fonction async à laquelle je fais passer la saisie de l’utilisateur et qui fait appel à une API.
Problème, je n’arrive pas à récupérer les valeurs (String) renvoyées par la fonction afin de les afficher à l’aide du validator (mes prints fonctionnent).
Ci-dessous les bouts de code pour vos yeux d’experts:

class _SignUpScreenState extends State<SignUpScreen> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _postalCodeController = TextEditingController();

  void _saveform() async {
    setState(() {
      _formKey.currentState!.validate();
    });
  }

  Future<String> getPostalCode(int postalCode) async {
    try {
      final response = await http.get(Uri.parse(
          'https://apicarto.ign.fr/api/codes-postaux/communes/$postalCode'));
      var data = jsonDecode(response.body);
      if (response.statusCode == 200 && data.isNotEmpty) {
        print('Code postal valide');
        return 'Code postal valide';
      }
      print('Entre un code postal valide');
      return 'Entre un code postal valide';
    } catch (e) {
      print('Vérification du code postal impossible');
      return 'Vérification du code postal impossible';
    }
  }

  @override
  void dispose() {
    _postalCodeController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final screenHeightRatio =
        MediaQuery.of(context).size.height / defaultScreenHeight;

    return Scaffold(
      appBar: const AppBarWidget(
        isAppBarBottom: true,
        appBarBottomText: 'Inscription',
        appBarBottomTextColor: Colors.black,
      ),
      body: SingleChildScrollView(
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              if (screenHeightRatio < 1)
                SizedBox(
                  height: 5 * screenHeightRatio,
                )
              else
                SizedBox(
                  height: 15 * screenHeightRatio,
                ),
              InputFormField(
                controller: _postalCodeController,
                inputFormatters: [
                  FilteringTextInputFormatter.digitsOnly,
                  LengthLimitingTextInputFormatter(5),
                ],
                keyboardType: const TextInputType.numberWithOptions(
                  decimal: false,
                  signed: false,
                ),
                labelText: 'Code postal :',
                screenHeightRatio: screenHeightRatio,
                textInputAction: TextInputAction.next,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Entre ton code postal';
                  }
                  if (value.isNotEmpty) {
                    getPostalCode(int.parse(value));
                  }
                  return null;
                },
              ),
              ButtonWidget(
                buttonColor: greenColor,
                buttonFontSize: 24,
                buttonHeight: 50,
                buttonText: 'Je valide',
                buttonTextColor: Colors.white,
                buttonWidth: 200,
                buttonFunction: _saveform,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text(
                    'Déjà inscrit ?',
                    style: TextStyle(
                      fontFamily: 'GillSans',
                    ),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).pushReplacementNamed('/signin');
                    },
                    child: const Text(
                      'Je me connecte',
                      style: TextStyle(
                        fontFamily: 'GillSans',
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Et le code de mon widget InputFormField:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:winiz/config/constants.dart';

class InputFormField extends StatelessWidget {
  final TextEditingController? controller;
  final bool? enableCopyPaste;
  final List<TextInputFormatter>? inputFormatters;
  final TextInputType? keyboardType;
  final String labelText;
  final int? maxLength;
  final bool? osbcureText;
  final double? screenHeightRatio;
  final IconButton? suffixIcon;
  final Color? suffixIconColor;
  final TextInputAction? textInputAction;
  final String? Function(String?)? validator;

  const InputFormField({
    super.key,
    required this.controller,
    required this.labelText,
    this.enableCopyPaste,
    this.inputFormatters,
    this.keyboardType,
    this.maxLength,
    bool? osbcureText,
    double? screenHeightRatio,
    this.suffixIcon,
    this.suffixIconColor,
    this.textInputAction,
    this.validator,
  })  : screenHeightRatio = screenHeightRatio ?? 1,
        osbcureText = osbcureText ?? false;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: 10.0,
      ),
      child: Container(
        margin: EdgeInsets.symmetric(vertical: 5 * screenHeightRatio!),
        padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 1.0),
        //height: 50 * screenHeightRatio!,
        width: double.infinity,
        decoration: BoxDecoration(
          border: Border.all(
            color: Colors.grey,
          ),
          borderRadius: BorderRadius.circular(30),
          color: yellowLightColor,
        ),
        child: TextFormField(
          controller: controller,
          decoration: InputDecoration(
            border: InputBorder.none,
            contentPadding:
                const EdgeInsets.symmetric(vertical: 5, horizontal: 20),
            errorStyle: const TextStyle(
              height: 0.3,
            ),
            labelText: labelText,
            suffixIcon: suffixIcon,
            suffixIconColor: suffixIconColor,
          ),
          enableInteractiveSelection: enableCopyPaste,
          inputFormatters: inputFormatters,
          keyboardType: keyboardType,
          maxLength: maxLength,
          obscureText: osbcureText!,
          textInputAction: textInputAction,
          validator: validator,
        ),
      ),
    );
  }
}

Un petit avis?
Merci beaucoup.

Edit:
Bon, après quelques grattages de tête, j’ai réussi à faire ce que je voulais. D’après ce que j’ai lu, on ne peut pas utiliser de fonction async directement à l’aide du validator.
J’exécute donc la fonction async quand on clique sur le bouton (fonction _saveform) avant le validator.
Je vous mets le code ci-dessous. N’hésitez pas à me faire des remarques si vous voyez des améliorations à apporter. Bonne soirée.

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

// import 'package:url_launcher/url_launcher.dart';

import '../config/constants.dart';

import '../widgets/appbar_widget.dart';
import '../widgets/button_widget.dart';
import '../widgets/checkbox_form_widget.dart';
import '../widgets/input_form_field_widget.dart';

class SignUpScreen extends StatefulWidget {
  const SignUpScreen({super.key});

  static const routeName = '/signup';

  @override
  State<SignUpScreen> createState() => _SignUpScreenState();
}

class _SignUpScreenState extends State<SignUpScreen> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
  final TextEditingController _postalCodeController = TextEditingController();
  String? _postalCodeMessage;

  void _saveform() async {
    String? postalCodeMessage;
    if (_postalCodeController.value.text != '') {
      postalCodeMessage =
          await checkPostalCode(_postalCodeController.value.text);
    }
    setState(() {
      _postalCodeMessage = postalCodeMessage;
      _formKey.currentState!.validate();
    });
  }

  Future<String?> checkPostalCode(postalCode) async {
    try {
      final response = await http.get(Uri.parse(
          'https://apicarto.ign.fr/api/codes-postaux/communes/$postalCode'));
      if (response.statusCode == 200) {
        return null;
      }
      if (response.statusCode == 400 || response.statusCode == 404) {
        return 'Entre un code postal valide';
      }
      return null;
    } catch (e) {
      return 'Vérification du code postal impossible';
    }
  }

  @override
  void dispose() {
    _postalCodeController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final screenHeightRatio =
        MediaQuery.of(context).size.height / defaultScreenHeight;

    return Scaffold(
      appBar: const AppBarWidget(
        isAppBarBottom: true,
        appBarBottomText: 'Inscription',
        appBarBottomTextColor: Colors.black,
      ),
      body: SingleChildScrollView(
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              if (screenHeightRatio < 1)
                SizedBox(
                  height: 5 * screenHeightRatio,
                )
              else
                SizedBox(
                  height: 15 * screenHeightRatio,
                ),
              InputFormField(
                controller: _postalCodeController,
                inputFormatters: [
                  FilteringTextInputFormatter.digitsOnly,
                  LengthLimitingTextInputFormatter(5),
                ],
                keyboardType: const TextInputType.numberWithOptions(
                  decimal: false,
                  signed: false,
                ),
                labelText: 'Code postal :',
                screenHeightRatio: screenHeightRatio,
                textInputAction: TextInputAction.next,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Entre ton code postal';
                  }
                  if (_postalCodeMessage != null) {
                    return _postalCodeMessage;
                  }
                  return null;
                },
              ),
              const SizedBox(
                height: 10,
              ),
              ButtonWidget(
                buttonColor: greenColor,
                buttonFontSize: 24,
                buttonHeight: 50,
                buttonText: 'Je valide',
                buttonTextColor: Colors.white,
                buttonWidth: 200,
                buttonFunction: _saveform,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text(
                    'Déjà inscrit ?',
                    style: TextStyle(
                      fontFamily: 'GillSans',
                    ),
                  ),
                  TextButton(
                    onPressed: () {
                      Navigator.of(context).pushReplacementNamed('/signin');
                    },
                    child: const Text(
                      'Je me connecte',
                      style: TextStyle(
                        fontFamily: 'GillSans',
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Une suggestion:
Dans ton ton InputFormField, le validator ne nécessite-t-il pas le mot clé async?

Et par la même occasion, pourquoi ton getPostalCode(int.parse(value)) n’a pas de await ?
Alors que sa valeur de retour c’est un Futur ?

Bonjour à tous, je me permet juste une remarque pr rapport au type de variable pour les codes postaux.
Dans ma jeune expérience qui n’était pas si lointaine, j’utilise également le integer pour les codes postaux dans mes bases de données jusqu’à ce que je tombe sur des codes postaux avec des lettres… et je peux vous dire que ça n’a pas été très fun de modifier tout mon code et la BDD.
Sans compter aussi les codes postaux avec des zéros en tête

2 « J'aime »

Tout à fait d’accord. J’avais modifié mon code pour passer en String comme on peut le voir dans le deuxième bout de code. Je me suis souvenu avoir eu il y a quelques années des codes postaux en 3000 au lieu de 03000 pa exemple… :sweat_smile:

1 « J'aime »