플러터 CRUD 기능 구현(Get_it, Drift, SQLift) – 일기장 앱 만들기 (6)

플러터(Flutter) 앱에서 쉽게 사용할 수 있는 의존성 주입 패키지 Get_it는 코드의 결합도를 낮추고 유지보수를 쉽게 할 수 있어 개발자들에게 편의성을 제공한다. 이번 글에서는 지금까지 만들었던 앱에서 기본적인 플러터 CRUD 기능 중 Create, Read을 구현하는 방법에 대해 작성한다.

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 버튼을 클릭하면 입력값이 콘솔에 출력된다.

플러터-CRUD-입력

데이터베이스 연동 기능

입력폼에서 값을 가져왔다면 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;
}

마무리

지금까지 일기장 앱에서 날짜별 데이터를 저장하는 기능을 구현하였습니다. 다음 포스팅에서는 사용자가 입력한 데이터를 데이터베이스에서 삭제하는 기능을 구현하도록 하겠습니다. 읽어주셔서 감사합니다.

전체 코드는 링크에서 확인할 수 있습니다.

Leave a Comment