Flutter のフォームバリデーションの Cookbook をやってみる
はじめに
このスクラップでは Flutter 公式ドキュメントの Build a form with validation をやってみる
前回
前のスクラップでは Flutter の carousel_slider を使ってみた
準備コマンド
flutter create hello_form
cd hello_form
なんか重要なことがさらっと書いてある
Tip: Using a GlobalKey is the recommended way to access a form. However, if you have a more complex widget tree, you can use the Form.of() method to access the form within nested widgets.
Create a Form with a GlobalKey
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: [
],
),
),
),
);
}
}
Add a TextFormField with validation logic
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: 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;
}
},
)
],
),
),
),
);
}
}
今のところメッセージは表示されない
Create a button to validate and submit the form
再度 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: 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('送信'),
),
)
],
),
),
),
);
}
}
メッセージが表示されるようになった!
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
を返します。
StatefulWidget で Riverpod 使えると嬉しいな
と思っていたら普通に使えることを知った、公式ドキュメントにしっかり書かれている
ありがとう、下記の記事に感謝
下記のページも参考になりそう
日本語の情報が多くて助かる
英語だけど何かありがたいことが書いてありそう
今更だけど
ラベルを設定するには InputDecoration を使う
TextFormField(
decoration: const InputDecoration(
labelText: 'メッセージ',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'メッセージご入力ください';
} else {
return null;
}
},
),
validator が async だったらどうするんだろう
色々読んでいるけど今のところ理解が及んでいない
おわりに
以上で一旦クローズ、とりあえず ConsumerStatefulWidget を使ってフォームを作ってみよう
ダメだったらまた調べよう
おわらない
フォームから値を取得したりするのを忘れていた
あと下記が参考になる
エラーテキストの設定
下記のように InputDecoration を使ってエラーテキストを設定できる
TextFormField(
decoration: const InputDecoration(
errorText: 'エラーテキスト',
),
),
ユーザー入力を取得する
Flutter 公式ドキュメントの Cookbook に書いてある
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('送信'),
),
)
],
),
),
),
);
}
}
なるべく TextFormField を使ってダメだったら TextField を使うのが良さそう
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('送信'),
),
)
],
),
),
),
);
}
}
変更には 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('送信'),
),
)
],
),
),
),
);
}
}
おわりに2
今度こそ一旦クローズ