데이터베이스나 인터넷을 이용해서 Future 형식의 데이터를 받아 리스트를 화면에 보여줄 때 Future Builder를 이용하면 편리합니다. 이 글에서는 Future Builder와 ListView Builder를 같이 사용하는 방법에 대해서 알아보겠습니다.
처음 시작 코드
* 설명의 목록번호와 코드의 주석번호를 매칭하여 보시면 됩니다.
처음 시작 코드는 다음과 같습니다.
- 화면에 FloatingActionButton이 하나 있으며, 이 버튼을 클릭할 경우에 add_student로 명명된 페이지로 이동합니다.
import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
centerTitle: true,
),
body: Column(
children: const [],
),
//1
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.pushNamed(context, '/add_student');
},
icon: const Icon(Icons.add),
label: const Text('Add Student'),
),
);
}
}
Future Builder, ListView.Builder 사용하기
본 글에서는 이 화면에 drift 데이터베이스로부터 학생 목록을 얻어서 리스트로 나열하는 방법에 대해서 정리하고자 합니다.
- 일단 데이터베이스를 연결하고 이 연결로부터 Future 형식의 리스트를 얻어와야 하기 때문에 stateless widget을 stateful widget으로 변경합니다.
- Drift 패키지를 이용하여 미리 정의한 AppDb 클래스 타입의 _db를 late 형식으로 정의합니다.
- initState를 정의합니다. initState 내부에서 _db를 AppDb()로 초기화를 시켜줍니다.
- dispose 함수도 같이 정의해줍니다. 데이터베이스를 닫을 때에는 Close() 메서드를 이용합니다.
- FutureBuilder 위젯을 만듭니다. future와 builder 두 개의 인자를 전달해야 하는데 future는 drift 데이터베이스를 통해서 만든 get 함수를 적용합니다. builder의 경우 두 개의 인자를 전달받는데 이 중 snapshot으로부터 데이터가 들어오게 됩니다.
- StudentInfo 클래스에는 학생정보를 정의해 놓았습니다. studentId, name, gender 정보가 정의되어 있습니다. snapshot.data에 _db의 List 정보가 전달되게 됩니다. 이를 studentInfos라는 변수에 저장합니다.
- snapshot의 connectionState 상태를 확인합니다. Future 타입의 경우 데이터를 모두 수신한 경우 ConnectionState.done 상태를 갖게 됩니다. 이를 갖기 전까지 CircularProgressIndicator가 표시되도록 합니다.
- snapshot.hasError는 에러가 발생하였는지를 확인할 수 있습니다. 에러가 발생한 경우 에러메시지를 화면에 표시합니다.
- studentInfos가 null이 아닌 경우에 ListView.builder를 통해 데이터를 화면에 보여줍니다. itemCount와 itemBuilder 두 개의 인자를 전달하게 되는데 itemBuilder의 index 인자를 이용해서 List의 아이템을 순차적으로 접근할 수 있습니다.
- Card 형식으로 각각의 아이템의 정보를 전달합니다.
import 'package:flutter/material.dart';
import '../data/local/db/app_db.dart';
//1
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
//2
late AppDb _db;
//3
@override
void initState() {
_db = AppDb();
super.initState();
}
//4
@override
void dispose() {
_db.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
centerTitle: true,
),
//5
body: FutureBuilder<List<StudentInfo>>(
future: _db.getStudents(),
builder: (context, snapshot) {
//6
final List<StudentInfo>? studentInfos = snapshot.data;
//7
if (snapshot.connectionState != ConnectionState.done) {
return const Center(
child: CircularProgressIndicator(),
);
}
//8
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
}
//9
if (studentInfos != null) {
return ListView.builder(
itemCount: studentInfos.length,
itemBuilder: (context, index) {
final studentInfo = studentInfos[index];
return Padding(
padding: const EdgeInsets.all(8.0),
//10
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(studentInfo.id.toString()),
Text(studentInfo.studentId.toString()),
Text(studentInfo.name.toString()),
Text(studentInfo.gender.toString()),
],
),
),
);
},
);
}
return const Text('no data found');
},
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
Navigator.pushNamed(context, '/add_student');
},
icon: const Icon(Icons.add),
label: const Text('Add Student'),
),
);
}
}
