入力・検証・表示の流れ
Flutter入門のStatefulWidgetとsetStateを使います。study_appのlib/main.dartを完成例に置き換えて flutter run -d chrome で実行します。学習内容と1〜1440分の時間を受け取り、両方正しければ画面に結果を表示します。保存先はメモリーで、再起動すると消えます。
フォームの完成例
Formは入力欄をまとめ、GlobalKeyでその検証処理へアクセスします。TextEditingControllerで現在の入力を読みます。validatorは不正なら説明文、正しければnullを返します。
import 'package:flutter/material.dart';
void main() => runApp(const StudyApp());
class StudyApp extends StatelessWidget {
const StudyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: MemoFormPage());
}
}
class MemoFormPage extends StatefulWidget {
const MemoFormPage({super.key});
@override
State<MemoFormPage> createState() => _MemoFormPageState();
}
class _MemoFormPageState extends State<MemoFormPage> {
final _formKey = GlobalKey<FormState>();
final _titleController = TextEditingController();
final _minutesController = TextEditingController();
String _message = '未登録';
@override
void dispose() {
_titleController.dispose();
_minutesController.dispose();
super.dispose();
}
void _submit() {
if (!(_formKey.currentState?.validate() ?? false)) return;
final title = _titleController.text.trim();
final minutes = int.tryParse(_minutesController.text.trim());
if (minutes == null) return;
setState(() { _message = '${title}を${minutes}分学習しました'; });
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('学習メモの入力')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _titleController,
decoration: const InputDecoration(labelText: '学習内容'),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return '学習内容を入力してください';
}
return null;
},
),
const SizedBox(height: 16),
TextFormField(
controller: _minutesController,
decoration: const InputDecoration(labelText: '学習時間(分)'),
keyboardType: TextInputType.number,
validator: (value) {
final minutes = int.tryParse(value?.trim() ?? '');
if (minutes == null || minutes < 1 || minutes > 1440) {
return '1〜1440の整数を入力してください';
}
return null;
},
),
const SizedBox(height: 16),
ElevatedButton(onPressed: _submit, child: const Text('登録')),
const SizedBox(height: 16),
Text(_message),
],
),
),
),
);
}
}空欄で「登録」 → 各入力欄にエラーが出る
学習内容「Dart」、時間「20」で登録 → Dartを20分学習しました
時間「abc」「0」「1441」 → 1〜1440の整数を入力してくださいキーボードと検証は別
TextInputType.numberは数値入力向けキーボードの指定です。貼り付けなどで数値以外も入るため、int.tryParseでの検証は必要です。trimで空白を除いてから検証し、空白だけの学習内容も拒否します。
登録ボタンを押すとvalidateが全入力欄のvalidatorを呼びます。1つでも不正なら早期returnし、前の登録結果を書き換えません。正常に登録した後、不正な時間で再登録しても前の結果が残ることを確かめてください。
Dart・20で登録 → Dartを20分学習しました
時間を0にして登録 → エラーが出て、登録結果は前のままControllerの後片付け
ControllerとGlobalKeyはbuildの外で1回だけ作ります。build内で作り直すと入力や検証の状態が失われる原因になります。Widgetが不要になるとdisposeが呼ばれるので、そこで2つのControllerもdisposeします。
SingleChildScrollViewでフォームを囲むと、狭い画面やキーボード表示時に入力欄をスクロールできます。ブラウザーの高さを小さくし、登録ボタンまで移動できることも確認します。
練習:学習内容を20文字以内に
空欄だけでなく、学習内容が20文字を超えた場合もエラーにしてください。DartのString.lengthはUTF-16コード単位なので、画面上の文字数にはcharactersを使います。これはFlutterのmaterial.dartから利用できます。
解答例を確認する
validator: (value) {
final title = value?.trim() ?? '';
if (title.isEmpty) return '学習内容を入力してください';
if (title.characters.length > 20) return '20文字以内で入力してください';
return null;
},空欄 → 学習内容を入力してください
21文字 → 20文字以内で入力してください
Dart・20分 → Dartを20分学習しました