본문 바로가기

[flutter, drift, SQLite] New 플러터와 드리프트(drift)로 간단한 메모장 만들기 - 1탄(기본 사용법, CRUD구현)

ironwhale 2023. 6. 16.

이전에도 플러터와 드리프트(과거 moor)로 간단한 todo 리스트를 만들어 보았는대요. 한지 오래되기도 했고 해서 다시 한번 정리해 보도록 하겠습니다. 

 

flutter로 사용하는 orm 패키지 Drift

flutter에서 sqlite를 사용하기 위해서는 몇 가지 방법이 존재하는데요. 이 drift는 플러터에서 sqlite를 사용할 수 있게 만들어주는 ORM 패키지입니다. 저는 처음에 왜 ORM 패키지를 사용해야 되는지는 몰랐는데 김영한 님의 스프링 강의를 통해 RDB와 객체지향 사이에 패러다임의 차이 때문에 사용한다고 배웠습니다. 즉, RDB를 객체와 같이 다룰 수 있게 해 준다 간단하게 이렇게 이해하고 넘어가시면 될 거 같습니다. 

 

공부한 내용

제가 이번 프로젝트의 목적은 아래와 같습니다. 

  • Drift의 기본 사용법
  • Dirft로 컬럼 추가(migration)

그럼 지금 부터 시작해 보도록 하겠습니다. 


목차

1. 초기 세팅

 - 패키지 설치

2. 테이블 만들기

3. SQLite와 플러터 프로젝트 연결하기

4. 코드 제너레이션

5. Repository 만들기

 


1.  초기 세팅

처음에는 새로운 프로젝트를 생성하고 패키지를 설치하셔야 합니다. 필요한 패키지는 아래와 같습니다. 

 

패키지 설치 목록

dependencies:
  flutter:
    sdk: flutter


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2
  drift: ^2.8.2
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.3
  logger:
  go_router: 

dev_dependencies:
  drift_dev: ^2.8.3
  build_runner: ^2.4.5
  flutter_test:
    sdk: flutter

2. 테이블 만들기

다음은 이번 프로젝트에 사용할 테이블을 만드는데 마이그레이션을 통해 나중에 카테고리(폴더) 컬럼도 추가할 예정입니다. 간단하게 pk와 내용만 있는 테이블 입니다. 

 

여기서 별도의 설정이 없으면 클래스명에 뒤에 s는 코드 제너레이션 과정에서 떨어져 나가 Memo라는 클래스로 만들어집니다.  

테이블 코드

//models.dart

import 'package:drift/drift.dart';

class Memos extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get content => text()();  
}

 


3. SQLite와 플러터 프로젝트 연결하기

sqlite와 플러터 프로젝트를 연결하기 위한 클래스가 필요합니다.

 

 - @DriftDatabase(tables: [Memos])

    코드 제너레이터에게 테이블 클래스 알려주는 기능을 합니다. 

 

 -  int get schemaVersion => 1

    컬럼을 추가하거나 테이블을 추가하는 등 마이그레이션 할 때 버전을 뜻합니다. 추후 컬럼 추가하는 작업을 할 때 2로

    바꾸고 각 버전에 따라 수행해야할 작업들은 조건문으로 만들 예정입니다.  

 

- LazyDatabase _openDb()

  sqlite의 파일의 경로를 설정해주고 sqlite파일을 생성하는 부분입니다. 

import 'dart:io';

import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:drift_train/drift/models.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;

part 'drift_helper.g.dart';

@DriftDatabase(tables: [Memos])
class Mydatabase extends _$Mydatabase {
  Mydatabase() : super(_openDb());

  @override
  int get schemaVersion => 1;
}

LazyDatabase _openDb() {
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase.createInBackground(file);
  });
}

아마도 위와 같은 코드를 작성하면 여러곳에서 에러가 뜰 것인데 이것은 코드제너레이션을 하면 없어질 것입니다.  이제 코드제너레이션 명령어를 통해 드리프트 설정을 마무리하시면 됩니다. 

 

4. 코드 제너레이션

이제 flutter pub run build_runner build  또는 dart run build_runner build를 통해 코드 제너레이션을 수행하면 

drift_helper.g.dart라는 파일이 생성될 것입니다. drift를 사용할 기본적인 준비는 끝났습니다. 


5. Repository 만들기

CRUD코드만 따로 모아 둘 리파지토리를 만들어 보겠습니다. 싱글턴으로 만들었고 이 과정에서 점 두개(..)를 찍는 cascade notation이라는 방법도 사용하였습니다. 캐스케이드 노테이션은 순차적으로 함수를 실행할 때 사용하는 다트의 문법입니다. 

 

- Mydatabase 객체만들기 

  데이터 베이스에 접근하기 위한 첫 단계는 이전에 만든 Mydatabase 객체가 있어야 합니다. 

final Mydatabase _myDatabase;
final을 초기화하는 법
파이널 변수를 초기화하는 방법은 생성자 주입을 통해 했었는데 MemoRepository클래스는 외부에서 주입하지 않고 내부에서 초기화가 필요하였습니다. 그동안은 late를 통해 해결했었는데 이번에는 이니셜라이져(inializer)를 사용하여 해결하였습니다.

코드
MemoRepository._inner() : _myDatabase = Mydatabase(); // 이니셜라이져를 사용한 초기화 

  - Create: 데이터 베이스에 입력하기 

기본 문법: into(테이블).insert(테이블컴패니온.insert(필드명:값)

Mydatabase 클래스 안에서 하면 _myDatabase라는 부분은 필요가 없습니다. 보통 튜토리얼과 다르게 필드명:값 형태로 하는 방식으로 해보았습니다. 

 

  // 메모삽입
  Future<int> saveMemo(
      {required String content, String category = "기본"}) async {
    int id = await _myDatabase.into(_myDatabase.memos).insert(
        MemosCompanion.insert(content: content));
    return id;
  }

  - Update:  수정하기 

기본 문법: (update(테이블)..where((t)=>조건).write(교체할 객체)

도메인이라고 하는 우리의 관심사인 메모 객체를 파라미터로 받아 그 객체로 수정하는 방식입니다.  아래 예시코드에 사용할 때 부분을 보시면 어떻게 수정하는지를 보실 수 있습니다. 

 // 메모 수정
  Future<int> updateMemo(Memo memo) async {
    return await (_myDatabase.update(_myDatabase.memos)
          ..where((tbl) => tbl.id.equals(memo.id)))
        .write(memo);
  }


//사용할 때 
_memoRepository.updateMemo(Memo( id: memo.id, content: _contentController.text,
                                                                     folder: _categoryController.text));

  - Delete:  삭제하기 

위에 수정하기 부분에서 update 부분을 delete로 바꿔주시면 간단하게 구현하실 수 있습니다. 

 // 메모 삭제
  Future<int> deleteMemo(Memo memo) async {
    return await (_myDatabase.delete(_myDatabase.memos)
          ..where((t) => t.id.equals(memo.id)))
        .go();
  }

 - Select:  읽어오기

원래 CRUD에서 두 번째이지만 ORM이나 SQL을 공부하면 이 셀렉트 부분이 가장 방대한 양을 차지하는 것 같습니다. 다만 이번 시간에는 전체 데이터를 읽어오는 부분만 보여드리도록 하겠습니다. 

 

//메모 전체 읽기
  Stream<List<Memo>> readAll() {
    return _myDatabase.select(_myDatabase.memos).watch();
  }

위에서 보이 듯이 뒤에 wath() 메서드를 사용하면 Stream 형태로 리턴되고 get()이라고 하면 Future형태로 되니 상황에 맞게 쓰시면 될 거 같습니다. 

리파지토리 전체코드

import 'package:drift/drift.dart';
import 'package:drift_train/drift/drift_helper.dart';
import 'package:flutter/material.dart';

class MemoRepository extends ChangeNotifier {
  //field

  final Mydatabase _myDatabase;

  //// Singleton
  static final MemoRepository _memoRepository = MemoRepository._inner();

  MemoRepository._inner() : _myDatabase = Mydatabase();

  factory MemoRepository() {
    return _memoRepository;
  }

/////
  // 메모삽입
  Future<int> saveMemo(
      {required String content}) async {
    int id = await _myDatabase.into(_myDatabase.memos).insert(
        MemosCompanion.insert(content: content));
    return id;
  }

  // 메모 수정
  Future<int> updateMemo(Memo memo) async {
    return await (_myDatabase.update(_myDatabase.memos)
          ..where((tbl) => tbl.id.equals(memo.id)))
        .write(memo);
  }

  // 메모 삭제
  Future<int> deleteMemo(Memo memo) async {
    return await (_myDatabase.delete(_myDatabase.memos)
          ..where((t) => t.id.equals(memo.id)))
        .go();
  }

  //메모 전체 읽기
  Stream<List<Memo>> readAll() {
    return _myDatabase.select(_myDatabase.memos).watch();
  }
}

 

 


마치며

기본적인 Flutter의 ORM 패키지인 Drift의 기본적인 사용법에 대해 정리해 보았습니다. 사실 플러터가 그렇게 사람들이 많이 사용하는 주류는 아닌데다가 프로바이더같은 유명 패키지도 아니다보니 한글로 된 자료 뿐아니라 영어로 된 예제도 찾기 어려운것 같습니다. 하지만 공식문서를 자꾸 보다보면 우리가 원하는 기능을 구현하기 위한 부분이 반드시 있으니 꼭 공식문서도 함께 보시기 바랍니다. 

 

다음 시간에는 마이그레이션을 통해 컬럼을 추가하는 부분을 해보도록 하겠습니다. 전체 코드는 아래 깃허브 주소를 남겨 두었으니 궁금하신 분들은 한번 보시게 바랍니다. 

 

깃허브

깃허브 주소: https://github.com/ironwhale1014/flutter_drift_memo

 

GitHub - ironwhale1014/flutter_drift_memo

Contribute to ironwhale1014/flutter_drift_memo development by creating an account on GitHub.

github.com

 

댓글