4
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

FlutterとAWSで始めるサービス開発 (4)AWS Cognitoへの新規アカウント登録

はじめに

前回の「(3)AWS Cognito連携」では、Cognitoユーザープールを準備し、Flutterアプリで使うための準備を行いました。今回は、それを使って、新規アカウント登録機能を実装したいと思います。

新規アカウント登録の流れ

  1. メールアドレスとパスワードをユーザープールに送信
  2. アカウントが、ステータス「UNCONFIRMED」、E メール確認済み「false」で作成される
  3. 入力したメールアドレス宛に確認コードが送信される
  4. メールアドレスと確認コードをユーザープールに送信
  5. アカウントのステータスが「CONFIRMED」、E メール確認済みが「_true」_に変化しアカウントが使えるようになる。

画面遷移

エラー処理等々を考えると問題があるのだがフローを追いやすくするため、以下のような単純な画面遷移とします。ログイン画面('/')から新規登録画面(RegisterUSer'')へ遷移し、レジスターコード入力画面('ConfirmRegistration')へ遷移し、処理が完了したらログイン画面へ戻るという流れになります。

画面遷移 Step1.png

コード

今回の実装したlib/main.dartファイルをすべて掲載します。cognitoのプールIDやクライアントIDは伏字にしていますので実際に動かす場合にはそこを変更してください。

import 'package:flutter/material.dart';
import 'package:amazon_cognito_identity_dart_2/cognito.dart';

final userPool = new CognitoUserPool(
    'ap-northeast-1_XoqXlafQm', '1e7ri3io3j0ds336077gelhhub');

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'login sample app',
      routes: <String, WidgetBuilder>{
        '/': (_) => new MyHomePage(),
        '/RegisterUser': (_) => new RegisterUserPage(),
        '/ConfirmRegistration': (_) => new ConfirmRegistration(null),
      },
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('ログイン'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
              child: Text('新しいアカウントの作成'),
              color: Colors.indigo,
              textColor: Colors.white,
              shape: StadiumBorder(),
              onPressed: () => Navigator.of(context).pushNamed('/RegisterUser'),
            ),
          ],
        ),
      ),
    );
  }
}

class RegisterUserPage extends StatelessWidget {
  final _mailAddressController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('アカウント作成'),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'test@examle.com',
                    labelText: 'メールアドレス',
                  ),
                  controller: _mailAddressController,
                ),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'password',
                    labelText: 'パスワード',
                  ),
                  obscureText: true,
                  controller: _passwordController,
                ),
              ),
              Container(
                alignment: Alignment.centerRight,
                padding: const EdgeInsets.all(8.0),
                child: RaisedButton(
                  child: Text('登録'),
                  color: Colors.indigo,
                  shape: StadiumBorder(),
                  textColor: Colors.white,
                  onPressed: () => _signUp(context),
                ),
              ),
            ]),
      ),
    );
  }

  void _signUp(BuildContext context) async {
    try {
      CognitoUserPoolData userPoolData = await userPool.signUp(
          _mailAddressController.text, _passwordController.text);
      Navigator.push(
        context,
        new MaterialPageRoute<Null>(
          settings: const RouteSettings(name: "/ConfirmRegistration"),
          builder: (BuildContext context) => ConfirmRegistration(userPoolData),
        ),
      );
    } on CognitoClientException catch (e) {
      await showDialog<int>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('エラー'),
            content: Text(e.message),
            actions: <Widget>[
              FlatButton(
                child: Text('OK'),
                onPressed: () => Navigator.of(context).pop(1),
              ),
            ],
          );
        },
      );
    }
  }
}

class ConfirmRegistration extends StatelessWidget {
  final _registrationController = TextEditingController();
  final CognitoUserPoolData _userPoolData;

  ConfirmRegistration(this._userPoolData);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('レジストレーションキー確認'),
      ),
      body: Center(
        child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: Text(_userPoolData.user.username),
              ),
              Padding(
                padding: const EdgeInsets.all(8.0),
                child: TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(),
                    hintText: 'レジストレーションコード',
                    labelText: 'レジストレーションコード',
                  ),
                  obscureText: true,
                  controller: _registrationController,
                ),
              ),
              Container(
                alignment: Alignment.centerRight,
                padding: const EdgeInsets.only(right: 8.0),
                child: RaisedButton(
                  child: Text('確認'),
                  color: Colors.indigo,
                  shape: StadiumBorder(),
                  textColor: Colors.white,
                  onPressed: () => _confirmRegistration(context),
                ),
              ),
            ]),
      ),
    );
  }

  void _confirmRegistration(BuildContext context) async {
    try {
      await _userPoolData.user
          .confirmRegistration(_registrationController.text);
      await showDialog<int>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('登録完了'),
            content: Text('ユーザーの登録が完了しました。'),
            actions: <Widget>[
              FlatButton(
                child: Text('OK'),
                onPressed: () =>
                    Navigator.of(context).popUntil(ModalRoute.withName('/')),
              ),
            ],
        );
        },
      );
    } on CognitoClientException catch (e) {
      await showDialog<int>(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            title: Text('エラー'),
            content: Text(e.message),
            actions: <Widget>[
              FlatButton(
                child: Text('OK'),
                onPressed: () => Navigator.of(context).pop(1),
              ),
            ],
          );
        },
      );
    }
  }
}

MyApp

MaterialAppのroutesパラメータで指定しているか気がFlutterでの画面遷移の肝となる実装になります。ここで画面に名前をつけて事前に登録しておきます。この登録した名前をベースに画面の移動を指示することで画面遷移を制御します。本実装では3種類の画面を名前をつけて登録しており、'/'で登録しているのが初期画面、'/RegisterUser'で登録しているのがユーザー新規登録画面、'/ConfirmRegistration'で登録しているのがレジストレーションコードの確認画面になります。

      routes: <String, WidgetBuilder>{
        '/': (_) => new MyHomePage(),
        '/RegisterUser': (_) => new RegisterUserPage(),
        '/ConfirmRegistration': (_) => new ConfirmRegistration(null),
      },

MyHomePage

初期に表示されるログイン画面です。画面は以下のようになっています。

画面1.png

新しいアカウントの作成を開始するためのボタンを定義しており、ボタンを押下したら、Navigator.of(context).pushNamed('/RegisterUser'),を実行します。

            RaisedButton(
              child: Text('新しいアカウントの作成'),
              color: Colors.indigo,
              textColor: Colors.white,
              shape: StadiumBorder(),
              onPressed: () => Navigator.of(context).pushNamed('/RegisterUser'),
            ),

これが画面を遷移するためのコードで、'/RegisterUser'と名前の付いた画面をPushするという指示です。Flutterの画面管理はStackのモデルになっており、Stackの最上位にある画面が表示されるようになっています。つまり、Stackに対してPushするとStackに積まれ画面が切り替わります。また、StackからPopすると最上位の画面が取り除かれ、ひとつ前の画面に戻るといった動きになります。絵で表すと以下の通りです。

stack.png

RegisterUserPage

アカウントの新規作成ページです。画面は以下のようになっています。

画面2.png

登録するためのメールアドレスとパスワードを入力するためのテキストフィールドと登録処理を実行するためのボタンを定義しています。
ボタンを押下すると_signupメソッドを実行します。このメソッドではまず、ユーザープールに入力されたメールアドレスとパスワードを送りアカウント作成します。

      CognitoUserPoolData userPoolData = await userPool.signUp(
          _mailAddressController.text, _passwordController.text);

上記API呼び出しが成功したら、APIの戻り値を渡し次の画面へ遷移させます。以下は、引数でデータを渡す場合の定型実装になります。

      Navigator.push(
        context,
        new MaterialPageRoute<Null>(
          settings: const RouteSettings(name: "/ConfirmRegistration"),
          builder: (BuildContext context) => ConfirmRegistration(userPoolData),
        ),
      );

ConfirmRegistration

確認コードの入力画面です。画面は以下のようになっています。

画面3.png

前画面で登録したメールアドレスの表示、確認コードを入力するためのテキストフィールドと確認を実行するためのボタンを定義しています。
ボタンを押下すると_confirmRegistrationメソッドを実行します。このメソッドではまず、ユーザープールに確認コードを送信します。
以下のように、前画面から渡されたアカウント作成結果のオブジェクトのconfirmRegistrationメソッドを使い、入力された確認コードを送信します。
dart
await _userPoolData.user
.confirmRegistration(_registrationController.text);

APIの呼び出しが成功したら、以下のダイアログを表示します。

画面4.png

ダイアログでOKを押下したらNavigator.of(context).popUntil(ModalRoute.withName('/'))を実行します。これは画面のStackから名前が'/'の画面までpopするという指示になります。以下のように一気にログイン画面まで戻れます。

stack popuntill.png

まとめ

ユーザープールへのアカウント登録の流れを確認できました。また、Flutterの画面遷移のごくごく基本的なところも見えてきたと思います。
次回「(5)AWS Cognitoでログイン」では、今回作ったアカウントを使ってログインする処理を見ていきたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
4
Help us understand the problem. What are the problem?