Isar 데이터베이스 사용하기 + 안드로이드 에러 해결

Isar

 Isar는 Hive의 뒤를 잇는 로컬 데이터베이스 패키지입니다.

플러터 이자르 데이터베이스 패키지

플러터 이자르 공식 홈페이지


공식 홈페이지에서 한국어를 선택하여 문서를 쉽게 읽을 수 있습니다.


이자르 데이터베이스 사용하기

Pubspec.yaml

dependencies:

  isar: *isar_version

  isar_flutter_libs: *isar_version # contains Isar Core


dev_dependencies:

  isar_generator: *isar_version

  build_runner: any


Code

메인 함수

main.dart의 main 함수에 async를 추가하고 WidgetsFlutterBinding.ensureInitialized()를 추가합니다.

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

runApp(const MyApp());
}

초기화

path provider 패키지의 getApplicationDocumentsDirectory 함수를 호출하여 데이터를 저장할 path를 가져옵니다.

path provider 패키지

파라미터로 넘겨준 리스트의 Schema로 끝나는 객체들은 컬렉션 클래스를 작성한 후 코드 제너레이션으로 생성된 객체들입니다. 컬렉션 클래스를 먼저 생성한 후 추가해주면 됩니다.

final path = await getApplicationDocumentsDirectory();

_isar = await Isar.open(
[
GoldRepositorySchema,
SoundSettingSchema,
InGameSaveRepositorySchema,
],
directory: path.path,
);


컬렉션 클래스

맨 위에 part를 추가하고 @collection 어노테이션을 붙여서 만듭니다. 컬렉션 클래스 안에 여러 함수도 추가하여 원하는 기능을 구현할 수 있습니다.

int, double, bool, List, Map 등 기본적인 타입의 데이터만 저장이 가능하고, Enum의 경우 @enumerated 어노테이션을 추가해야 합니다.

아래 예시처럼 다른 객체를 추가하고 싶은 경우, 해당 객체에 @embedded 어노테이션을 추가해야 합니다.

저장하고 싶지 않은 데이터가 있다면 필드 바로 위에 @ignore 어노테이션을 추가합니다.

코드 제너레이션을 진행한 후 이자르 데이터베이스 초기화 하는 코드 부분에 스키마 객체를 추가해주세요.

코드 제너레이션은 dev_dependencies에 build_runner가 작성되어 있어야 합니다.

코드 제너레이션: dart run build_runner build

또는 파일을 변경할 때마다 자동으로 실행하고 싶다면: dart run build_runner watch (종료: Ctrl + C)

part 'in_game_save_repository.g.dart';

@collection
final class InGameSaveRepository {
Id id = Isarbase.fixedId;

int round = 0;

int inGameGold = 0;

List<InGameSaveModel> inGameSaveDataList = [];

/// 게임 데이터 백업
void backupInGameData({
required int round,
required int inGameGold,
required List<InGameSaveModel> inGameSaveDataList,
}) {
this.round = round;
this.inGameGold = inGameGold;
this.inGameSaveDataList = inGameSaveDataList;
}

/// 앱 첫 실행시 한 번 호출
Future<InGameSaveRepository?> readInGameSave() async {
final result = await Isarbase.read(this);

return result as InGameSaveRepository?;
}

/// 게임 저장 후 종료
Future<void> writeInGameSave() async {
await Isarbase.write(this);
}

/// 게임 데이터 삭제하기
Future<void> deleteInGameSave() async {
await Isarbase.inGameDelete(this);
}
}


임베드된 객체

임베드할 객체 맨 위에 part 추가 및 @embedded 어노테이션을 추가합니다.

part 'in_game_save_model.g.dart';

@embedded
final class InGameSaveModel {
/// 기물의 팀 소속
@enumerated
final Team team;

/// 기물 타입
@enumerated
final PieceType pieceType;

/// 기물의 현재 좌표
final int x;
final int y;

const InGameSaveModel({
this.team = Team.red,
this.pieceType = PieceType.king,
this.x = 4,
this.y = 8,
});
}


CRUD

쓰기

writeTxn (Txn = Transactions)을 통해 @collection 클래스의 데이터를 저장합니다.

goldRepositorys는 코드 제너레이션을 통해 생성된 인스턴스입니다. 컬렉션 클래스 이름의 camel case + 끝에 s를 붙여서 만들어집니다.

put 함수의 파라미터로 컬렉션 클래스의 인스턴스를 주면 됩니다.

static Future<void> write(Object data) async {
await _isar!.writeTxn(() async {
switch (data.runtimeType.toString()) {
case 'GoldRepository':
await _isar!.goldRepositorys.put(data as GoldRepository);
case 'SoundSetting':
await _isar!.soundSettings.put(data as SoundSetting);
case 'InGameSaveRepository':
await _isar!.inGameSaveRepositorys.put(data as InGameSaveRepository);
default:
return;
}
});
}


읽기

읽을 데이터의 id를 반드시 넘겨줘야 하는데, 저는 하나의 Row만 저장해두면 돼서 아이디를 1로 고정했습니다. (fixedId = 1)

static Future<Object?> read(Object data) async {
Object? result;

switch (data.runtimeType.toString()) {
case 'GoldRepository':
result = await _isar!.goldRepositorys.get(fixedId);
case 'SoundSetting':
result = await _isar!.soundSettings.get(fixedId);
case 'InGameSaveRepository':
result = await _isar!.inGameSaveRepositorys.get(fixedId);
default:
return null;
}

return result;
}


삭제

await Isarbase._isar!.writeTxn(() async {
await _isar!.inGameSaveRepositorys.delete(fixedId);
});


Isar 데이터베이스 안드로이드 에러

이자르 패키지 3.0.0 버전 이후부터 안드로이드 그레이들 8.x 버전을 지원하지 않는다고 합니다. 안드로이드에서 실행시키면 에러가 발생할 겁니다.

디버그 모드, 릴리즈 모드 둘다 각각 다른 에러가 납니다. android/build.gradle 파일을 다음과 같이 수정해보세요.

rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
// fix for verifyReleaseResources
// ============
afterEvaluate { project ->
if (project.plugins.hasPlugin("com.android.application") ||
project.plugins.hasPlugin("com.android.library")) {
project.android {
compileSdkVersion 34
buildToolsVersion "34.0.0"
}
}
if (project.hasProperty("android")) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
// ============
project.buildDir = "${rootProject.buildDir}/${project.name}"
project.evaluationDependsOn(':app') // add this also
}
subprojects {
project.evaluationDependsOn(":app")
}

다른 부분인 두 번째 subprojects 부분을 업데이트 해주세요.


죽은 프로젝트?


Published 20 months ago...

이제 곧 2년이 다 되어 가네요.. 정말 훌륭한 로컬 데이터베이스인데 제발 버리지 않았기를 빕니다...


전체 코드

import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'package:samyeonchoga/repository/gold/gold_repository.dart';
import 'package:samyeonchoga/repository/in_game/in_game_save_repository.dart';
import 'package:samyeonchoga/repository/sound/sound_setting.dart';

sealed class Isarbase {
static Isar? _isar;

static const int fixedId = 1;

static Future<void> initIsarbase() async {
final path = await getApplicationDocumentsDirectory();

_isar = await Isar.open(
[
GoldRepositorySchema,
SoundSettingSchema,
InGameSaveRepositorySchema,
],
directory: path.path,
);
}

static Future<void> write(Object data) async {
await _isar!.writeTxn(() async {
switch (data.runtimeType.toString()) {
case 'GoldRepository':
await _isar!.goldRepositorys.put(data as GoldRepository);
case 'SoundSetting':
await _isar!.soundSettings.put(data as SoundSetting);
case 'InGameSaveRepository':
await _isar!.inGameSaveRepositorys.put(data as InGameSaveRepository);
default:
return;
}
});
}

static Future<Object?> read(Object data) async {
Object? result;

switch (data.runtimeType.toString()) {
case 'GoldRepository':
result = await _isar!.goldRepositorys.get(fixedId);
case 'SoundSetting':
result = await _isar!.soundSettings.get(fixedId);
case 'InGameSaveRepository':
result = await _isar!.inGameSaveRepositorys.get(fixedId);
default:
return null;
}

return result;
}

static Future<void> inGameDelete(InGameSaveRepository inGamaData) async {
await Isarbase._isar!.writeTxn(() async {
await _isar!.inGameSaveRepositorys.delete(fixedId);
});
}
}

댓글

이 블로그의 인기 게시물

카트라이더 개선 방안

팩토리 패턴을 응용한 플러터의 클린 아키텍처

프로젝트 사면초가