Search code examples
flutterflutter-reactive-forms

Why reactiveTextFields doesn't work well when in different class than FormGroup?


I have a class where I initialized FormGroup:

final form = FormGroup(
    {
      'oldPassword': FormControl<String>(
        value: null,
        validators: [
          Validators.required,
        ],
      ),
      'newPassword': FormControl<String>(
        value: null,
        touched: true,
        validators: [
          Validators.pattern(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$',
              validationMessage:
                  'Required: \n * One uppercase and one downcase letter \n * Atleast one number \n * Atleast 8 characters')
        ],
      ),
      'repeatPassword': FormControl<String>(
        value: null,
        validators: [
          Validators.required,
        ],
      ),
    },
    validators: [
      Validators.mustMatch(
        'newPassword',
        'repeatPassword',
      )
    ],
  );

I created a StatefullWidget class ReactivePasswordField:

import 'package:flutter/material.dart';
import 'package:reactive_forms/reactive_forms.dart';

class ReactivePasswordField extends StatefulWidget {
  final String controlName;
  final String hintText;
  final Function() onSubmitted;

  const ReactivePasswordField({
    required this.controlName,
    required this.hintText,
    required this.onSubmitted,
  });

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

class _ReactivePasswordFieldState extends State<ReactivePasswordField> {
  bool isObscure = true;
  IconData icon = Icons.visibility_off;
  late String controlName;

  @override
  void initState() {
    controlName = widget.controlName;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return ReactiveTextField(
      autofocus: true,
      obscureText: isObscure,
      formControlName: 'oldPassword',
      onSubmitted: () => widget.onSubmitted,
      validationMessages: (control) => {
        'required': 'Field cannot be empty.',
        'mustMatch': 'New password must match.'
      },
      decoration: InputDecoration(
        filled: true,
        prefixIcon: Icon(
          Icons.lock,
          color: Color(0xffe96cbd),
        ),
        hintText: widget.hintText,
        suffixIcon: GestureDetector(
            onTap: () {
              if (isObscure) {
                setState(() {
                  icon = Icons.visibility_off;
                  isObscure = false;
                });
              } else {
                setState(() {
                  icon = Icons.visibility;
                  isObscure = true;
                });
              }
            },
            child: Icon(
              icon,
              color: Color(0xff3A4A78),
            )),
        border: OutlineInputBorder(
          borderSide: BorderSide.none,
          borderRadius: BorderRadius.circular(20.0),
        ),
      ),
    );
  }
}

I did it, because i want every ReactivePasswordField to have own state to manage showing and hiding input view. The problem is when I input to one of ReactivePasswordField the others have same input. My Validators also doesn't work. How can i fix it?

There is how i use my ReactivePasswordFields in a class where i initialized FormGroup:

SingleChildScrollView(
          child: ReactiveForm(
        formGroup: form,
        child: Column(
          children: [
            ReactivePasswordField(
                controlName: 'oldPassword',
                hintText: 'Current password',
                onSubmitted: () => form.focus('newPassword')),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              child: ReactivePasswordField(
                controlName: 'newPassword',
                hintText: 'New password',
                onSubmitted: () => form.focus('repeatPassword'),
              ),
            ),
            ReactivePasswordField(
              controlName: 'repeatPassword',
              hintText: 'Repeat password',
              onSubmitted: () => {},
            )
          ],
        ),
      )),

Solution

  • I just messed up in my ReactivePasswordField class. There are the changes:

    import 'package:flutter/material.dart';
    import 'package:reactive_forms/reactive_forms.dart';
    
    class ReactivePasswordField extends StatefulWidget {
      final String controlName;
      final String hintText;
      final Function() onSubmitted;
    
      const ReactivePasswordField({
        required this.controlName,
        required this.hintText,
        required this.onSubmitted,
      });
    
      @override
      _ReactivePasswordFieldState createState() => _ReactivePasswordFieldState();
    }
    
    class _ReactivePasswordFieldState extends State<ReactivePasswordField> {
      bool isObscure = true;
      IconData icon = Icons.visibility_off;
    
    
      @override
      Widget build(BuildContext context) {
        return ReactiveTextField(
          autofocus: true,
          obscureText: isObscure,
    
    //-------------CHANGE--------------------------------------------------------
    //Here i had static String instead of widget.controlName 
          formControlName: widget.controlName,
          onSubmitted: () => widget.onSubmitted,
          validationMessages: (control) => {
            'required': 'Field cannot be empty.',
            'mustMatch': 'New password must match.'
          },
          decoration: InputDecoration(
            filled: true,
            prefixIcon: Icon(
              Icons.lock,
              color: Color(0xffe96cbd),
            ),
            hintText: widget.hintText,
            suffixIcon: GestureDetector(
                onTap: () {
                  if (isObscure) {
                    setState(() {
                      icon = Icons.visibility_off;
                      isObscure = false;
                    });
                  } else {
                    setState(() {
                      icon = Icons.visibility;
                      isObscure = true;
                    });
                  }
                },
                child: Icon(
                  icon,
                  color: Color(0xff3A4A78),
                )),
            border: OutlineInputBorder(
              borderSide: BorderSide.none,
              borderRadius: BorderRadius.circular(20.0),
            ),
          ),
        );
      }
    }