FlutterやDartでエラーハンドリングをする際、どのような方針で実装をしていますか?
基本的にはどの言語でも以下のようにtry-catch
でハンドリングを行うと思います。
try {
// 正常処理
} catch(e) {
// 例外処理
}
しかし、上記のような単調なtry-catch
ではいかなるエラーもすべて同様の処理をしなくてはなりません。
つまり、アプリの中で想定しうるエラーと想定外エラーはしっかり線引きをしようというのがこの記事の目的です。
実装
下準備1. エラー情報を格納する抽象クラス
エラーメッセージでよくあるエラーコード・タイトル・メッセージを格納するクラスです
abstract class ErrorCode {
const ErrorCode();
/// エラーコード
String get errorCode;
/// タイトル
String get errorTitle;
/// メッセージ
String get errorMessage;
}
下準備2. 共通のExceptionクラス
このクラスは直接使用しません。あくまで土台となくabstructクラスです
import 'package:custom_exception/exception/error_code.dart';
abstract class CustomException implements Exception {
const CustomException(
this.errorCode, {
this.info,
});
final ErrorCode errorCode;
final dynamic info;
@override
String toString() {
return 'CustomException{errorCode: ${errorCode.errorCode}, title: ${errorCode.errorTitle}, message: ${errorCode.errorMessage}, info: $info}';
}
}
実装1. サーバー共通エラーの定義
下準備で実装したErrorCode
をenum
に実装します。
/// サーバー共通エラーコード
enum ServerCommonErrorCode implements ErrorCode {
systemError(
'ER001',
'システムエラー',
'エラーが発生しました。しばらくしてもう一度お試しください。\n\n何度か試しても改善しない場合はアプリを再起動してください。',
),
// 400エラー
badRequestError(
'ER002',
'リクエストエラー',
'リクエストが不正です。',
),
// 403エラー
forbiddenError(
'ER003',
'アクセス権限エラー',
'アクセス権限がありません。',
),
// 500エラー
internalServerError(
'ER004',
'システムエラー',
'エラーが発生しました。しばらくしてもう一度お試しください。\n\n何度か試しても改善しない場合はアプリを再起動してください。',
),
;
const ServerCommonErrorCode(
this._errorCode,
this._errorTitle,
this._errorMessage,
);
final String _errorCode;
final String _errorTitle;
final String _errorMessage;
@override
String get errorCode => _errorCode;
@override
String get errorTitle => _errorTitle;
@override
String get errorMessage => _errorMessage;
static ServerCommonErrorCode? fromCode(String errorCode) =>
values.firstWhereOrNull((element) => element.errorCode == errorCode);
}
実装2. カスタムExceptionクラスの実装
土台となるCustomException
クラスと上記で実装したServerCommonErrorCode
を使ってカスタムExceptionクラスを実装します。
class ServerCommonError extends CustomException {
const ServerCommonError(
ServerCommonErrorCode errorCode, {
dynamic info,
}) : super(errorCode, info: info);
// factoryでエラーから生成する
factory ServerCommonError.fromCode(String errorCode) {
final errorInfo = ServerCommonErrorCode.fromCode(errorCode);
// 取得に失敗した場合、一律システムエラーとする
if (errorInfo == null) {
throw const ServerCommonError(ServerCommonErrorCode.systemError);
}
return ServerCommonError(errorInfo);
}
}
実際に使ってみよう
さて、実際に上記で実装したServerCommonError
クラスを使用してみます。
今回は同じレイヤーで実装してしまいますが、実際はrepositoryやserviceクラスなどでハンドリングする方針が好ましいかなと思います。
お試して、PokeAPIを用いてAPIリクエストをした場合を想定して実装します。
Future<void> fetchPikachuInfo() async {
try {
final Uri requestUrl =
Uri.parse("https://pokeapi.co/api/v2/pokemon/pikachu");
final response = await http.get(requestUrl);
// ステータスコード別に例外を返す
switch (response.statusCode) {
case 200:
// 200 OK
debugPrint(response.toString());
break;
case 400:
case 403:
// 403 Forbidden
throw const ServerCommonError(ServerCommonErrorCode.forbiddenError);
case 500:
// 500 Internal Server Error
throw const ServerCommonError(
ServerCommonErrorCode.internalServerError);
default:
// その他のステータスコード
throw const ServerCommonError(ServerCommonErrorCode.systemError);
}
} on ServerCommonError catch (errorInfo) {
// サーバー共通エラーの場合、定義したエラー情報を取得できる
debugPrint(errorInfo.errorCode.toString());
debugPrint(errorInfo.info.toString());
} on Exception catch (_, stack) {
debugPrint(stack.toString());
} catch (error) {
debugPrint(error.toString());
}
}
Github