결과처리
| topics | 500-모바일개발 501 Flutter 0_SubTopics/505 상태관리 |
| types | 이론 실습 |
| tags |
결과 처리
API 호출이나 비동기 작업의 결과를 처리하는 패턴이다.
왜 Result 패턴을 쓸까
// 나쁜 예 - try-catch만 사용
try {
final user = await fetchUser();
// 성공 처리
} catch (e) {
// 에러 처리
}
문제점: 에러 타입을 명시적으로 다루기 어렵다. catch 블록에서 모든 에러를 처리해야 한다. 코드가 분산된다.
Result 타입 구현
Sealed Class 활용 (Dart 3.0+)
sealed class Result<T> {}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
}
class Failure<T> extends Result<T> {
final String message;
final Exception? exception;
Failure(this.message, [this.exception]);
}
사용 예시
Future<Result<User>> fetchUser(String id) async {
try {
final response = await api.getUser(id);
return Success(User.fromJson(response));
} on NetworkException catch (e) {
return Failure('네트워크 오류', e);
} on ServerException catch (e) {
return Failure('서버 오류', e);
} catch (e) {
return Failure('알 수 없는 오류');
}
}
UI에서 처리
Widget build(BuildContext context) {
return FutureBuilder<Result<User>>(
future: fetchUser('123'),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
}
return switch (snapshot.data!) {
Success(data: var user) => UserProfile(user: user),
Failure(message: var msg) => ErrorWidget(message: msg),
};
},
);
}
로딩 상태 포함
sealed class AsyncResult<T> {}
class Loading<T> extends AsyncResult<T> {}
class Success<T> extends AsyncResult<T> {
final T data;
Success(this.data);
}
class Failure<T> extends AsyncResult<T> {
final String message;
Failure(this.message);
}
Notifier에서 사용
class UserNotifier extends Notifier<AsyncResult<User>> {
@override
AsyncResult<User> build() => Loading();
Future<void> fetch(String id) async {
state = Loading();
final result = await repository.getUser(id);
state = switch (result) {
Success(data: var user) => Success(user),
Failure(message: var msg) => Failure(msg),
Loading() => Loading(),
};
}
}
Riverpod의 AsyncValue
Riverpod을 사용한다면 직접 만들 필요 없다. AsyncValue가 이미 있다.
final userProvider = FutureProvider<User>((ref) async {
return await repository.getUser();
});
// UI
ref.watch(userProvider).when(
loading: () => CircularProgressIndicator(),
error: (err, stack) => Text('Error: $err'),
data: (user) => UserProfile(user: user),
);
왜 AsyncValue를 권장하냐면: 로딩/에러/데이터 상태가 이미 정의되어 있고,
when,maybeWhen등 편리한 메소드를 제공한다.
Either 패턴 (fpdart)
함수형 프로그래밍 스타일을 선호한다면 fpdart 패키지의 Either를 사용할 수 있다.
import 'package:fpdart/fpdart.dart';
Future<Either<Failure, User>> fetchUser() async {
try {
final user = await api.getUser();
return Right(user);
} catch (e) {
return Left(Failure('에러 발생'));
}
}
// 사용
final result = await fetchUser();
result.fold(
(failure) => print(failure.message),
(user) => print(user.name),
);
언제 뭘 쓸까
| 상황 | 추천 |
|---|---|
| Riverpod 사용 중 | AsyncValue |
| 간단한 Result 필요 | Sealed Class 직접 구현 |
| 함수형 프로그래밍 선호 | fpdart의 Either |
| 복잡한 에러 타입 분기 | Sealed Class + 패턴 매칭 |
관련 문서
- AsyncValue vs AsyncData - AsyncValue 상세
- dart 3.0 - Sealed Class, 패턴 매칭
- riverpod read listen - Riverpod 상태 관리