Flutter convert a form with provider and riverpod

Hi guys,
I am looking to transform a form code using provider and riverpod. I put you first my functional code and second part the code I’m trying to do with provider.

First part ------------------------------------------------------------------------------

So my basic code is composed by :

  • Calculator Screen :
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;
  }
}

Second part ------------------------------------------------------------------------------

In this part I try to redo my form with provider.
I have a critical error which tells me: ‘The method validate was called on null’

  • Calculator Screen :
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

class CalculatorScreen extends StatefulWidget{
  const CalculatorScreen({Key key}) : super(key: key);

  @override
  _CalculatorScreenState createState() => _CalculatorScreenState();
}

class _CalculatorScreenState extends State<CalculatorScreen>
{
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
      child: Scaffold(
          body : SingleChildScrollView(
            child: Column(
              children: [
                const TitleWidget(),
                ContainerComponent(
                  background: AppColors.colorBgLight,
                  children: [
                    CalculatorFormWidget(),
                    ButtonComponent.primary(
                      text: "Calculer",
                      context: context,
                      onPressed : context.read(formCalculatorProvider).submitData(),
                    ),
                  ],
                )
              ],
            ),
          ),
      ),
    );
  }
}

class TitleWidget extends StatelessWidget{
  const TitleWidget({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const TitleComponent(
      title: "Calcul de charge",
      description: "Parametrer l'environnement de tir",
    );
  }
}

class CalculatorFormWidget extends HookWidget{
  const CalculatorFormWidget({Key key}): super(key : key);

  @override
  Widget build(BuildContext context) {

    final _controllerDistance = useTextEditingController();
    final _formModel = useProvider(formCalculatorProvider.state);
    FormCalculatorModel _formData = FormCalculatorModel();

    return Form(
      key : useProvider(formCalculatorProvider).formKey,
      autovalidate: _formModel.autoValidate,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextFormField(
            decoration: InputDecoration(
              labelText: "Distance",
            ),
            controller: _controllerDistance,
            validator: (String value){
              return FormValidatorService.isDistanceValid(value);
            },
          ),
        ],
      ),
    );
  }
}
  • FormCalculatorNotifier :
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

enum EnumFormState
{
  EMPTY,
  SUCCESS,
  ERROR
}

class FormDataCalculatorModel {
  const FormDataCalculatorModel({
    this.distance,
  });

  final int distance;
}

class FormCalculatorModel {
  const FormCalculatorModel({
    this.formState,
    this.autoValidate,
    this.formData,
    this.formKey
  });

  final EnumFormState formState;
  final bool autoValidate;
  final FormDataCalculatorModel formData;
  final GlobalKey<FormState> formKey;
}

class FormCalculatorNotifier extends StateNotifier<FormCalculatorModel>
{
  FormCalculatorNotifier() : super(_initial);

  static const EnumFormState _initialState = EnumFormState.EMPTY;
  //static const ValidationItem _initialItem = ValidationItem(value : null, error : null);
  static const _initial = FormCalculatorModel(
      formState : _initialState,
      autoValidate: false,
      formData: FormDataCalculatorModel(),
  );

  GlobalKey<FormState> get formKey => GlobalKey<FormState>();

   submitData(){
     if (!formKey.currentState.validate()) {
       return;
     }
     formKey.currentState.save();
  }
}
  • The provider :
final formCalculatorProvider = StateNotifierProvider((ref) => FormCalculatorNotifier());