오늘 코드 디버깅을 하는 도중에 문제가 나타났다.
데이터를 받아오는 것보다. Flutter 에서 Widget 을 build 하는 게 느린 상황이 나타난 것이다.
(어느 순간 잘 보이는 데이터가..)
(안보영)
Q: 데이터가 로딩되는 것보다 빌드되는게 느려서 버그가 자주 생겨 이럴 땐 어떻게 해?
Provider.of<SelectedMarkerProvider>(context).memories
데이터를 불러오는 것이 빌드되는 것보다 느리다면, FutureBuilder
를 사용하여 데이터를 먼저 불러온 후에 위젯을 빌드할 수 있습니다. 이렇게 하면 데이터를 기다리는 동안 로딩 인디케이터를 표시할 수 있어 사용자 경험이 개선됩니다.
다음 코드 예시를 참고하세요:
import 'dart:async'; // 추가
// ...
// _body 함수에 FutureBuilder를 사용
Widget _body(context, List<MemoryModel> selectedMarkerMemorys) {
return FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator()); // 데이터를 기다리는 동안 로딩 인디케이터 표시
} else {
if (snapshot.hasError) {
return Center(child: Text('데이터를 불러오는 도중 오류가 발생했습니다.')); // 에러 발생 시
} else {
// 이 부분은 기존 코드를 그대로 사용하되, selectedMarkerMemorys 대신 snapshot.data 를 사용
int itemCount = snapshot.data.length;
return Expanded(
child: itemCount > 0
? ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
// ... 기존 코드 ...
},
)
: const Center(
child: Text(
'아직 이곳엔 추억이 없어요.',
style: TextStyle(color: Colors.grey),
),
),
);
}
}
},
);
}
// getData 함수를 추가하여 비동기로 데이터를 불러오기
Future<List<MemoryModel>> getData() async {
return Provider.of<SelectedMarkerProvider>(context).memories;
}
이렇게 FutureBuilder
를 사용하면, Provider
로부터 데이터를 비동기적으로 불러와 완료되면 위젯이 빌드됩니다. 데이터가 로드되는 동안 로딩 인디케이터를 표시하여 사용자에게 로딩 상태를 알릴 수도 있습니다.
FutureBuilder를 사용하면 Provider로부터 데이터를 비동기적으로 불러와 완료되면 위젯이 빌드됩니다.
일단 이름을 들어보면 Future + Builder -> 즉 일단 나중에 Builder 한다는 느낌이 팍팍온다.
그럼 일단 뤼튼에게 또 물어봐보장
Q: FutureBuilder 가 무엇이야?
FutureBuilder
는 Flutter의 위젯으로, Future
형태의 비동기 데이터를 다루기 쉽게 도와주는 위젯입니다. FutureBuilder
를 사용하면 데이터의 로딩 상태, 완료 상태, 에러 상태를 쉽게 구분하여 처리할 수 있습니다.
주로 Future
를 반환하는 비동기 작업(예를 들면, 서버에서 데이터를 가져오는 작업)의 결과를 기다린 후, 그 결과에 따라 적절한 위젯을 표시하는 용도로 사용됩니다. 작업이 진행 중일 때는 로딩 인디케이터를 표시하고, 완료되면 결과에 따라 다른 위젯을 보여줄 수 있습니다.
FutureBuilder
의 사용 예시:
FutureBuilder(
future: getDataFuture(), // Future 형태의 데이터를 반환하는 함수를 전달
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator(); // 데이터 로딩 중일 때 로딩 인디케이터 표시
} else {
if (snapshot.hasError) {
return Text('데이터 불러오기에 실패했습니다.'); // 에러 발생시 에러 메시지 표시
} else {
return Text('데이터: ${snapshot.data}'); // 데이터 로딩이 완료되면 결과를 표시
}
}
},
)
위 예시에서 getDataFuture
함수는 Future
형태의 데이터를 반환하는 함수로, 원격 서버에서 데이터를 가져오거나 로컬 저장소에서 데이터를 불러올 때 사용할 수 있습니다. FutureBuilder
는 이 함수의 결과를 비동기적으로 기다린 후, 상태에 따라 적절한 위젯을 빌드합니다.
아하 그니까 FutureBuilder 에서 Future 라는 property 에 데이터를 등록하면 그 데이터가 다 불러와질 때 까지 Builder 하지 않구 일단 다 불러오면 Builder 를 실행한다는 거구나
그럼 내 코드에 적용해보장
import 'package:tuple/tuple.dart';
// ...
return showModalBottomSheet(
// ...
builder: (context) {
return FutureBuilder<Tuple2<MarkerModel, List<MemoryModel>>>(
future: _getData(context, markerId),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
// 오류 처리...
} else {
final markerData = snapshot.data!.item1;
final memoryList = snapshot.data!.item2;
// 데이터를 사용하여 UI 구성...
}
} else {
return CircularProgressIndicator(); // 로딩 중 표시하기 위한 위젯
}
},
);
});
// getMarkerData와 getMemoryList 함수의 호출 순서를 관리하는 _getData 함수 추가
Future<Tuple2<MarkerModel, List<MemoryModel>>> _getData(BuildContext context, int markerId) async {
final markerData = await getMarkerData(context, markerId);
final memoryList = await getMemoryList(context);
return Tuple2(markerData ,memoryList);
}
임시
var selectedMarkerMemorys =
Provider.of<SelectedMarkerProvider>(context).memories;
return SizedBox(
height: MediaQuery.of(context).size.height * 0.65,
child: Stack(children: [
Column(
children: [
_header(context),
const SizedBox(height: 15),
const Divider(thickness: 1, color: Colors.grey),
_body(context, selectedMarkerMemorys),
],
),
Positioned(
right: 16.0,
bottom: 16.0,
child: FloatingActionButton(
backgroundColor: Colors.white,
onPressed: () async {
// AddNewMemory(context);
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AddMemoryScreen()));
//bottomBodyUpdate(context);
},
child: Image.asset(
'assets/images/map_icon.png',
),
)),
]),
);