-
Notifications
You must be signed in to change notification settings - Fork 8
예외 핸들링 예외 표시 통일
전종현 edited this page Jun 9, 2024
·
3 revisions
-
예외 처리 방식을 각 도메인 마다 통일 해야 할 필요가 있었다.
-
프레젠테이션 계층에서도 데이터 계층에서 사용한 외부 의존성이 예외를 통해 그대로 전달되었다.
ex) Firebase 패키지 의존성 -
} on FirebaseException catch (e) {
-
스낵바를 통해 예외를 표시하는 것은 모든 화면에서 동일하게 존재하는 기능으로 중복되는 코드가 발생하였다.
-
데이터 계층에서 발생한 예외를 잡아서 핸들링 하는 곳이 View 또는 ViewModel로 일관성이 없었다.
-
사용자에게 알려야 할 오류 일 경우, 발생한 곳에서 UI 단으로 throw하여 처리 할 필요가 있다고 판단했다.
-
예외 처리 통일을 위해, DataSource에서 잡은(
catch
) 예외를 커스텀 예외(ExceptionCode
)로 변경하여 던지도록(throw
) 처리하였다.class RemoteFileDataSourceImpl implements RemoteFileDataSource { final FirebaseStorage _firebaseStorage; final FirebaseAuthService _firebaseAuthService; ... @override Future<List<String>> saveImage( {required File croppedImage, required File compressedImage}) async { try { ... // 이미지 요청 로직 } catch (e) { throw switch (e) { // 발생한 예외를 타입에 따라 직접 구현한 ExceptionCode 클래스로 매핑 FirebaseException _ => ExceptionCode.internalServerException, DioException _ => ExceptionCode.networkException, _ => e, }; } } }
-
유저에게 표시되어야 할 예외 처리를 통일하기 위해, Base 추상 클래스를 작성하여 각 뷰 모델(BaseChangeNotifer)과 뷰(BaseState)에 일괄 적용하였다.
-
뷰모델에서(BaseChangeNotifer) 유스케이스 요청에 대한 예외를 잡아서, 예외 상태를 나타내는
exceptionHolder
에 전달하도록 하였다.abstract class BaseChangeNotifier extends ChangeNotifier { // Stream // final _exceptionController = StreamController<ExceptionStatus>(); ExceptionStatus? _exceptionHolder; ExceptionStatus? get exceptionHolder => _exceptionHolder; // 뷰에서 예외 처리 후, 상태 초기화 void exceptionHandled() { _exceptionHolder = null; } // Exception -> Stream 전송 void notifyException({required Object? exception}) { _exceptionHolder = exceptionToExceptionStatus(exception: exception); } // Exception -> ExceptionStatus 매핑 ExceptionStatus exceptionToExceptionStatus({required Object? exception}) { return switch (exception) { ExceptionCode _ => ExceptionStatus(message: (exception.errorMessage)), ... _ => ExceptionStatus(message: ('알 수 없는 오류가 발생하였습니다'), exceptionAlertType: ExceptionAlertType.snackBar), }; } // 구현체에서 사용 예시 /// HomeScreenViewModel 배경 이미지 주소 가져오기 Future<void> _getBackgroundImagePath() async { try { final weatherBackgroundImageList = await getBackgroundImageListUseCase.execute(); if (weatherBackgroundImageList.isEmpty) { _weatherBackgroundImage = ImagePath.homeBackgroundSunny; } WeatherBackgroundImage image = weatherBackgroundImageList.firstWhere( (element) => element.code == WeatherCode.fromValue(currentWeather!.code).value); _weatherBackgroundImage = image.imagePath; } catch (e) { // 예외 상태(exceptionHolder)로 전달 notifyException(exception: e); } }
-
뷰(BaseState)에서 바인딩하고 있던
exceptionHolder
의 값이 변경되면, 에러 메세지를 스낵바로 표시하도록 처리하였다.abstract class BaseState<S extends StatefulWidget, V extends BaseChangeNotifier> extends State<S> { @override void didChangeDependencies() { super.didChangeDependencies(); final ExceptionStatus? exceptionStatus = context.read<V>().exceptionHolder; if (exceptionStatus == null) return; context.read<V>().exceptionHandled(); Future.microtask(() { // 예외 상태(exceptionHolder)를 스낵바로 표시 ExceptionAlertHandler.showAlert( context: context, message: exceptionStatus.message, ); }); } }
- 특수 상황에 대한 처리가 예외로 바뀌면서 메서드의 동작이 명확해지고 코드의 가독성이 좋아졌다.
- 추상 클래스 구현을 통해, 뷰에서 예외 상태를 표시하는 보일러 플레이트 코드가 사라졌다.
- 중복되는 코드가 사라졌다.
- exception 핸들링(
try-catch-finally
) 방식에 대해 깊게 고민해 볼 수 있는 계기가 되었다. - 데이터 소스 단에서 발생 가능한 다양한 예외 케이스와 그 중에서 유저에게 알려야 할 예외는 무엇인지에 대해 고민하게 되었다.