FlutterでFirebaseAuthのエラーハンドリング

#背景
FlutterでFirebaseAuthを使ってログインやアカウント作成をするときに、パスワードの間違いメールアドレスの間違いなどユーザーにエラー内容を通知したいのですが、FlutterのFirebaseAuthのエラーコードがわからなかったので、僕がやっているエラーハンドリングの方法をまとめておきます。

iOSは↓これみたいなのですが、Flutterはどうしたら良いのか、、、
[Firebase iOS Auth エラーの処理](https://firebase.google.com/docs/auth/ios/errors?hl=ja)
もし知っている方いれば教えて下さい！笑

#FirebaseAuthから発生する例外をenumに定義

まずはFirebaseAuthからの処理結果をenumに定義します。
きっと他にたくさんあると思いますが、よく使いそうなエラー状態を列挙しています。

```dart
enum FirebaseAuthResultStatus {
  Successful,
  EmailAlreadyExists,
  WrongPassword,
  InvalidEmail,
  UserNotFound,
  UserDisabled,
  OperationNotAllowed,
  TooManyRequests,
  Undefined,
}
```

#FirebaseAuthExceptionクラス

FlutterでFirebaseAuthの例外はFirebaseAuthExceptionクラスで渡されます。

FirebaseAuthExceptionの中身はこのようになっていました。
メールアドレスなどのユーザー情報も渡されるみたいなので、必要であれば適宜使っていきます。

```firebase_auth_exception.dart
class FirebaseAuthException extends FirebaseException implements Exception {
  @protected
  FirebaseAuthException(
      {@required this.message,
      this.code,
      this.email,
      this.credential,
      this.phoneNumber,
      this.tenantId})
      : super(plugin: 'firebase_auth', message: message, code: code);

  /// Unique error code
  final String code;

  /// Complete error message.
  final String message;

  /// The email of the user's account used for sign-in/linking.
  final String email;

  /// The [AuthCredential] that can be used to resolve the error.
  final AuthCredential credential;

  /// The phone number of the user's account used for sign-in/linking.
  final String phoneNumber;

  /// The tenant ID being used for sign-in/linking.
  final String tenantId;
}
```

#FirebaseAuthのエラーコードを取得

FirebaseAuthExceptionで渡されたエラーをcodeを元に、先程enumで宣言した型を返す関数を作成します。

あとでFirebaseAuthのエラーハンドリング用ののclassを作って使うのでstaticにしておきます。

```dart
static FirebaseAuthResultStatus handleException(FirebaseAuthException e) {
  FirebaseAuthResultStatus result;
  switch (e.code) {
    case 'invalid-email':
      result = FirebaseAuthResultStatus.InvalidEmail;
      break;

    case 'wrong-password':
      result = FirebaseAuthResultStatus.WrongPassword;
      break;

    case 'user-not-found':
      result = FirebaseAuthResultStatus.UserNotFound;
      break;

    case 'user-disabled':
      result = FirebaseAuthResultStatus.UserDisabled;
      break;

    case 'too-many-requests':
      result = FirebaseAuthResultStatus.TooManyRequests;
      break;

    case 'operation-not-allowed':
      result = FirebaseAuthResultStatus.OperationNotAllowed;
      break;

    case 'email-already-in-use':
      result = FirebaseAuthResultStatus.EmailAlreadyExists;
      break;

    default:
      result = FirebaseAuthResultStatus.Undefined;
      break;
  }
  return result;
}
```

#エラーメッセージを作成

FirebaseAuthResultStatusによって、エラーメッセージを返す関数を作成しておきます。
このエラーメッセージをshowDialogなどでユーザーに通知するので、好きなように書き換えて使ってください。

```dart
static String exceptionMessage(FirebaseAuthResultStatus result) {
  String message = '';
  switch (result) {
    case FirebaseAuthResultStatus.InvalidEmail:
      message = 'メールアドレスが間違っています。';
      break;

    case FirebaseAuthResultStatus.WrongPassword:
      message = 'パスワードが間違っています。';
      break;

    case FirebaseAuthResultStatus.UserNotFound:
      message = 'このアカウントは存在しません。';
      break;

    case FirebaseAuthResultStatus.UserDisabled:
      message = 'このメールアドレスは無効になっています。';
      break;

    case FirebaseAuthResultStatus.TooManyRequests:
      message = '回線が混雑しています。もう一度試してみてください。';
      break;

    case FirebaseAuthResultStatus.OperationNotAllowed:
      message = 'メールアドレスとパスワードでのログインは有効になっていません。';
      break;

    case FirebaseAuthResultStatus.EmailAlreadyExists:
      message = 'このメールアドレスはすでに登録されています。';
      break;

    default:
      message = '予期せぬエラーが発生しました。';
      break;
  }
  return message;
}
```

#FirebaseAuthの例外を扱うHandlerクラスを作成

上で作ったstatic関数をHandlerクラスに入れます。

```dart
class FirebaseAuthExceptionHandler {

  // 上で作った関数
  static FirebaseAuthResultStatus handleException(FirebaseAuthException e) {}

  // 上で作った関数
  static String exceptionMessage(FirebaseAuthResultsStatus result) {}
}
```

#ログインのロジック部分の関数


今回は例としてログイン時の処理で使い方を書いていきます。

emailとpasswordを引数でFirebaseAuthResultStatusを返す関数を作成します。

```dart
Future<FirebaseAuthResultStatus> signIn({String email, String password}) async {
  FirebaseAuthResultStatus result;
  try {
    final UserCredential userCredential = await FirebaseAuth.instance
        .signInWithEmailAndPassword(email: email, password: password);

    if (userCredential.user == null) {
      // ユーザーが取得できなかったとき
      result = FirebaseAuthResultStatus.Undefined;
    } else {
      // ログイン成功時
      result = FirebaseAuthResultStatus.Successful;
    }
  } catch (error) {
    // エラー時
    result = FirebaseAuthExceptionHandler.handleException(error);
  }
  return result;
}
```

#ログイン処理の実装

ログイン処理の実装です。
先程作ったsignIn関数の戻り値でログインの成功、失敗を判別します。
失敗時にエラーメッセージを取得して、何かでユーザーに通知しましょう。
今回は_showErrorDialogでダイアログ表示をするようにしました。

```dart
void login() async {
  final FirebaseAuthResultStatus result = await signIn(
    email: _email,
    password: _password,
  )

  if (result == FirebaseAuthResultStatus.Successful {
    // ログイン成功時の処理
  } else {
    // ログイン失敗時の処理
    final errorMessage = FirebaseAuthExceptionHandler.exceptionMessage(result);
    
    // エラー情報をユーザーに何かで通知
    _showErrorDialog(context, errorMessage);
  }
}
```

#エラーダイアログの作成

ただエラーメッセージを表示してOKボタンでpopするだけのエラーダイアログ関数です。

```dart
void _showErrorDialog(BuildContext context, String message){
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (BuildContext dialogContext) {
      return AlertDialog(
        title: Text(message),
        actions: <Widget>[
          FlatButton(
            child: Text('OK'),
            onPressed: () {
              Navigator.pop(dialogContext);
            },
          ),
        ],
      );
    },
  );
}
```

#参考にした記事

基本的にこの記事を参考に書いたのですが、エラーコードが変わっている(?)っぽいので、そこだけ修正しました。
[Firebase Auth Exceptions handling | Flutter](https://www.codementor.io/@haroonkhan9426/firebase-auth-exceptions-handling-flutter-16x0gaad2u)

#さいごに

もしFlutter×Firebaseを最近やり始めてエラーハンドリングの仕方に迷ってる人がいれば、参考にしてください！
もっと良い方法やアドバイスなどあれば教えて下さい！