데이터베이스나 인터넷을 이용해서 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'), ), ); } }