27
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Flutter】Formを一から作りながら解説してみたよ

Last updated at Posted at 2020-05-17

image.png

概要

アプリを作っていると、フォームってほぼ確実に一回は作りますよね。
ログイン画面とか。
そんなフォーム作成に特化したWidget、Formについて今回は解説していきたいと思います。

ソースコードは以下に置いています。
https://github.com/daiki1003/form_example

前提

Flutterプロジェクトを作成し、出来上がった_MyHomePageStatebody部分を中心に記載していきます。

FormとTextFormField

まず、Formで包んだTextFormFieldを用意してみる

main.dart
@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Form(
        child: TextFormField(
          decoration: InputDecoration(labelText: 'Title'),
        ),
      ),
    );
  }

※以降はbodyのみ

実行結果

解説

これだけで、アニメーションなどが実装されたちょっと良い感じのテキストフォームが出来上がる。

キーボードを二つ並べる

main.dart
Form(
  child: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'id'),
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
      ),
    ],
  ),
)

実行結果

解説

はい、2つ並びました。

まず、
FormはTextFormFieldなどFormFieldを基底とするWidgetと裏で連携していますが、それしか置けないわけではない
です。

入力中のパスワードを非表示にする

main.dart
Form(
  child: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'id'),
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
        obscureText: true, // 追加
      ),
    ],
  ),
)

実行結果

解説

この様に入力したテキストが隠れる様になります。

doneボタンが押された時の処理を記述したい

main.dart
Form(
  child: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'id'),
        textInputAction: TextInputAction.next,
        onFieldSubmitted: (value) {
          print(value);
        }, // 追加
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
        obscureText: true,
      ),
    ],
  ),
)

実行結果

引数valueには現在入力されている値が渡されます。
よって、コンソールに現在入力されているidの値が表示されます。

キーボードの右下ボタンをdoneからnextに変更

main.dart
Form(
  child: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'id'),
        textInputAction: TextInputAction.next, // 追加
        onFieldSubmitted: (value) {
          print(value);
        },
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
        obscureText: true,
      ),
    ],
  ),
)

実行結果

解説

nextになりました。
が、 これだけではpasswordに移動してはくれません

passwordに移動する

main.dart
@override
Widget build(BuildContext context) {
  final _passwordFocusNode = FocusNode(); // 追加

  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Form(
      child: Column(
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(labelText: 'id'),
            textInputAction: TextInputAction.next,
            onFieldSubmitted: (_) {
              FocusScope.of(context).requestFocus(_passwordFocusNode);
            }, // 変更
          ),
          TextFormField(
            decoration: InputDecoration(labelText: 'password'),
            obscureText: true,
            focusNode: _passwordFocusNode, // 追加
          ),
        ],
      ),
    ),
  );
}

実行結果

解説

これで無事にpasswordに移動しましたね。

FocusNode インスタンスを生成
・移動したいTextFormFieldに割り当てる
・FocusScope.of(context).requestFocusで対象のTextFormFieldにフォーカスを当てる

と言う流れで実行できます。

初期値を設定する

main.dart
Form(
  child: Column(
    children: <Widget>[
      TextFormField(
        initialValue: 'initial id', // 追加
        decoration: InputDecoration(labelText: 'id'),
        textInputAction: TextInputAction.next,
        onFieldSubmitted: (_) {
          FocusScope.of(context).requestFocus(_passwordFocusNode);
        },
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
        obscureText: true,
        focusNode: _passwordFocusNode,
      ),
    ],
  ),
)

実行結果

現状の入力値を保存したい

main.dart
@override
Widget build(BuildContext context) {
  final _passwordFocusNode = FocusNode();
  final _form = GlobalKey<FormState>(); // 追加
  String _userId;
  String _password;

  return Scaffold(
    appBar: AppBar(
      title: Text(widget.title),
    ),
    body: Form(
      key: _form, // 追加
      child: Column(
        children: <Widget>[
          TextFormField(
            decoration: InputDecoration(labelText: 'id'),
            textInputAction: TextInputAction.next,
            onFieldSubmitted: (_) {
              FocusScope.of(context).requestFocus(_passwordFocusNode);
            },
            onSaved: (value) {
              _userId = value;
            }, // 追加
          ),
          TextFormField(
            decoration: InputDecoration(labelText: 'password'),
            obscureText: true,
            focusNode: _passwordFocusNode,
            onSaved: (value) {
              _password = value;
            }, // 追加
          ),
          FlatButton(
            child: Text('Save'),
            color: Colors.grey,
            onPressed: () {
              _form.currentState.save();
              print(_userId);
              print(_password);
            },
          ), // 追加
        ],
      ),
    ),
  );
}

実行結果

flutter: test
flutter: testpassword

解説

まず、GlobalKey<FormState>インスタンスを生成します。
このインスタンスに対してcurrentStateを参照すると指定したState(今回はFormState)のインスタンスが取得できます。
このインスタンスに対してsaveメソッドを投げることによってFormの内容を保存する事が出来ます。

さて、保存をすると、各TextFormFieldのonSavedメソッドが呼ばれます。
なので、各々のTextFormFieldにこれを設定してあげてしかるべき場所に保存します。

saveメソッドを実行し終えた後は各widgetのonSavedが動いた後なので、無事にprintが動作すると言う流れです。

バリデーションしたい

main.dart
Form(
  key: _form,
  child: Column(
    children: <Widget>[
      TextFormField(
        decoration: InputDecoration(labelText: 'id'),
        textInputAction: TextInputAction.next,
        validator: (value) {
          if (value.isEmpty) {
            return 'Please provide a value.';
          }
          if (value.length <= 4) {
            return 'id must be longer than 4 characters.';
          }
          if (16 < value.length) {
            return 'id must be less than 16 characters.';
          }
          return null;
        }, // 追加
        onFieldSubmitted: (_) {
          FocusScope.of(context).requestFocus(_passwordFocusNode);
        },
        onSaved: (value) {
          _userId = value;
        },
      ),
      TextFormField(
        decoration: InputDecoration(labelText: 'password'),
        obscureText: true,
        focusNode: _passwordFocusNode,
        validator: (value) {
          if (value.isEmpty) {
            return 'Please enter a password.';
          }
          return null;
        }, // 追加
        onSaved: (value) {
          _password = value;
        },
      ),
      FlatButton(
        child: Text('Save'),
        color: Colors.grey,
        onPressed: () {
          _form.currentState.save();
        },
      ),
    ],
  ),
)

実行結果

解説

見てお分かりのとおり、validatorに
現在の入力値を引数にとり、エラー文言を返却するFunction
を設定します。

エラーがなければnullを返します。

その他バリデーション

数値かどうか確かめたい

// 整数
if (int.tryParse(value) == null) {
  return 'Input a valid number.';
}

// 小数値
if (double.tryParse(value) == null) {
  return 'Input a valid number.';
}

指定した文字列で始まる|終わるか確かめたい

if (!value.startsWith('http')) {
  return 'Input a valid url.';
}

if (!value.endsWith('.png')) {
  return 'Input a png format url';
}

この他にも多種多様なプロパティが指定出来るので試してみてください。

誰かのお役に立てば。

27
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
17

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?