728x90
아래는 Flutter 프로젝트에서 Bloc 사용으로 인한 성능 문제를 FutureBuilder와 같은 대체 방법으로 해결한 트러블슈팅 사례입니다. Bloc 패턴을 처음 도입하면서 발생한 이슈와 최적화 과정에서의 선택 이유, 최종 해결 방법을 단계별로 정리해 보았습니다.
트러블슈팅 사례: Flutter Bloc 패턴 사용으로 인한 성능 문제 해결
프로젝트 개요
Flutter로 개발된 쇼핑 애플리케이션의 홈 화면에서 배너, 추천 상품, 인기 상품 데이터를 비동기적으로 불러와 표시하는 기능을 구현하고 있었습니다. 초기 구현에서는 Bloc 패턴을 사용하여 홈 화면의 상태를 관리하고 비동기 데이터 로드를 수행했습니다.
문제 발생
Bloc을 도입한 초기 구현 이후, 홈 화면에서 다음과 같은 문제가 발생했습니다:
- 성능 저하: 앱이 불필요하게 빈번히 리빌드되면서 UI가 느려졌습니다.
- 프레임 누락: 특히 저사양 기기에서 Skipped frames 오류가 지속적으로 발생했습니다. 이는 메인 스레드에서 과도한 작업이 이루어졌기 때문입니다.
- 복잡성 증가: 데이터 상태 관리를 위해 Bloc 패턴을 사용했으나, 간단한 데이터 표시 UI에 불필요하게 복잡한 상태 관리 구조가 도입되었습니다.
문제 원인 분석
- BlocBuilder가 전체 화면을 감싸고 있어, 상태 변경이 발생할 때마다 전체 홈 화면이 리빌드되었습니다.
- 불필요한 상태 관리 도입: 간단한 비동기 데이터 로드만을 필요로 했으나, Bloc 패턴이 오히려 UI의 복잡성을 증가시키고 성능을 저하시켰습니다.
해결 과정
- 필요성 재평가:
- UI에서 단순히 데이터를 불러와 한 번 표시하는 경우, 상태 관리가 필수적이지 않다고 판단했습니다.
- Bloc의 상태 관리를 유지할 필요 없이, FutureBuilder를 통해 단순히 데이터를 로드하고 결과를 표시하는 방식이 더 적합하다고 판단했습니다.
- 구현 변경:
- FutureBuilder를 사용하여 비동기 데이터 로드를 수행했습니다.
- FutureBuilder는 Bloc과 달리 로딩, 성공, 실패 세 가지 상태만을 처리하면 되므로, 코드의 가독성과 유지보수성을 높였습니다.
- 불필요한 상태 변화를 줄이고 FutureBuilder의 빌드 트리거만으로 데이터 표시가 가능해지면서 성능이 즉시 개선되었습니다.
- 최종 코드 구조
- 기존의 Bloc을 제거하고, 홈 화면을 구성하는 위젯들이 FutureBuilder로 데이터를 로드하여 필요한 UI만 업데이트하도록 수정했습니다.
- FutureBuilder의 비동기 결과에 따라 로딩 상태와 에러 상태를 처리하여 필요한 데이터만 로드하고, 데이터 변경이 없는 한 불필요한 리빌드를 방지했습니다.
class Home extends StatelessWidget {
final PageController _pageController = PageController();
// 데이터 로드 함수 정의
Future<Map<String, dynamic>> fetchHomeData() async {
await Future.delayed(Duration(seconds: 1)); // 데이터 로딩 시뮬레이션
return {
'bannerItems': [
BannerItem(title: '혜택1', subtitle: '쿠폰 제공', backgroundColor: Colors.black),
// 더 많은 배너 아이템
],
'recommendedProducts': ['product1.png', 'product2.png'],
'popularProducts': ['product3.png', 'product4.png']
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Home")),
body: FutureBuilder<Map<String, dynamic>>(
future: fetchHomeData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); // 로딩 중
} else if (snapshot.hasError) {
return Center(child: Text('데이터를 불러오는데 실패했습니다.')); // 에러 발생 시
} else if (snapshot.hasData) {
final data = snapshot.data!;
return ListView(
children: [
BannerWidget(
pageController: _pageController,
bannerItems: data['bannerItems'],
),
RecommendedProductsWidget(
recommendedProducts: data['recommendedProducts'],
isPortrait: MediaQuery.of(context).orientation == Orientation.portrait,
),
PopularProductsWidget(
popularProducts: data['popularProducts'],
isPortrait: MediaQuery.of(context).orientation == Orientation.portrait,
),
],
);
} else {
return Center(child: Text('데이터가 없습니다.'));
}
},
),
);
}
}
결과
- 성능 개선: FutureBuilder로 변경한 후 불필요한 상태 변화를 제거하여, 스크롤과 리빌드가 부드러워졌습니다.
- 프레임 누락 문제 해결: 메인 스레드에서의 부하가 줄어들어 Skipped frames 오류가 사라졌습니다.
- 코드 간소화 및 유지보수성 향상: 블록 패턴을 사용하는 것보다 코드가 간단해졌고, 필요한 부분만 비동기 처리하여 구조가 명확해졌습니다.
이 사례는 Bloc을 사용할 때와 그렇지 않을 때의 적합한 사용 조건을 파악하고, 최적의 비동기 로딩 방식을 통해 앱 성능을 개선하는 과정에 중점을 두었습니다. 간단한 데이터 로딩 작업에서는 상태 관리가 필요하지 않은 경우 FutureBuilder나 Provider와 같은 가벼운 방법이 더 적합할 수 있다는 점을 보여주는 사례입니다.
728x90
'★★★트러블 슈팅' 카테고리의 다른 글
호출순서에 따라 구현이 안되는 문제 (0) | 2024.11.03 |
---|---|
갑자기 빌드 안되는날 (0) | 2024.10.30 |
asset image/ 휠 클릭 (0) | 2024.10.29 |
페이스북 토큰에 불 들어오는거 해결 (0) | 2024.10.05 |
★2차 프로젝트 회고(feat. 트러블 슈팅) (1) | 2024.10.02 |