This Week's Record

Flutter에서 SQL사용하기 (feat. Drift) 본문

Flutter/Data&상태

Flutter에서 SQL사용하기 (feat. Drift)

Eju 2023. 7. 19. 14:22

STEP1 패키지 설치

0. 작업 들어가기전에 패키지부터 설치해주자!

아래 4개는 dependencies에

drift

sqlite3_flutter_libs

path_provider

path 

 

아래 2개는 dev_dependencies에

drift_dev

build_runner

 

STEP2 model 작성

1. 먼저 model폴더를 만들고 그 안에 사용할 table들을 정의해준다.

import 'package:drift/drift.dart';

class Schedule extends Table {
  // PRIMARY KEY
  IntColumn get id => integer().autoIncrement()(); //중복이 안생기도록 autoIncrement

  // 내용
  TextColumn get content => text()();

  // 일정 날짜
  DateTimeColumn get date => dateTime()();

  // 시작 시간
  IntColumn get startTime => integer()();

  // 종료 시간
  IntColumn get endTime => integer()();

  // Category Color Table ID
  IntColumn get colorId => integer()();

  // 생성날짜
  DateTimeColumn get createdAt => dateTime().clientDefault(
        () => DateTime.now(), // 자동으로 now로 값이 저장됨!그리고 이건 default값이므로 값을 강제로 넣어주면 그 값으로 대체!
      )();
}

 

STEP3  데이터베이스 코드 작성

2. drift로 데이터베이스에 연결할 코드를 작성하기위해, database > drift_database.dart 파일을 만들고
아래 'drift_database.g.dart'라는 파일을 part키워드를 이용해 불러온다.
* part는 import랑 달리 private 값들을 불러올 수 있다.

참고로 아직 'drift_database.g.dart'파일은 존재하지 않지만, 생성될 예정이다!

part 'drift_database.g.dart'; // 현재파일명.g.dart

 

4. DriftDatabase 데코레이터를 이용해서 어떤 클래스들을 테이블로 쓸지 정해준다. 아래와 같이!

 

@DriftDatabase(
  tables: [
    Schedule,
    CategoryColor,
  ],
)

 

5. 실제로 데이터베이스를 형성할 클래스를 LocalDatabase라는 이름으로 만들어주고 _$LocalDatabase 클래스를 상속받도록 하기.

* 나중에 'drift_database.g.dart'가 생성될때 클래스 이름만 보고서 _$LocalDatabase라는 클래스를 만듦.

그래서 이 데이터 베이스에 관련된 모든 기능들을 다 사용할 수 있음! ㅎ_ㅎ (drift가 자동으로 그 기능들을 다 만들어주는거임)

class LocalDatabase extends _$LocalDatabase {
  //여기서 _$LocalDatabase는 나중에 drift에서 만드는 'drift_database.g.dart';파일안에 있을 거임. 그리고 프라이빗인데 쓸 수 있는 이유는 part로 불러와서.
  LocalDatabase() : super(_openConnection());
}

 

6. LocalDatabase에서 로컬데이터베이스를 만드는 작업을 drift가 다 해주지만, 우리가 제공해줘야하는게 하나 있음.

super값에도 넣었지만, 그건 바로 _openConnection클래스!

_openConnection의 내용을 살펴보면 OS에서 앱에 관한 파일을 관리할 특정 경로를 가져와서 그 파일로 데이터 베이스를 만들라고 알려주는 클래스이다.

LazyDatabase _openConnection() {
  // 실제 데이터베이스 파일을 어떤 위치에 생성시킬건지 설정 - SQL은 보조기억장치에다가 저장하도록 연결해줌띠 - 보조기억장치 어뒤?인지 명시
  return LazyDatabase(() async {
    final dbFolder =
        await getApplicationDocumentsDirectory(); // 앱설치시 os에서 앱별로 각각 사용할 수 있는 하드드라이브의 특정위치를 지정해둠. 그 배정받은 위치를 가져오는 방법!
    final file = File(p.join(
        dbFolder.path, 'db.sqlite')); // 실제 경로에다가 'db.sqlite' 파일 생성하고 file에 저장
    return NativeDatabase(file);
  });
}

 

아래는 drift_database.dart 파일 전체코드!

// 'private'값들은 불러올 수 없다.
import 'dart:io';

import 'package:calender_scheduler/models/category_color.dart';
import 'package:calender_scheduler/models/schedule.dart';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

// private 값 까지 불러올 수 있다.
part 'drift_database.g.dart';

@DriftDatabase(
  tables: [
    Schedule,
    CategoryColor,
  ],
)
class LocalDatabase extends _$LocalDatabase {
  //여기서 _$LocalDatabase는 나중에 drift에서 만드는 'drift_database.g.dart';파일안에 있을 거임. 그리고 프라이빗인데 쓸 수 있는 이유는 part로 불러와서.
  LocalDatabase() : super(_openConnection());
}

LazyDatabase _openConnection() {
  // 실제 데이터베이스 파일을 어떤 위치에 생성시킬건지 설정 - SQL은 보조기억장치에다가 저장하도록 연결해줌띠 - 보조기억장치 어뒤?인지 명시
  return LazyDatabase(() async {
    final dbFolder =
        await getApplicationDocumentsDirectory(); // 앱설치시 os에서 앱별로 각각 사용할 수 있는 하드드라이브의 특정위치를 지정해둠. 그 배정받은 위치를 가져오는 방법!
    final file = File(p.join(
        dbFolder.path, 'db.sqlite')); // 실제 경로에다가 'db.sqlite' 파일 생성하고 file에 저장
    return NativeDatabase(file);
  });
}

 

7. 터미널에 아래 내용을 실행시킨다.

이 코드는 drift에 한정된 코드가 아니라, 제너레이션을 해주는 모든 코드에 적용됨.

flutter pub run build_runner build

그럼, 터미널에 코드들이 와랄라 뜨다가 작업이 끝나면 아래와 같이 drfit_database.g.dart 라는 파일이 생김.

*이때 안생기면 기다려보거나 다시 켜보시길. 로드가 안 된 것 일 수도 있기 때문

 

8. LocalDatabase에 override missing 에러가 뜨는데, schemaVersion을 오버라이딩해줘야함.

여기서 shcemaVersion은 처음에는 1을 넣어주면 되는데, 데이터베이스의 구조가 바뀔때마다 즉 table이 바뀔때마다 버전을 올려줘야함

class LocalDatabase extends _$LocalDatabase {
  //여기서 _$LocalDatabase는 나중에 drift에서 만드는 'drift_database.g.dart';파일안에 있을 거임. 그리고 프라이빗인데 쓸 수 있는 이유는 part로 불러와서.
  LocalDatabase() : super(_openConnection());
  
  @override
  int get schemaVersion => 1; // 데이터베이스에 설정한 상태들의 버전. 데이터베이스의 구조(table)가 바뀔때마다 버전 올려줘야함. 
}

 

------- 이제 쿼리를 짤 준비 완료-! ------

 

9. 각각의 데이터베이스에 insert할 수 있는 쿼리를 짜자.

LocalDatabase 클래스 안에 코드를 짜면 되는데, 아래를 참고!

*사용된 ScheduleCompanion, schedule 모두 _$LocalDatabase안에 생성되어 있는 값임

*insert는 자동으로 primary key값을 int형태로 돌려줌!

class LocalDatabase extends _$LocalDatabase {
  //여기서 _$LocalDatabase는 나중에 drift에서 만드는 'drift_database.g.dart';파일안에 있을 거임. 그리고 프라이빗인데 쓸 수 있는 이유는 part로 불러와서.
  LocalDatabase() : super(_openConnection());

  Future<int> createSchedule(ScheduleCompanion data) => into(schedule).insert(data);
  //schedule 또한 _$LocalDatabase안에 생성되어 있는 값임, 그리고 insert는 자동으로 primary key값을 int형태로 돌려줌!

  @override
  int get schemaVersion =>
      1; // 데이터베이스에 설정한 상태들의 버전. 데이터베이스의 구조(table)가 바뀔때마다 버전 올려줘야함.
}

 

10. 그럼 select하는 코드도 짜보자.

select는 값을 불러오는 것이다 보니 1.Stream으로 가져올수도있고(메모리 적게씀) 2.Future로 가져올수도 있음(한꺼번에 값을 다 받을 수 있음)

{"originWidth":668,"originHeight":308,"style":"alignCenter","caption":"get:List로 다가져오기

class LocalDatabase extends _$LocalDatabase {
  LocalDatabase() : super(_openConnection());

  ...생략...

  Future<List<CategoryColorData>> getCategoryColors() =>
      select(categoryColor).get();

  @override
  int get schemaVersion => 1; 
}

** 여기보면 나는 CategoryColorData 로 해뒀지만 많은 예시에서 CategoryColor로 적혀있을 것이다. (기존에 우리가 table만든 클래스)

하지만 그 데이터타입은 기존에 우리가 만들어둔 CategoryColor 클래스가 아니라 _$LocalDatabase안에 만들어진 클래스를 리스트타입으로 내보내는 것이므로, 만약 에러가 뜨고 끝에 Data를 붙여야한다고 하면 바꾸고 진행해보시라!

 

 

자자 그럼 SQL 코드짜기는 완료!

STEP4  데이터베이스 main 함수에 적용

11. main함수에 데이터베이스 선언해주기 -> 이제 database. 하면 그 안에 있던 함수들 불러올 수 있다!

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 위의 함수는 플러터 프레임워크가 준비가 됐는지 확인하는 함수.
  // runApp이 실행되면 자동으로 실행되는데, initializeDateFormatting같이 runApp가 실행되기 전에 실행할 코드가 있으면 준비됐는지 확인해봐야한다.

  await initializeDateFormatting();
  // main에서도 async가 된다.. 그리고 이렇게 초기화하는 함수도 미리 작동시켜놓을 수 있담
  
  // 📍여기!!
  final database = LocalDatabase();

  runApp(
    MaterialApp(
      theme: ThemeData(
        fontFamily: 'NotoSans',
      ),
      home: HomeScreen(),
    ),
  );
}

 

12. 데이터 불러오는 예시

*값은 모두 dirft 패키지에서 가져온 Value에 감싸야한다. ex) hexCode: Value(hexCode),

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await initializeDateFormatting();

  final database = LocalDatabase();
  
  // 📍여기!
  final colors = await database.getCategoryColors();
  if (colors.isEmpty) {
    for (String hexCode in DEFAULT_COLORS) {
      await database.createCategoryColor(
        CategoryColorCompanion(
          hexCode: Value(hexCode),
        ),
      );
    }
  }

  runApp(
    MaterialApp(
      theme: ThemeData(
        fontFamily: 'NotoSans',
      ),
      home: HomeScreen(),
    ),
  );
}