Flutterでいい感じのログインフォームを実装する


こんにちは!

はじめまして!福岡でフロントエンドエンジニアをしているなつめです!
今回はFlutterですぐに使えるログインフォームについて書きました。
コメント・スキ・いいね!・バッジ・厳しいご意見など、なんでももらえると嬉しいです!どしどしお待ちしております!

対象読者

  • サクッとログインフォームを実装したい人
  • でも最低限のUIは欲しい人
  • テンプレっぽいログインフォームを探している人

こんなログインフォームを実装します

完成形はこんな感じ
通常時のUI
通常時

バリデーション時のUI
バリデーション時

全体のコードは最後に記述しています!
では行きましょう!

1.textFormFieldを使う

Flutterにはバリデーションを付けることができるTextFormFieldというものがあるので、今回はこれを使っていきます。

https://api.flutter.dev/flutter/material/TextFormField-class.html

書き方

例:メールアドレスのフォームの場合

TextFormField(
  autovalidateMode: AutovalidateMode.onUserInteraction,
  validator: ValidateText.email,
  onChanged: (text) {
  //フォームに入力された時の処理
  },
),

ポイント

1.ユーザーが入力した時にバリデーションを行いたいのでAutovalidateModeをonUserInteractionに設定
2.validatorにバリデーションするための関数を設定(後述します)

2.パスワードの表示の切り替え

書き方

TextFormField(
  decoration: const InputDecoration(
     //TextFormFieldの右側にアイコンを表示
     suffixIcon: IconButton(
        icon:  Icon(isVisible? Icons.visibility : Icons.visibility_off) ,
        onPressed: () {
	  //アイコンが押された時に表示・非表示をのstateを切り替える
	 setState(!isVisible);
        },
      ),
  ),
  //テキストの表示を制御
  obscureText: !isVisible,
),

ポイント

1.suffixIconにパスワード表示・非表示を切り替えるアイコンを設定
2.アイコンが押された時にsetStateでisVisibleの値を反転し、表示・非表示を切り替える
3.テキストの表示を制御するobscureTextにisVisibleを渡してテキストの表示・非表示も切り替える

3.バリデーション

書き方

例:パスワードのバリデーションの場合

class ValidateText {
  static String? password(String? value){
    //TextFormFieldに値が入されたときだけvalidateする
    if(value != null){
      String pattern = r'^[a-zA-Z0-9]{6,}$';
      RegExp regExp = RegExp(pattern);
      if(!regExp.hasMatch(value)){
      //正規表現の条件にマッチしていない時だけエラー文を返す
        return '6文字以上の英数字を入力してください';
      }
    }
  }
}

ポイント

1.入力値が正規表現とマッチしているかチェックする関数を別に定義
2.正規表現で入力値が正規表現とマッチしているかチェック

ちなみにr'正規表現'のrはエスケープ()なしで文字列で記号を扱いたいときに付ける接頭辞らしいです。

RegExpについては正規表現による文字列チェックを行うための呪文のようなものだと思ってだいじょうぶです!(たぶん)

詳しく知りたい方は下記を参照

https://qiita.com/kawarabasami/items/11de6023f411d419141e
https://api.flutter.dev/flutter/dart-core/RegExp-class.html

以下サンプルコード

import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {

  
  _State createState() => _State();
}

class _State extends State<LoginPage> {
  String? email;
  String? password;
  bool isVisible = false;

  void toggleShowPassword () {
    setState(() {
      isVisible = !isVisible;
    });
  }

  void setEmail (String email) {
    this.email = email;
  }
  void setPassword (String password) {
    this.password = password;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title:  const Text("ログイン"),
      ),
      body: Center(
          child:Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    children: [
                      TextFormField(
                        autovalidateMode:
                        AutovalidateMode.onUserInteraction,
                        validator: ValidateText.email,
                        decoration: const  InputDecoration(
                            filled: true,
                            hintText: 'Email'
                        ),
                        onChanged: (text) {
                          setEmail(text);
                        },
                      ),
                      const SizedBox(
                        height: 16,
                      ),
                      TextFormField(
                        autovalidateMode:
                        AutovalidateMode.onUserInteraction,
                        validator: ValidateText.password,
                        decoration: InputDecoration(
                            suffixIcon: IconButton(
                              icon:  Icon(isVisible? Icons.visibility : Icons.visibility_off) ,
                              onPressed: () {
                                toggleShowPassword();
                              },
                            ),
                            filled: true,
                            hintText: 'Password'),
                        onChanged: (text) {
                          setPassword(text);
                        },
                        obscureText: !isVisible,
                      ),
                      const SizedBox(
                        height: 16,
                      ),
                      ElevatedButton(
                        onPressed:  () {
                          //ここにログイン処理を書く
                        },
                        child: const Text("ログイン"),
                      ),
                    ],
                  )
              ),
            ],
          ),
      ),
    );
  }
}



class ValidateText {
  static String? password(String? value){
    if(value != null){
      String pattern = r'^[a-zA-Z0-9]{6,}$';
      RegExp regExp = RegExp(pattern);
      if(!regExp.hasMatch(value)){
        return '6文字以上の英数字を入力してください';
      }
    }
  }

  static String? email(String? value){
    if(value != null){
      String pattern = r'^[0-9a-z_./?-]+@([0-9a-z-]+\.)+[0-9a-z-]+$';
      RegExp regExp = RegExp(pattern);
      if(!regExp.hasMatch(value)){
        return '正しいメールアドレスを入力してください';
      }
    }
  }
}

参考記事

https://api.flutter.dev/flutter/material/TextFormField-class.html
https://dev.classmethod.jp/articles/flutter-inline-validation/
https://qiita.com/kawarabasami/items/11de6023f411d419141e
https://api.flutter.dev/flutter/dart-core/RegExp-class.html