플러터(Flutter) 앱에서 쉽게 사용할 수 있는 의존성 주입 패키지 Get_it는 코드의 결합도를 낮추고 유지보수를 쉽게 할 수 있어 개발자들에게 편의성을 제공한다. 이번 글에서는 지금까지 만들었던 앱에서 기본적인 플러터 CRUD 기능 중 Create, Read을 구현하는 방법에 대해 작성한다.
플러터 CRUD 목차
Get_it 패키지 소개
Get_it은 Flutter 앱에서 의존성 주입을 관리하는 효과적인 플러그인이다. 데이터베이스 클래스를 전역에서 사용하도록 등록하면, Get_it을 통해 어디서든 동일한 데이터베이스 인스턴스를 손쉽게 가져와 사용할 수 있다. 이는 코드의 일관성을 유지하고 개발을 간편하게 만들어준다.
아래 코드에서 GetIt.I.registerSingleton을 사용하여 LocalDatabase클래스를 애플리케이션 전역에 등록하였다.
import 'database/drift_database.dart'; import 'package:get_it/get_it.dart'; // 애플리케이션 진입점인 main 함수 Future<void> main() async { ... // LocalDatabase 인스턴스 생성 final database = LocalDatabase(); // GetIt을 사용하여 LocalDatabase를 애플리케이션 전역에서 Singleton으로 등록 GetIt.I.registerSingleton<LocalDatabase>(database); // 앱 실행 runApp(MyApp()); }
플러터 CRUD 기능
플러터 CRUD(Create, Read, Update, Delete)중에 이번 포스트에서 Create, Read에 위한 기능을 구현하고 다음 글에서 Update, Delete에 대해 작성하겠다.
입력값 저장 기능
입력 위젯에서 입력한 값을 save을 눌렀을 때 가져와야 된다. 클래스 내부에 TextEditingController 인스턴스 변수를 선언하여 클래스의 모든 메서드에서 접근 가능하도록 하면 모든 위젯에서 contentController 인스턴스 변수를 사용할 수 있다.
class _DiarySheetState extends State<DiarySheet> { // 텍스트 입력을 관리하는 TextEditingController 인스턴스 변수 선언 TextEditingController contentController = TextEditingController(); ... // 다중 행 입력을 처리하는 TextFormField 위젯 설정 TextFormField( // contentController 인스턴스 변수 사용 controller: contentController, keyboardType: TextInputType.multiline, maxLines: 25, decoration: InputDecoration( hintText: '입력해주세요.', // 만약 입력값이 없다면 hintText 표시 ), ); ... }
ElevatedButton에서 onPressed할때 _saveDiaryEntry 함수가 실행되며 debugPrint()로 콘솔에 메시지를 출력된다.
debugPrint는 개발 모드에서만 동작하며, 릴리스 모드에서는 아무런 동작을 하지 않는다. 이는 앱의 성능에 부담을 주지 않으면서 디버깅 정보를 출력할 수 있도록 도와준다.
ElevatedButton( onPressed: () { _saveDiaryEntry(date); }, child: Text('Save'), ), ... void _saveDiaryEntry(DateTime date) async { debugPrint(contentController.text); }
save 버튼을 클릭하면 입력값이 콘솔에 출력된다.
데이터베이스 연동 기능
입력폼에서 값을 가져왔다면 Sqlite를 사용하여 저장해야 된다. GetIt 패키지를 사용하여 로컬 데이터베이스에 contentController.text 값을 저장하였다.
void _saveDiaryEntry(DateTime date) async { try { // 새로운값 생성 await GetIt.I<LocalDatabase>().createSchedule( DiaryDbCompanion( content: Value(contentController.text), //날짜별 내용 date: Value(date), //선택한 날짜 create: Value(DateTime.now())), //생성일자 ); // 저장 성공 시 메시지 출력 (선택적) debugPrint('Diary entry saved successfully.'); } catch (e) { // 저장 실패 시 에러 메시지 출력 (선택적) debugPrint('Failed to save diary entry: $e'); } }
TextFormField 위젯
날짜별 데이터 유무에 따라 위젯이 다르도록 보여줘야 된다. 해당 날짜 DB에 데이터가 있다면 위젯에서 데이터를 보여주며 데이터가 없다면 빈 위젯을 보여주도록 구현한다.
아래 코드의 _buildDiaryEntrie함수에 두가지 조건의 widget을 리턴할 수 있도록 구현하였다.
Container( // Container의 정렬 속성을 설정합니다. alignment: Alignment.center, // Container의 여백(padding)을 설정합니다. padding: EdgeInsets.all(20), // Container 안에 포함된 위젯들을 세로로 나열하는 Column 위젯을 사용 child: Column( children: [ // _buildDiaryEntries() 메서드로부터 반환된 위젯을 추가 _buildDiaryEntries(), // "Save"라는 텍스트를 갖는 ElevatedButton을 추가 ElevatedButton( // 버튼이 클릭되었을 때 실행될 콜백 함수를 설정 onPressed: () { // _saveDiaryEntry(date) 메서드를 호출하여 일정을 저장 _saveDiaryEntry(date); }, // 버튼에 표시될 텍스트를 설정 child: Text('Save'), ), ], ), )
GetIt.I().watchSchedules(date)을 이용하여 DB값을 조회하고 데이터가 비어있는 경우 TextFormField 위젯을 그대로 반환하고 데이터가 있는 경우 생성일자로 정렬하고 최근 값(diaryDataList.last.content)을 가져온다.
Widget _buildDiaryEntries() { // StreamBuilder를 사용하여 일기 데이터의 변화를 감지하고 업데이트하는 위젯을 반환 return StreamBuilder<List<DiaryDbData>>( // watchSchedules(date)로부터 반환된 일기 데이터의 스트림을 감시 stream: GetIt.I<LocalDatabase>().watchSchedules(date), builder: (context, snapshot) { // 데이터 로딩 중일 때 if (snapshot.connectionState == ConnectionState.waiting) { // 로딩 상태를 나타내는 CircularProgressIndicator를 반환 return CircularProgressIndicator(); } // 데이터 로딩이나 에러가 없을 때 else if (snapshot.hasError) { // 에러 상태를 나타내는 텍스트를 반환 return Text('Error: ${snapshot.error}'); } // 데이터가 성공적으로 로딩되었을 때 else { // 로드된 일기 데이터 목록을 가져옴 final diaryDataList = snapshot.data ?? []; // 데이터 생성 일자로 정렬 diaryDataList.sort((a, b) => a.create.compareTo(b.create)); // 일기 데이터가 비어있는 경우 if (diaryDataList.isEmpty) { // TextFormField를 반환하고, 입력 컨트롤러를 사용하여 텍스트를 입력 return TextFormField( controller: contentController, keyboardType: TextInputType.multiline, maxLines: 25, decoration: InputDecoration( hintText: '입력해주세요.', // 일기 데이터가 없을 때만 hintText를 표시 ), ); } // 일기 데이터가 존재하는 경우 else { // 마지막 내용으로 초기화된 TextFormField를 반환 return TextFormField( controller: getControllerText(diaryDataList.last.content), keyboardType: TextInputType.multiline, maxLines: 25, decoration: InputDecoration( hintText: '입력해주세요.', // 일기 데이터가 없을 때만 hintText를 표시 ), ); } } }, ); } ...
getControllerText함수는 주어진 텍스트로 초기화된 TextEditingController를 반환한다.
// 주어진 텍스트로 초기화된 TextEditingController를 반환하는 함수 TextEditingController getControllerText(String text) { // TextEditingController를 생성하고 주어진 텍스트로 초기화 contentController = TextEditingController(text: text); // 초기화된 TextEditingController를 반환 return contentController; }
마무리
지금까지 일기장 앱에서 날짜별 데이터를 저장하는 기능을 구현하였습니다. 다음 포스팅에서는 사용자가 입력한 데이터를 데이터베이스에서 삭제하는 기능을 구현하도록 하겠습니다. 읽어주셔서 감사합니다.
전체 코드는 링크에서 확인할 수 있습니다.