기본적으로 예외는 try-catch를 활용하여 처리한다. catch에서 적절한 에러 객체를 리턴해주는 것이 좋다.
목표
// 데이터 처리의 성공 실패를 담을 객체
abstract class Result<T> {
factory Result.success(T data) = Success;
factory Result.error(Exception e) = Error;
}
class Success<T> implements Result<T> {
final T data;
Success(this.data);
}
class Error<T> implements Result<T> {
final Exception e;
Error(this.e);
}
<Result<ChatUser>> authStateChanges() {
return FirebaseAuth.instance.authStateChanges().map((User? user) {
if (user != null) {
return Result.success(ChatUser(user.email, user.photoURL, user.displayName),
);
}
return Result.error(Exception('로그인 실패'));
});
}
Stream
응답을 Result 클래스로 랩핑하기
예외가 예상되는 지점에서 try-catch 사용하기
Stream을 활용하여 UI 쪽으로 예외를 알린다. 일회성 알림이므로 notifyChange()는 사용 할 수 없다.
final _eventController = StreamController<UiEvent>();
Stream<UiEvent> get eventStream => _eventController.stream;
_eventController.add(UiEvent.showSnackBar(message));
final Result<List<Photo>> results = await useCases.getPhotos(query);
results.when(success: (photos) {
_state = state.copyWith(photos: photos, isLoading: false);
_eventController.add(const UiEvent.endLoading());
notifyListeners();
}, error: (message) {
_eventController.add(UiEvent.showSnackBar(message));
_state = state.copyWith(isLoading: false);
});
class _MainScreenStateState extends State<MainScreen> {
void initState() {
Future.microtask(() {
context.read<MainViewModel>().fetch('iphone');
context.read<MainViewModel>().eventStream.listen((event) {
event.when(showSnackBar: (message) {
final snackBar = SnackBar(content: Text(message));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, endLoading: () {
const snackBar = SnackBar(content: Text('로딩이 완료되었습니다'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
});
});
});
super.initState();
}