📌

【Flutter】思い通りのバリデーションを作ろう

2022/12/12に公開

この記事は、Flutter大学アドベントカレンダー2022 13日目の記事です。
https://qiita.com/advent-calendar/2022/flutteruniv

はじめに

はじめまして、ヤスーです。
普段はメディア系のSIer、趣味でFlutterアプリ開発を行なっています。

今回はform_field_validatorのパッケージと正規表現を利用してカスタムバリデーション(入力フォームのチェック処理)の実装を記事にしてみました。

最近、入力フォームの実装で、名前の「必須入力チェック」と「文字数チェック」の組み合わせや、インスタグラムの「URLフォーマットチェック(正規表現でのチェック)」等Flutterでバリデーションを開発する機会があったので、その時の方法を載せてみてます。
サンプルコードは以下に格納していますので、ぜひこちらも合わせて参考にしていただけると幸いです。

https://github.com/yasui-kohei/custom_validator

目次

  1. パッケージのインストール
  2. カスタムバリデーションの実装
  3. 入力フォーム画面での利用

1. パッケージのインストール

カスタムバリデーションを作成するためのパッケージをインストールしていきます。
Flutterプロジェクトのpubspec.yamlに、form_field_validatorを追加してpub getします。

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
+ form_field_validator: ^1.1.0

2. カスタムバリデーションの実装

今回は以下のバリデーションを実装していきます。
2.1 名前入力のバリデータ

  • 必須入力チェック
  • 最大長チェック

2.2 正規表現によるインスタグラムのURL入力規則のバリデータ

  • 無効な文字チェック
  • URLのチェック

2.1 名前入力のバリデータ

名前入力時に未入力の場合と5文字を超えた場合に、バリデーションのチェックエラーが表示される実装を行います。

名前入力用バリデータの変数準備

form_field_validatorのパッケージを利用して簡単に実装ができます。
まず、validators.dartというファイルを作成して、そこへnameValidatorという変数名で以下のように定義します。

validators.dart
import 'package:form_field_validator/form_field_validator.dart';
	
final nameValidator = MultiValidator([]);

MultiValidatorは、複数のバリデータ(入力チェック処理)をリストで纏めます。

必須入力チェック

MultiValidatorのリストに必須入力チェックのバリデータを追加します。

validators.dart
import 'package:form_field_validator/form_field_validator.dart';
	
final nameValidator = MultiValidator([
+  // 必須入力チェック
+  RequiredValidator(errorText: "名前を入力してください"),
]);

RequiredValidatorは入力必須チェックのバリデーションで、1つ目の引数errorTextには入力チェックエラー時に表示する文字列を記述しています。

最大長チェック

MultiValidatorのリストに最大長チェック用のバリデータを追加します。

validators.dart
import 'package:form_field_validator/form_field_validator.dart';
	
final nameValidator = MultiValidator([
   // 必須入力チェック
   RequiredValidator(errorText: "名前を入力してください"),
+  // 最大長チェック
+  MaxLengthValidator(5,errorText: "名前は5文字以内で入力してください")
]);

MaxLengthValidatorは最大長チェックのバリデーションになっています。1つ目の引数には最大長の数字、2つ目の引数errorTextにはチェックエラー時に表示する文字列を記述しています。

2.2 SNSのURL入力バリデーション

自分のInstagramのURLに無効な文字が含まれている場合と、InstagramのURLフォーマットに従っていない場合に、バリデーションのチェックエラーが表示される実装を行います。

InstagramのURL入力用バリデータの変数準備

validators.dartinstagramValidatorの変数を追記します。

validators.dart
+ final instagramValidator = MultiValidator([]);

無効な文字のチェック

SNSのURL内に無効な文字列が含まれていないか(半角英数字+記号のみの入力制限)をチェックするバリデータを作成する。
url_validator.dartという新しいファイルに以下の定義をする。

url_validator.dart
import 'package:form_field_validator/form_field_validator.dart';

class UrlValidator extends TextFieldValidator {
  UrlValidator({required String errorText}) : super(errorText);

  
  bool isValid(String? value) =>
      RegExp(r"^[a-zA-Z0-9.a-zA-Z0-9./:!#$%&'*+-=?^_`{|}~]+$").hasMatch(value!);
}


validators.dartに作成したバリデータを追加します。

validators.dart
+ import 'package:custom_validator/validator/sns_url_character_validator.dart';
	
final instagramValidator = MultiValidator([
+  UrlValidator(errorText: "URLに無効な文字が含まれています"), 
]);

InstagramのURLチェック

InstagramのアカウントURLは「https://www.instagram.com/ユーザー名」となっています。そのため、URLフォーマットを正規表現でチェックするバリデータを作成します。
instagram_url_validator.dartを作成して以下を記述します。

instagram_url_validator.dart
import 'package:form_field_validator/form_field_validator.dart';

/// InstagramのURLであることを確認するValidator
class InstagramUrlValidator extends TextFieldValidator {
  InstagramUrlValidator({required String errorText}) : super(errorText);

  
  bool isValid(String? value) => RegExp(
          r"^https?://www\.instagram\.com/[a-zA-Z0-9.a-zA-Z0-9./:!#$%&'*+-=?^_`{|}~]+$")
      .hasMatch(value!);
}


validators.dartに作成したバリデータを追加します。

validators.dart
import 'package:custom_validator/validator/sns_url_character_validator.dart';
+ import 'package:custom_validator/validator/instagram_url_validator.dart';

final instagramValidator = MultiValidator([
   UrlValidator(errorText: "URLに無効な文字が含まれています"), 
+  InstagramUrlValidator(errorText: "Instagramの正しいURLを入力してください(https://www.instagram.com/ユーザー名)")  
]);

3. 入力フォーム画面での利用

画面ソースにおけるバリデータの設定手順は以下の通りになります。

  1. TextFormFieldvalidator引数に目次 2.カスタムバリデータの実装で実装したnameValidatorinstagramValidatorを設定する。
  2. Formクラスで1.の入力フォーム等のウィジェットを囲う
  3. 決定ボタン押下時等にValidatorが動くようkey.currentState!.validate()の処理を記述する。
    まずは、ソースコードを載せます。
form_field_widget.dart
import 'package:custom_validator/success.dart';
import 'package:custom_validator/validators/custom_validators.dart';
import 'package:flutter/material.dart';

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

  
  _FormFieldWidgetState createState() => _FormFieldWidgetState();
}

class _FormFieldWidgetState extends State<FormFieldWidget> {
  
  Widget build(BuildContext context) {
    const key = GlobalObjectKey<FormState>('FORM_KEY');

    /// ユーザー名のTextEditingController
    TextEditingController userNameController = TextEditingController();

    /// インスタグラムURLのTextEditingController
    TextEditingController instagramUrlController = TextEditingController();
    return Form(
      key: key,
      child: Padding(
        padding: const EdgeInsets.all(15.0),
        child: Column(
          children: [
            /// 名前のテキストフォーム
            const Text('ユーザー名 '),
            TextFormField(
              controller: userNameController,
              validator: nameValidator,
              decoration: const InputDecoration(
                errorMaxLines: 3,
              ),
            ),
            const SizedBox(height: 30),
	    
            /// インスタグラムURLの入力フォーム
            const Text('インスタグラムのURL'),
            TextFormField(
              controller: instagramUrlController,
              validator: instagramValidator,
              decoration: const InputDecoration(
                errorMaxLines: 3,
              ),
            ),
            const SizedBox(height: 30),

            /// 決定ボタン
            MaterialButton(
              onPressed: () {
                if (key.currentState!.validate()) {
                  // フォームの入力値が正しかった場合成功画面に遷移する。
                  Navigator.push(context,
                    MaterialPageRoute(builder: (context) => const SuccessScreen())
                  );
                }
              },
              child: const Text(
                '決定',
              ),
            ),
          ],
        ),
      ),
    );
  }
}

1. TextFormFieldで入力フォームを用意して、validatorの引数に作成したカスタムバリデータを設定する。

名前入力用のフォームとインスタグラムURL入力用のフォームを用意する。

form_field_widget.dart
import 'package:custom_validator/validators/custom_validators.dart';

const Text('ユーザー名'),
TextFormField(
    controller: userNameController,
    // 上記で作成したnameValidatorを挿入
    validator: nameValidator
    decoration: const InputDecoration(
    errorMaxLines: 3,
  ),
),
form_field_widget.dart
import 'package:custom_validator/validators/custom_validators.dart';

/// インスタグラムのURL入力フォーム
const Text('インスタグラムのURL'),
TextFormField(
    controller: userNameController,
    // 上記で作成したinstagramValidatorを挿入
    validator: instagramValidator,
    decoration: const InputDecoration(
    errorMaxLines: 3,
  ),
),

2. Formクラスで入力フォームのウィジェット等を囲う

form_field_widget.dart
const key = GlobalObjectKey<FormState>('FORM_KEY');

Form(key: key,
  child: //※入力フォーム等のウィジェット(ソースが長いため省略)
)

3. 決定ボタン押下時等にValidatorが動くようkey.currentState!.validate()の処理を記述する。

form_field_widget.dart
/// 決定ボタン
MaterialButton(
  onPressed: () {
    if (key.currentState!.validate()) {
      // フォームの入力値が正しかった場合成功画面に遷移する。
      Navigator.push(context,
        MaterialPageRoute(builder: (context) => const SuccessScreen())
      );
    }
  },
  child: const Text('OK',),
),

おわりに

今回は、カスタムバリデータの実装方法について紹介していきました。
紹介した方法は、自分でカスタムできることに加えて、バリデータの使い回しなどもできることが利点だと思っています。

参考

https://pub.dev/packages/form_field_validator
https://dev.classmethod.jp/articles/flutter-inline-validation/
https://flutter.salon/dart/regexp/

Discussion