😸

[Flutter]Validationを用いたformを作る

2022/10/02に公開

参考記事は以下
Build a form with validation

  • 公式リファレンスのページを丸々噛み砕くのもいいが、「なぜそれを事前定義する?」など、疑問に思いがちな点を付け加えていく

Validationとは

  • 入力されたデータが「不正な書き方じゃない?」「変な文字使ってない?」かを調べてエラー表示などをすること

Validationを用いたformの作り方

1. GlobalKeyでformを作る

重要なのはfinal _formKey = GlobalKey<FormState>();以下

  • GlobalKey<FormState>()を持たせた_formKeyオブジェクトを作成
  • 管理したい場所にkeyプロパティを設置して親はForm(系)Widget(プロパティに「key」をとれるもの)にする。
main.dart
import 'package:flutter/material.dart';
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  
  MyCustomFormState createState() {
    return MyCustomFormState();
  }
}
class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();//定義して

  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,//管理したい場所にkeyプロパティを設置
      child: Column(
        children: <Widget>[
          // Add TextFormFields and ElevatedButton here.
        ],
      ),
    );
  }
}

2. バリデーションの処理を持たせたTextFormField Widgetを用意する

  • 上記①のForm Widget以下にバリデーションの処理を持たせたTextFormField Widgetを用意
TextFormField(
  // バリデーション処理を持たせたTextFormField(系)Widgetをテキストフィールド(記入欄)を
  //①の「form Widget」の子供にもたせる
  validator: (value) {
    //バリデーション。テキストフィールドがnull or 空の状態なら「良くないよ、記入しろよ」って怒るようにしとく。
    if (value == null || value.isEmpty) {
      return 'Please enter some text';
    }
    return null;
    //ユーザーの入力が有効の場合、すなわちStringエラーがない場合、バリデーターは null を返す必要がある。
    //validatorは「異常ないか検知」する仕組みなのでどの異常にもならんかったら「異常なし = null」を返す
  },
),

3. formの内容をバリデート(検証)結果に沿って送信するためのボタンを設置する

  • 送信ボタン押した時に検証済みならif内の処理が走るようにする
ElevatedButton(
  onPressed: () {
    // isValid(検証ずみ)かどうかに焦点を当てる。
    if (_formKey.currentState!.validate()) {
      //_formKeyのcurrentStateが検証済みなら、以下の処理。スナックバーで「成功したよ」って出す。
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Processing Data')),
      );
    }
  },
  child: const Text('Submit'),
),
  • SnackBar:一般に画面下などから出てくる「成功しました」「ログインできませんでした」的なトースト(ログ)のこと
    →実際に表示するWidgetがこれ
  • ScaffoldMessenger:子Widget以下でScaffoldのSnackBarなどを呼び出せるクラス。
  • currentStateメソッド:GlobalKeyを持ったオブジェクト(ここでは_formKey)が定義されてるWidget(クラス)以下の状態をもつ。以下で解説

サンプルコード

  • 公式引用、重要なのは①②③の部分
main.dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    const appTitle = 'Form Validation Demo';

    return MaterialApp(
      title: appTitle,
      home: Scaffold(
        appBar: AppBar(
          title: const Text(appTitle),
        ),
        body: const MyCustomForm(),
      ),
    );
  }
}
class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  
  MyCustomFormState createState() {
    return MyCustomFormState();
  }
}
class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();//①:定義

  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      //①:formのkeyプロパティにオブジェクトを持たせる。ここ以下のWidgetを管理できるようになる
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          TextFormField(
            //②:バリデーションの処理を持たせたTextFormField Widgetを用意する
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Please enter some text';
              }
              return null;
            },
          ),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 16.0),
            child: ElevatedButton(
              onPressed: () {
                //③:formの内容をバリデート(検証)して送信するためのボタンを設置する
                if (_formKey.currentState!.validate()) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('Processing Data')),
                  );
                }
              },
              child: const Text('Submit'),
            ),
          ),
        ],
      ),
    );
  }
}

疑問点と考察

1. GlobalKeyって?

Keyについての参考記事
公式GlobalKey Class

「Formを一意に識別できるもの」

って言われてもピンとこないので言い換える

  • アプリ全体のどこからでも(グローバルに)ピンポイントで指させるマーク」的な感じのもの

反対に
LocalKeyとは

  • そのオブジェクト以下でしか指させないマーク」的な感じ

上のサンプルコードも、もし final _formKey = LocalKey <FormState>()で定義していたら
key: _formKey,を定義したwidgetとその子widget以外からは正確に状態を指し示すことはできなくなる。
そこのグローバルorローカルの判断は状態を把握したい範囲やコストによる

2. currentStateメソッドとは?

  • GlobalKeyを持つツリー内のウィジェットの現在の状態をとってくるメソッド。

    1. このGlobalKeyに一致するウィジェットがツリー内にない場合、要するに「キーを指させない場合
    2. そのウィジェットが StatefulWidget でない場合、要するに「statefulWidget or 状態管理できるWidget ではない場合

    この二つの場合で currentState = null になる!

また、

  • なぜサンプルコードで「!」でnullじゃないって断言してるの?

②のバリデートでテキストフィールドが空の場合やnullなら弾かれるようにしてあるから、③では.currentState!(nullはあり得ないよ!)としてる。


所感

Keyについて理解するタイミングとしては良かった。
「エレメントツリー(状態を司る)からwidgetツリーを一意に指し示す」のが多分厳密な言い方で、statefulな状態管理も絡んでくるので自分もよくわかってない節がありまくるが、参考になれば嬉しいです🙇‍♂️

GitHubで編集を提案

Discussion