Flutter - Form Validation

2022. 7. 13. 12:48DEV/Flutter

반응형

사용 위젯

  • Form
  • TextFormField

TextField 위젯을 사용하면 TextEditingController 를 사용해야하는데, 여러 개의 필드 사용 시 관리가 어려워진다. 그래서 TextFormField 를 사용한다. 그럼 바로 시작해보자.

1. Form 생성

Form 위젯을 생성하고 key 값을 지정한다.

  1. key : GlobalKey() 지정
  2. child : TextFormField 위젯 추가 예정
class FormScreen extends StatefulWidget {
  const FormScreen({Key? key}) : super(key: key);

  @override
  State<FormScreen> createState() => _FormScreenState();
}

class _FormScreenState extends State<FormScreen> {
    // * Form GlobalKey
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey, // * GlobalKey
      child: Column(
        children: const [
          // * TextFormField
        ],
      ),
    );
  }
}

2. TextFormField Refactoring

기본적으로 Form에서 사용하는 TextFormField의 공통 설정들이 있는데 모든 필드에 직접 속성을 설정해서 사용하면 유지 보수가 힘들어진다. 그래서 리팩토링을 해서 사용하는 걸 권장한다. Class로 선언해서 위젯으로 만들 수도 있고, 함수형으로 만들어서 위젯을 반환하는 방식으로 만들 수도 있다.

Class 방식

class MyFormField extends StatelessWidget {
  final String hintText;
  final IconData icon;
  final FormFieldValidator? validator;
  final FormFieldSetter? onSaved;

  const MyFormField({
    Key? key,
    required this.hintText,
    required this.icon,
    this.validator,
    this.onSaved,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      decoration: InputDecoration(
        prefixIcon: Icon(
          icon,
          color: Palette.iconColor,
        ),
        enabledBorder: const OutlineInputBorder(
          borderSide: BorderSide(color: Palette.textColor1),
          borderRadius: BorderRadius.all(Radius.circular(35)),
        ),
        focusedBorder: const OutlineInputBorder(
          borderSide: BorderSide(color: Palette.textColor1),
          borderRadius: BorderRadius.all(Radius.circular(35)),
        ),
        hintText: hintText,
        hintStyle: const TextStyle(
          color: Palette.textColor1,
          fontSize: 14,
        ),
        contentPadding: const EdgeInsets.all(10), // * 높이 조절
      ),
      validator: validator,
      onSaved: onSaved,
            autovalidateMode: AutovalidateMode.always, // * 필드별 자동 체크 모드 ON
    );
  }
}

Function 방식

myTextFormField({
  required Key key,
  required String hintText,
  required IconData icon,
  required FormFieldValidator validator,
  required FormFieldSetter onSaved,
}) {
  return TextFormField(
    decoration: InputDecoration(
      prefixIcon: Icon(
        icon,
        color: Palette.iconColor,
      ),
      enabledBorder: const OutlineInputBorder(
        borderSide: BorderSide(color: Palette.textColor1),
        borderRadius: BorderRadius.all(Radius.circular(35)),
      ),
      focusedBorder: const OutlineInputBorder(
        borderSide: BorderSide(color: Palette.textColor1),
        borderRadius: BorderRadius.all(Radius.circular(35)),
      ),
      hintText: hintText,
      hintStyle: const TextStyle(
        color: Palette.textColor1,
        fontSize: 14,
      ),
      contentPadding: const EdgeInsets.all(10), // * 높이 조절
    ),
    validator: validator,
    onSaved: onSaved,
  );
}

3. TextFormField 생성

  • key : ValueKey() 지정
  • validator : <FormFieldValidator> 타입의 validation 체크 함수 → 오류 시 오류 문구 출력
  • onSaved : <FormFieldSetter> 타입의 value 저장 함수 → setState()
String name = '';
String email = '';

...
MyFormField( // * Class Refactoring
    key: const ValueKey(1),
    validator: (value) {
        // * Check Validation
        if (!RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(value)) {
            return 'Please enter valid email adress';
        }
        return null;
    },
  onSaved: (value) {
        // * Save value
      setState(() {
        password = value!;
      });
        },
    hintText: 'Email',
  icon: Icons.lock,
),
myTextFormField( // * Function Refactoring
    key: const ValueKey(2),
    validator: (value) {
        // * Check Validation
        if (value!.isEmpty || value.length < 6) {
            return 'Password must be at least 7 characters long';
        }
        return null;
    },
  onSaved: (value) {
        // * Save value
      setState(() {
        password = value!;
      });
        },
    hintText: 'Password',
  icon: Icons.lock,
),

...

4. Validaion 실행

  1. Validate Form: if (_formKey.currentState!.validate()
  2. Save Values : _formKey.currentState!.save()
  3. Auto Validation : TextFormField > autovalidateMode: AutovalidateMode.onUserInteraction → 각 필드에 사용자가 입력하는 값의 변화에 따라 즉시 결과를 보여준다.

5. Example Code

import 'package:flutter/material.dart';
import 'package:mychat/config/palette.dart';
import 'package:mychat/screen/sign_screen.dart';

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

  @override
  State<FormScreen> createState() => _FormScreenState();
}

class _FormScreenState extends State<FormScreen> {
  final _formKey = GlobalKey<FormState>();
  String name = '';
  String email = '';
  String password = '';

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          MyFormField(
            key: const ValueKey(1),
            validator: (value) {
              if (value!.isEmpty || value.length < 4) {
                return 'Please enter at least 4 characters';
              }
              return null;
            },
            onSaved: (value) {
              setState(() {
                name = value!;
              });
            },
            hintText: 'User name',
            icon: Icons.account_circle,
          ),
          const SizedBox(height: 8),
          myTextFormField(
            key: const ValueKey(2),
            validator: (value) {
              if (!RegExp(
                      r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
                  .hasMatch(value)) {
                return 'Please enter valid email adress';
              }
              return null;
            },
            onSaved: (value) {
              setState(() {
                email = value!;
              });
            },
            hintText: 'Email',
            icon: Icons.email,
          ),
          const SizedBox(height: 8),
          myTextFormField(
            key: const ValueKey(3),
            validator: (value) {
              if (value!.isEmpty || value.length < 6) {
                return 'Password must be at least 7 characters long';
              }
              return null;
            },
            onSaved: (value) {
              setState(() {
                password = value!;
              });
            },
            hintText: 'Password',
            icon: Icons.lock,
          ),
          ElevatedButton(
            onPressed: () {
              // * 모든 Validation이 OK인 경우
              if (_formKey.currentState!.validate()) {
                // * 입력된 값들을 모두 저장
                _formKey.currentState!.save();
              }
            },
            child: const Text(
              'Save',
              style: TextStyle(
                color: Colors.white,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

myTextFormField({
  required Key key,
  required String hintText,
  required IconData icon,
  required FormFieldValidator validator,
  required FormFieldSetter onSaved,
}) {
  return TextFormField(
    decoration: InputDecoration(
      prefixIcon: Icon(
        icon,
        color: Palette.iconColor,
      ),
      enabledBorder: const OutlineInputBorder(
        borderSide: BorderSide(color: Palette.textColor1),
        borderRadius: BorderRadius.all(Radius.circular(35)),
      ),
      focusedBorder: const OutlineInputBorder(
        borderSide: BorderSide(color: Palette.textColor1),
        borderRadius: BorderRadius.all(Radius.circular(35)),
      ),
      hintText: hintText,
      hintStyle: const TextStyle(
        color: Palette.textColor1,
        fontSize: 14,
      ),
      contentPadding: const EdgeInsets.all(10), // * 높이 조절
    ),
    validator: validator,
    onSaved: onSaved,
    autovalidateMode: AutovalidateMode.onUserInteraction,
  );
}

REF
🔗 [Blog]Form 으로 손쉽게 여러개의 텍스트필드 상태관리하기!
🔗 [YouTube] Form 으로 손쉽게 여러개의 텍스트필드 상태관리하기!
🔗 플러터(Flutter) 조금 매운맛 강좌 22 | 텍스트폼필드 validation 구현

반응형