Closed18

Flutter のフォームバリデーションの Cookbook をやってみる

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Create a Form with a GlobalKey

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

void main() {
  runApp(const MyApp());
}

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

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Build a form with validation',
      home: MyCustomForm(),
    );
  }
}

class MyCustomForm extends StatefulWidget {
  const MyCustomForm({super.key});

  
  State<MyCustomForm> createState() => MyCustomFormState();
}

class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Center(
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              
            ],
          ),
        ),
      ),
    );
  }
}

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Add a TextFormField with validation logic

MyCustomFormState に変更を加えた

main.dart
class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'ご入力ください';
                  } else {
                    return null;
                  }
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

今のところメッセージは表示されない

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

Create a button to validate and submit the form

再度 MyCustomFormState に変更を加えた

main.dart
class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'ご入力ください';
                  } else {
                    return null;
                  }
                },
              ),
              const SizedBox(height: 16),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        const SnackBar(content: Text('バリデーションに成功しました')),
                      );
                    }
                  },
                  child: const Text('送信'),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

メッセージが表示されるようになった!

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

How does this work?

To validate the form, use the _formKey created in step 1. You can use the _formKey.currentState() method to access the FormState, which is automatically created by Flutter when building a Form.

The FormState class contains the validate() method. When the validate() method is called, it runs the validator() function for each text field in the form. If everything looks good, the validate() method returns true. If any text field contains errors, the validate() method rebuilds the form to display any error messages and returns false.

頑張って自力で和訳してみた

フォームをバリデーションするためには Step.1 で作成された _formKey を使います。_formKey.currentState() メソッドを使って FormState にアクセスすることができます。FormState はフォームをビルドするときに Flutter が自動的に作成します。

FormState クラスには validate() メソッドがあります。validate() メソッドが呼び出された時、FormState は フォームに含まれる全てのテキストフィールドの validator() 関数を実行します。すべての関数実行が成功した場合、validate() メソッドは true を返します。一部の関数実行が失敗した場合、validate() メソッドはフォームを再ビルドしてエラーメッセージを表示してから false を返します。

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

StatefulWidget で Riverpod 使えると嬉しいな

と思っていたら普通に使えることを知った、公式ドキュメントにしっかり書かれている

https://riverpod.dev/docs/concepts/reading/#extending-consumerstatefulwidgetconsumerstate-instead-of-statefulwidgetstate

ありがとう、下記の記事に感謝

https://zenn.dev/joo_hashi/articles/ef200c2078e993

下記のページも参考になりそう

https://www.flutter-study.dev/firebase-app/riverpod

日本語の情報が多くて助かる

https://medium.com/@theaayushbhattarai/form-validation-using-riverpod-4e0f902331af

英語だけど何かありがたいことが書いてありそう

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

今更だけど

ラベルを設定するには InputDecoration を使う

TextFormField(
  decoration: const InputDecoration(
    labelText: 'メッセージ',
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'メッセージご入力ください';
    } else {
      return null;
    }
  },
),
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

おわりに

以上で一旦クローズ、とりあえず ConsumerStatefulWidget を使ってフォームを作ってみよう

ダメだったらまた調べよう

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

エラーテキストの設定

下記のように InputDecoration を使ってエラーテキストを設定できる

TextFormField(
  decoration: const InputDecoration(
    errorText: 'エラーテキスト',
  ),
),
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

ユーザー入力を取得する

Flutter 公式ドキュメントの Cookbook に書いてある

https://docs.flutter.dev/cookbook/forms/retrieve-input

main.dart
class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();
  final _controller = TextEditingController();

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  labelText: 'メッセージ',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'メッセージご入力ください';
                  } else {
                    return null;
                  }
                },
              ),
              const SizedBox(height: 16),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      final message = _controller.text;
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('メッセージは「$message」です')),
                      );
                    }
                  },
                  child: const Text('送信'),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

save() メソッドを使う

save() メソッドを使うと TextEditingController がいらなくなるので便利

class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();
  var _message = '';

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                decoration: const InputDecoration(
                  labelText: 'メッセージ',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'メッセージご入力ください';
                  } else {
                    return null;
                  }
                },
                onSaved: (newValue) => _message = newValue!,
              ),
              const SizedBox(height: 16),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      _formKey.currentState!.save();
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('メッセージは「$_message」です')),
                      );
                    }
                  },
                  child: const Text('送信'),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
薄田達哉 / tatsuyasusukida薄田達哉 / tatsuyasusukida

変更には TextEditingController が必要

取得だけではなく変更も必要な場合は結局 TextEditingController が必要

class MyCustomFormState extends State<MyCustomForm> {
  final _formKey = GlobalKey<FormState>();
  final _controller = TextEditingController();

  
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Build a form with validation'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _controller,
                decoration: const InputDecoration(
                  labelText: 'メッセージ',
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'メッセージご入力ください';
                  } else {
                    return null;
                  }
                },
              ),
              const SizedBox(height: 16),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      final message = _controller.text;
                      _controller.text = '';
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('メッセージは「$message」です')),
                      );
                    }
                  },
                  child: const Text('送信'),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}
このスクラップは2023/01/10にクローズされました