반응형
들어가며
Flutter로 앱을 개발하다 보면 앱 시작 시 여러 데이터를 동시에 불러와야 하는 상황이 자주 발생한다. 특히 로컬저장소, SQLite, API 등 여러 데이터소스를 활용할때는 더욱 그렇다. 오늘은 앱을 개발하면서 마주친 비동기 초기화 문제와 그 해결 과정을 공유하려 한다.
문제상황
최근 개발중인 사진 앱에서는 시작 시 두가지 데이터를 불러와야 했다.
- 로컬 파일 시스템의 파일 정보
- SQLite 데이터베이스에 저장된 ID 정보
처음에는 단순하게 다음과 같이 구현하였다.
class HomeScreen extends ConsumerStatefulWidget {
@override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen> {
@override
void initState() {
super.initState();
fetchAlbums().then((result) {
ref.read(localAlbumProvider.notifier).state = result;
});
fetchMyAlbum().then((data) {
final albumIds = data.map((item) => item.albumId).toList();
ref.read(albumStateProvider.notifier).setAlbums(albumIds);
});
}
// ...
}
그러나 이 방식은 몇가지 문제가 있었다.
발생한 문제점들
- 데이터 동기화 문제
- 두 데이터 로딩이 독립적으로 실행되어 완료 시점이 달랐음
- 데이터가 로딩되지 않은 상태에서 화면이 그려져 데이터가 없는 상태로 진행되었음.
- 빈 화면 노출
- 데이터 로딩 전에 화면이 렌더링되어 사용자가 빈 화면을 보게 됨
- 좋지 않은 사용자 경험을 제공
- 타입 안정성 부족
- 암시적 타입 변환으로 인한 잠재적 런타임 에러 가능성이 있었음.
그래서 이 문제를 해결하기 위해 아래와 같이 코드를 수정했다.
// 로딩 상태 관리를 위한 Provider
final initLoadingProvider = StateProvider<bool>((ref) => true);
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key});
@override
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends ConsumerState<HomeScreen> {
@override
void initState() {
super.initState();
_initData();
}
Future<void> _initData() async {
try {
// 병렬로 데이터 로딩
final results = await Future.wait<dynamic>([
fetchAlbums(),
fetchMyAlbum(),
]);
// 명시적 타입 캐스팅
final albums = results[0] as List<AssetPathEntity>;
final myAlbums = results[1] as List<MyAlbum>;
// Provider 상태 업데이트
ref.read(localAlbumProvider.notifier).state = albums;
final albumIds = myAlbums.map((item) => item.albumId).toList();
ref.read(albumStateProvider.notifier).setAlbums(albumIds);
// 로딩 완료 처리
ref.read(initLoadingProvider.notifier).state = false;
} catch (e) {
print('초기화 중 에러 발생: $e');
// 에러 처리 로직
}
}
@override
Widget build(BuildContext context) {
final isLoading = ref.watch(initLoadingProvider);
if (isLoading) {
return const Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
return // 메인 화면 위젯
}
}
개선된 점
- 데이터 동기화: 두 데이터 소스의 로딩이 완료되기 까지 loading 을 보여줌
- 사용자 경험: 로딩중에 로딩 인디케이터 표시
- 에러처리: try - catch 블록으로 에러 상황 관리
- 타입 안정성: 명시적인 타입 지정으로 런타임 에러 방지
- 성능: 병렬 데이터 로딩으로 초기화 시간 단축
이로 인해 배운점은 다음과 같다.
- Riverpod에서 비동기 초기화를 다룰 때는 loading 상태 관리가 중요
- Future.wait 를 활용한 병렬 처리로 성능 최적화 가능
- 사용자 경험을 고려한 로딩 상태 표기의 중요성.
마무리하며
이번 경험을 통해 다음에는 여러 api 호출이 필요할때 어떻게 핸들링 할 수 있을지를 알게 되었고, 화면이 랜더링 되기 전어떻게 사전로딩을 하여 시작할지를 배웠다. 이것을 응용해 여기저기서 많이 사용할 수 있을거 같다.
반응형
'공부 > 프로그래밍' 카테고리의 다른 글
[springboot, msa] gateway 서버에 Filter에서 던져진 Exception 핸들링하기 (0) | 2024.12.07 |
---|---|
Flutter 앱 개발 시리즈: SQLite 데이터베이스 다루기 + 버전관리 (1) | 2024.11.28 |
[springboot 3.x, querydsl] QClass 생성 및 경로 설정 (2) | 2023.03.31 |
springboot Test 중에 403, 401 에러가 날때(spring security) (2) | 2023.03.22 |
[Linux] 젠킨스(jenkins) 포트번호 변경 (0) | 2023.03.20 |
댓글