🏸
sealed class で例外処理を作ってみた
対象者
- Dart3のシールドクラスに興味ある人.
- 何が作れるか探求したい人.
やること/やらないこと
- 思いつきで作ったみたので、とりあえず認証機能を実装してみた.
- sealed classをすごく追求はしなかった.
プロジェクトの説明
Dart3から追加されたsealed class
クラスを使って何か作ってみたかった。認証機能の例外処理が発生したときに、エラーによって異なるSnackBarを出す処理を考えてみた。普段書いてるコードよりも長くなってしまって、いるのかな〜という感じでしたが...
sealed class
ってなんなのか?、Kotlinでもあるんですけど、enumのような使い方ができるクラスです。私は、エラー処理をするswith文を書いた関数に取り入れてみました。
これがシールドクラス
// インスタン化できないシールドクラスを定義します。
sealed class FirebaseAuthSealed {}
// 継承したクラスを定義します。
class EmailInvalid extends FirebaseAuthSealed {
EmailInvalid(this.message);
final String message;
}
class AccountDisabled extends FirebaseAuthSealed {
AccountDisabled(this.message);
final String message;
}
class AccountNotFound extends FirebaseAuthSealed {
AccountNotFound(this.message);
final String message;
}
class PasswordsDoNotMatch extends FirebaseAuthSealed {
PasswordsDoNotMatch(this.message);
final String message;
}
class DefaultError extends FirebaseAuthSealed {
DefaultError(this.message);
final String message;
}
// FirebaseAuthSealedは、FirebaseAuthの例外を列挙型で表現したものです。
FirebaseAuthSealed mapErrorCodeToEnum(String code) {
switch (code) {
case 'invalid-email':
return EmailInvalid('メールアドレスが無効です。');
case 'user-disabled':
return AccountDisabled('このアカウントは無効になっています。');
case 'user-not-found':
return AccountNotFound('アカウントが見つかりません。');
case 'wrong-password':
return PasswordsDoNotMatch('パスワードが一致しません。');
default:
return DefaultError('エラーが発生しました。');
}
}
// FirebaseAuthSealedの例外を、ユーザーに見せるメッセージに変換します。
String describe(FirebaseAuthSealed exception) {
switch (exception.runtimeType) {
case EmailInvalid:
return (exception as EmailInvalid).message;
case AccountDisabled:
return (exception as AccountDisabled).message;
case AccountNotFound:
return (exception as AccountNotFound).message;
case PasswordsDoNotMatch:
return (exception as PasswordsDoNotMatch).message;
case DefaultError:
default:
return (exception as DefaultError).message;
}
}
今回は、ログインだけしか使っていないが、認証に必要なメソッドを定義したクラスを作ってみました。メールアドレスが一致していなければ、メールアドレスのエラーを出し、パスワードが一致していなければパスワードのエラーを出します。
認証のクラス
import 'package:auth_seald/auth_sealed.dart';
import 'package:firebase_auth/firebase_auth.dart';
// メールアドレスとパスワードでの認証を行うサービスで、sealedクラスで例外を表現します。
class AuthService {
final auth = FirebaseAuth.instance;
Future<UserCredential> signUp(String email, String password) async {
try {
final result = await auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
throw describe(mapErrorCodeToEnum(e.code));
} catch (e) {
throw 'エラーが発生しました。';
}
}
Future<UserCredential> signIn(String email, String password) async {
try {
final result = await auth.signInWithEmailAndPassword(
email: email,
password: password,
);
return result;
} on FirebaseAuthException catch (e) {
throw describe(mapErrorCodeToEnum(e.code));
} catch (e) {
throw 'エラーが発生しました。';
}
}
// パスワードのリセット
Future<void> sendPasswordResetEmail(String email) async {
try {
await auth.sendPasswordResetEmail(email: email);
} on FirebaseAuthException catch (e) {
throw describe(mapErrorCodeToEnum(e.code));
} catch (e) {
throw 'エラーが発生しました。';
}
}
}
こちらがアプリのログインとログイン後のデモをするページです。
main.dart
import 'package:auth_seald/auth_service.dart';
import 'package:auth_seald/firebase_options.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const SignInPage(),
);
}
}
class SignInPage extends StatelessWidget {
const SignInPage({super.key});
Widget build(BuildContext context) {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
return Scaffold(
appBar: AppBar(
title: const Text('ログイン'),
),
body: Center(
child: Column(
children: [
const SizedBox(height: 16),
const Text('メールアドレス'),
const SizedBox(height: 8),
TextField(
controller: _emailController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'メールアドレスを入力してください',
),
),
const SizedBox(height: 16),
const Text('パスワード'),
const SizedBox(height: 8),
TextField(
controller: _passwordController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'パスワードを入力してください',
),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
try {
await AuthService().signIn(
_emailController.text,
_passwordController.text,
);
if (context.mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const HomePage(),
),
);
}
} catch (e) {
if(context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(e.toString()),
),
);
}
}
},
child: const Text('ログイン'),
),
],
),
),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ホーム'),
),
body: Center(
child: Column(
children: [
const SizedBox(height: 16),
const Text('ホーム'),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
await FirebaseAuth.instance.signOut();
if (context.mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => const SignInPage(),
),
);
}
},
child: const Text('ログアウト'),
),
],
),
),
);
}
}
こちらがデモです。入力に成功するとログインできます。
感想
今日は、sealed class
について探求して認証機能で使えないか試してみました。他にも便利な使い道ないか探求中です。Dart3ってあまり情報ないので、色々試してみたいです。
Discussion