LoginSignup
4
5

More than 3 years have passed since last update.

FlutterでProviderパッケージ利用して、ログイン画面を作成する

Posted at

Flutterでログイン画面を作成したので、メモしておきます。
状態管理は、公式ドキュメントで利用しているproviderパッケージを利用します。

作成したもの

  • ログイン正常の動作

login_ok.gif

  • ログインエラーの動作

login_error.gif

レポジトリ

ディレクトリ構成

/path/to/directory/lib
|--main.dart
|--repository
|  |--auth_repository.dart
|--ui
|  |--index_page.dart
|  |--login_model.dart
|  |--login_page.dart

パッケージ追加

pubspec.yamlに以下パッケージを追加し、パッケージをインポート(dart pub get)します。

/pubspec.yaml
dependencies:
  flutter:
    sdk: flutter

  provider: ^5.0.0 # <--- 追加
  font_awesome_flutter: ^9.0.0-nullsafety # <--- パスワードマスク用のアイコン利用のため

実装

  • 認証レポジトリ追加

実際は、認証API(Firebase Authenticationなど)などをコールしますが、サンプル実装のため、必ず成功(true)を返します。

lib/repository/auth_repository.dart
class AuthRepository {
  Future<bool> auth() {
    return Future.value(true);
  }
}
  • 状態クラス追加

ChangeNotifierを継承して、状態クラスを作成します。
id, password, showPassword(=パスワード表示・非表示)などのフィールドと、認証や、パスワード表示切り替えなどの処理を持ちます。

lib/ui/login_model.dart
class LoginModel extends ChangeNotifier {
  final AuthRepository repository;
  String id = '';
  String password = '';
  String message = '';
  bool showPassword = false; //パスワードを平文で表示する

  LoginModel(this.repository);

  void setMessage(String value) { //エラーメッセージ設定
    message = value;
    notifyListeners();
  }

  void togglePasswordVisible() { //パスワード表示切り替え
    showPassword = !showPassword;
    notifyListeners();
  }

  String? emptyValidator(String? value) { //必須入力チェック
    if (value == null || value.isEmpty) {
      return '入力してください';
    }
    return null;
  }

  Future<bool> auth() async {
    print("id: $id, password: $password");
    var results = await repository.auth();
    return results;
  }
}
  • 画面(Widget)実装

ChangeNotifierProviderを利用して、状態クラスを子供のWidgetで利用できるようにします。

lib/ui/login_page.dart
class LoginPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => LoginModel(
        AuthRepository(),
      ),
      child: LoginApp(),
    );
  }
}

状態クラスは、以下メソッドを通じて利用します。
* context.read(変更監視なし)
* context.watch(変更監視あり)

lib/ui/login_page.dart
class LoginApp extends StatelessWidget {
  final _formKey = GlobalKey<FormState>(); // 入力フォーム

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(24),
          child: Form(
            key: _formKey,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                TextFormField(
                  decoration: const InputDecoration(
                    labelText: 'UserId',
                    hintText: 'ユーザIDを入力してください',
                  ),
                  validator: context.read<LoginModel>().emptyValidator, // 入力チェック
                  onSaved: (value) => context.read<LoginModel>().id = value!, // save() 時に同期
                ),
                TextFormField(
                  obscureText: !context.watch<LoginModel>().showPassword, // パスワード表示状態を監視(watch)
                  decoration: InputDecoration(
                    labelText: 'Password',
                    hintText: 'パスワードを入力してください',
                    suffixIcon: IconButton(
                      icon: Icon(context.watch<LoginModel>().showPassword // パスワード表示状態を監視(watch)
                          ? FontAwesomeIcons.solidEye
                          : FontAwesomeIcons.solidEyeSlash),
                      onPressed: () =>
                          context.read<LoginModel>().togglePasswordVisible(), // パスワード表示・非表示をトグルする
                    ),
                  ),
                  validator: context.read<LoginModel>().emptyValidator, // 入力チェック
                  onSaved: (value) =>
                      context.read<LoginModel>().password = value!,
                ),
                Container( // エラー文言表示エリア
                  margin: EdgeInsets.fromLTRB(0, 16, 0, 8),
                  child: Text(
                    context.watch<LoginModel>().message,
                    style: TextStyle(
                      fontSize: 16,
                      color: Colors.red,
                    ),
                  ),
                ),
                Container(
                  width: double.infinity,
                  child: Padding(
                    padding: const EdgeInsets.symmetric(vertical: 16),
                    child: ElevatedButton(
                      onPressed: () async { // ログインボタンアクション
                        context.read<LoginModel>().setMessage(''); // エラーメッセージを空に

                        if (_formKey.currentState!.validate()) { // 入力チェック
                          _formKey.currentState!.save(); // 入力チェックOK -> フォームの値を同期する

                          var response =
                              await context.read<LoginModel>().auth();
                          print('auth response = $response');

                          if (response) {
                            Navigator.push( // 画面遷移
                              context,
                              MaterialPageRoute(
                                builder: (context) => IndexPage(),
                              ),
                            );

                            ScaffoldMessenger.of(context).showSnackBar( // SnackBar表示
                              SnackBar(
                                content: Text('ログインしました'),
                              ),
                            );
                          } else {
                            context
                                .read<LoginModel>()
                                .setMessage('パスワードが誤っています'); // エラーメッセージセット
                          }
                        }
                      },
                      child: const Text('ログイン'),
                    ),
                  ),
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

終わりに

状態管理を切り出すことで、Widgetのコードが見通しがよくなりました。
riverpodflutter_hooksあたりも今後試していきたいと思います。

次回は、今回作成した画面を元に、Widgetのテストを記載したいと思います。

参考

4
5
1

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
4
5