Study Record

[Flutter] drift 시작하기 (Sql 기반 데이터베이스와 테이블 만들기) 본문

Flutter/라이브러리

[Flutter] drift 시작하기 (Sql 기반 데이터베이스와 테이블 만들기)

초코초코초코 2023. 3. 23. 19:23
728x90

 

✍ drift

드리프트(Drift)는 Flutter 와 dart 를 위한 반응형 지속성 라이브러리로, sqlite 위에 구축되었다.  SQL 와 Dart 언어를 사용하여 쿼리를 작성할 수 있고 여러 테이블과 join 연산뿐 아니라 WITH,WINDOW 와 같은 복잡한 SQL 언어도 사용할 수 있다. Android 와 IOS 뿐만 아니라 Windows, Linux, Web 에서도 작동한다. 이 글에서 설명하는 drift 는 2.6.0 버전에 해당한다. 이 글에 해당하는 모든 코드는 dift docs 를 참고했다.

 

 

drift | Dart Package

Drift is a reactive library to store relational data in Dart and Flutter applications.

pub.dev

 

 

😶 drift 프로젝트에 적용하기

 

pubspec.yaml 파일에 dependency 를 추가한 뒤, pub get 버튼을 누른다.

dependencies:
  drift: ^2.6.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.3

dev_dependencies:
  drift_dev: ^2.6.0
  build_runner: ^2.3.3

 

만약, pub get 버튼을 눌렀을 때 version solving failed 과 관련된 에러가 나온다면 path 라이브러리가 1.8.3 과 1.8.2 로 겹쳐 어떤 것을 사용할지 몰라 나오는 오류이므로 dependency_overrides 키워드를 사용하여 해결할 수 있다.

 

dependency_overrides 로 path 어떤 버전의 라이브러리를 사용할지 정해준다.

dependencies:
  drift: ^2.6.0
  sqlite3_flutter_libs: ^0.5.0
  path_provider: ^2.0.0
  path: ^1.8.3

dev_dependencies:
  drift_dev: ^2.6.0
  build_runner: ^2.3.3

dependency_overrides:
  path: ^1.8.3

 

 

😶 drift 를 사용하여 데이터베이스와 테이블 생성하기

 

Todo 테이블과 Category 테이블이 있는 Test_database 데이터베이스를 생성하려고 한다.

 

 

 

😶 테이블 정의하기

먼저 파일이름을 test_database.dart 으로 파일을 생성한 뒤, 테이블을 생성하는 코드이다.

import 'package:drift/drift.dart';

// this will generate a table called "todos" for us. The rows of that table will
// be represented by a class called "Todo".
class Todos extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 6, max: 32)();
  TextColumn get content => text().named('body')();
  IntColumn get category => integer().nullable()();
}

// This will make drift generate a class called "Category" to represent a row in
// this table. By default, "Categorie" would have been used because it only
//strips away the trailing "s" in the table name.
@DataClassName('Category')
class Categories extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get description => text()();
}

Table 을 상속받는 Todos, Categories 클래스는 테이블에 해당한다. 나중에 데이터 클래스가 만들어질 때, s 를 뗀 Todo, Categorie 를 사용하게 되는데 DataClassName 어노테이션은 데이터 클래스의 이름을 Categorie 이 아니라 Category 로 사용할 수 있게 해준다. column 은 꼭 "=>" 연산자를 사용해야 하며, 위의 예시처럼 IntColumn, TextColumn 등의 데이터 타입은 Column 으로 설정할 수 있다. Todo 테이블의 content 칼럼은 named('body') 함수에 의해 이름이 content 가 아니라 body 로 사용된다.

 

 

 

😶 데이터베이스 생성하기

part 명령어로 현재 파일 이름의 ".g.dart" 가 붙은 파일을 불러오는데 처음에는 파일이 존재하지 않아 오류가 나오지만 나중에 자동으로 생성될 파일이므로 괜찮다. 파일 이름이 "test_database.dart" 이므로 "test_database.g.dart" 파일을 part 한다.  @DriftDatabase 어노테이션에 정의한 테이블 클래스를 넣고 자기 이름 앞에 "_$" 를 상속받는 클래스로 데이터베이스 클래스를 선언한다. 자기 이름 앞에 "_$" 가 붙은 클래스는 "test_database.g.dart" 에 포함된 파일로 나중에 자연스레 오류가 없어진다.

_openConnecton() 함수에서는 기기에 저장된 데이터베이스 정보가 저장된 파일을 불러오는데 없으면 새로 생성된다. 

import 'package:drift/drift.dart';
import 'dart:io';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
// assuming that your file is called filename.dart. This will give an error at
// first, but it's needed for drift to know about the generated code
part 'test_database.g.dart';

@DriftDatabase(tables: [Todos, Categories])
class TestDatabase extends _$TestDatabase {
  // we tell the database where to store the data with this constructor
  TestDatabase() : super(_openConnection());

  // you should bump this number whenever you change or add a table definition.
  // Migrations are covered later in the documentation.
  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  // the LazyDatabase util lets us find the right location for the file async.
  return LazyDatabase(() async {
    // put the database file, called test_database.sqlite here, into the documents folder
    // for your app.
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'test_database.sqlite'));
    return NativeDatabase.createInBackground(file);
  });
}

 

 

 

😶 빌드하기

위에서 설명한 테이블과 데이터베이스를 생성하는 코드를 합친 "test_database.dart" 파일의 코드이다.

import 'package:drift/drift.dart';
import 'dart:io';
import 'package:drift/native.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
part 'test_database.g.dart';

class Todos extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get title => text().withLength(min: 6, max: 32)();
  TextColumn get content => text().named('body')();
  IntColumn get category => integer().nullable()();
}

@DataClassName('Category')
class Categories extends Table {
  IntColumn get id => integer().autoIncrement()();
  TextColumn get description => text()();
}

@DriftDatabase(tables: [Todos, Categories])
class TestDatabase extends _$TestDatabase {
  TestDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;
}

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

 

 

터미널에서 "flutter pub run build_runner build" 를 실행하면 문제가 없다면 test_database.g.dart 파일이 생성될 것이다. 그러면 test_database.dart 파일에서의 오류도 전부 사라진다.

 

> flutter pub run build_runner build

 

 

 

 

😶 main 에서 테스트하기

main 함수가 포함된 파일로 돌아와 test_database.dart 파일에 만들어둔 데이터베이스 클래스(TestDatabase) 를 가져와 insert 명령어로 테스트해본다. 테스트를 위해 작성하는 코드이기 때문에 runApp() 함수를 실행하기 전(앱이 초기화되기 전)에  드리프트(drift)를 사용하는 것이기 때문에 WidgetsFlutterBinding.ensureInitialized(); 함수를 미리 실행해 준다.

import 'package:drift/drift.dart' show Value;
import 'package:drift_test/database/test_database.dart';
import 'package:flutter/material.dart';

void main() async {
  // runApp 이 실행하면 자동으로 실행되지만 플러터 프로그램이 준비가 됐는지 확인
  WidgetsFlutterBinding.ensureInitialized();

  final database = TestDatabase();

  // Simple insert:
  await database
      .into(database.categories)
      .insert(CategoriesCompanion.insert(description: Value('my first category')));

  // Simple select:
  final allCategories = await database.select(database.categories).get();
  print('Categories in database: $allCategories');
  
  // 앱 실행
  runApp(const MyApp());
}
...

 

데이터베이스가 잘 생성되었다면 Log 에 Categories in database ~ 가 출력되었을 것이다.

 

안드로이드의 경우, Device File Explorer 로 확인 결과 test_database.sqlite 가 app_flutter 파일 안에 생성된다.

 

DB Brower 프로그램으로 Device File Explorer 에서 찾은 파일을 열어보니 위에서 선언한 대로 테이블이 잘 생성되었다.

 

728x90