2023. 4. 21. 20:32ㆍDEV/Flutter
Flutter의 Local NoSQL Database 중 Hive를 사용해보려고 한다. 쉽고, 간단하고, 빠르게~
Hive란?
Hive는 순수 Dart로 만들어진 Key-Value 형식의 빠른 데이터 베이스이다.
💡 쿼리, 멀티-아이솔레이트 지원 또는 객체 간 연결이 필요한 경우 Isar Database를 확인할 것.
Features
- 🚀 Cross platform: mobile, desktop, browser
- ⚡ Great performance (see benchmark)
- ❤️ Simple, powerful, & intuitive API
- 🔒 Strong encryption built in
- 🎈 NO native dependencies
- 🔋 Batteries included
Work Progress
📎 Hive Doc
작업 과정을 간단하게 요약하면
- Add to project : Install hive_flutter package
- Create Hive Object class : Model Class를 생성한다 → @HiveType , @HiveField , part 'filename.g.dart'
- Generate adapter : build_runner로 Build해서 .g.dart에 Model Class의 TypeAdapter를 생성한다.
- Initialize Flutter : 가장 최상단에서 Hive를 초기화한다. main.dart → awit Hive.initFlutter();
- Register adapter : 생성한 Adapter를 등록한다.
- Open Box : 박스를 열어 사용할 수 있게 한다.
- CRUD database : 객체(데이터)를 사용한다.
Installation
Hive를 사용하기 위해서는 다음 라이브러리를 설치해야 한다.
- dependencies
- hive : Hive 실제 Database
- hive_flutter : Flutter에서 Hive 사용을 할 수 있도록 해준다.
- dev_dependencies
- hive_generator : Database와 Flutter를 연결해주는 어댑터를 자동적으로 생성해준다.
- build_runner : TypeAdapter를 생성할 때 사용한다.
dependencies:
hive:
hive_flutter:
dev_dependecies:
hive_generator:
build_runner:
1. Create Hive Object class
- 클래스에 대한 TypeAdapter를 생성하려면 해당 클래스에 @HiveType 어노테이션을 추가하고 typeId(0에서 223 사이의 고유한 값)를 지정한다.
- 저장되어야 하는 모든 필드에 @HiveField 어노테이션을 추가한다.
- HiveObject를 상속받아서 Model을 생성하면 해당 객체의 정보를 변경하는 것만으로도 수정/삭제 등이 가능하다.
- HiveList Type으로 다른 객체와의 관계를 설정할 수 있다. RDB의 Join으로 이해하면 된다.
import 'package:hive/hive.dart';
part 'person.g.dart';
@HiveType(typeId: 1)
class Person extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int age;
@HiveField(2)
HiveList<Person>? friends; // Nullable 설정 : 생성할 때 친구가 없으므로
Person({
required this.name,
required this.age,
this.friends, // Nullable 설정
});
@override
String toString() => '{name:$name, age:$age, frends:$friends}';
String getFriends() { // 친구 데이터를 String으로 UI에 표시하기 위해 사용
var names = '';
if (friends != null) {
for (var friend in friends!) {
names = '$names${names == '' ? '' : ', '}${friend.name}';
}
}
return names;
}
}
2. Generate Adapter
Front-end에서 Database를 사용하면 데이터베이스 형식이 아닌 파일 형식으로 저장된다. 이를 데이터로 변환해주기 위한 Adapter를 만들어서 사용한다.
3. TypeAdapter 생성
Adapter를 만들기 위해 터미널을 열고 Project Root 경로에서 build를 실행한다.
% flutter pub run build_runner build
생성된 Adapter를 확인하면 이렇다.
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'person.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class PersonAdapter extends TypeAdapter<Person> {
@override
final int typeId = 1;
@override
Person read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Person(
name: fields[0] as String,
age: fields[1] as int,
friends: (fields[2] as List).cast<String>(),
);
}
@override
void write(BinaryWriter writer, Person obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.age)
..writeByte(2)
..write(obj.friends);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is PersonAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
만약 build 오류가 발생한다면 아래의 명령어를 실행한다.
flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs
Adapter가 잘 생성됐다면 이제 사용할 준비가 됐다.
4. Initialize Hive & Adapter 등록
main() 함수에서 Hive를 초기화하고, 생성한 TypeAdapter를 등록한다.
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
await Hive.initFlutter(); // * Hive 초기화
Hive.registerAdapter(TaskAdapter()); // * Adapter 등록
runApp(
const MyApp(),
);
}
5. Open Box & Create box file
- 모든 데이터는 Box에 저장된다.
- Box를 SQL Database의 Table로 이해하면 된다.
- Box는 구조를 갖고 있지 않아서 어떤 것이든 담을 수 있다.
6. HiveRepository로 관리하기
Hive 비즈니스 로직을 별도의 파일로 만들어 관리를 용이하게 한다.
Singleton 방식과 static 방식 중 선택해서 사용한다. main() 함수에서 openBox() 함수를 호출할 때 코드가 달라진다.
Singleton 방식
const String TASK_BOX = 'TASK_BOX';
class HiveRepository {
// * Singleton 설정
static final HiveRepository _singleton = HiveHelper._internal();
factory HiveRepository() {
return _singleton;
}
HiveRepository._internal();
// * Box 생성 : 모든 데이터는 Box에 저장된다.
Box<Task>? tasksBox;
// * Box를 열어주는 함수 생성
// -> Box에 데이터를 담을 준비를 한다.
Future openBox() async {
// * Open Box : Box가 여러 개인 경우 여기에 계속 추가화면 됨
tasksBox = await Hive.openBox(TASK_BOX);
}
}
Future<void> main() async {
// * Hive 설정
await Hive.initFlutter(); // Initialize Hive
Hive.registerAdapter(PersonAdapter()); // Register Adapter
await HiveRepository().openBox(); // Open Box
runApp(const App());
}
static 방식
import 'package:hive/hive.dart';
import 'package:madang/todo/task.dart';
const String TASK_BOX = 'TASK_BOX';
class HiveRepository {
// * Box 생성 : 모든 데이터는 Box에 저장된다.
Box<Task>? tasksBox;
// * Box를 열어주는 함수 생성
// -> Box에 데이터를 담을 준비를 한다.
static Future openBox() async {
// * Open Box : Box가 여러 개인 경우 여기에 계속 추가화면 됨
tasksBox = await Hive.openBox(TASK_BOX);
}
}
Future<void> main() async {
// * Hive 설정
await Hive.initFlutter(); // Initialize Hive
Hive.registerAdapter(PersonAdapter()); // Register Adapter
await HiveRepository.openBox(); // Open Box
runApp(const App());
}
Data CRUD
Create
- box.add(value) : id를 자동으로 생성하고 데이터 입력
- box.put(key, value) : key(id)를 지정해서 데이터 입력
// * Key값을 직접 지정하면서 저장하는 경우 -> Key를 직접 지정하면 그 Key로 읽어올 수 있다.
box.put('key', 'value');
box.putAll([Person1, Person2, Person3, ...]);
// * HiveObject 상속 시 단순 저장 가능
box.add(Person);
Read
- get(key) : key값으로 데이터를 가져온다.
- getAt(index) : index(위지)로 데이터를 가져온다.
// * 특정 데이터 하나만 읽어오는 경우
box.get(0); // 0 : index
box.get('key');
// * 데이터 전체를 리스트로 읽어오는 경우
box.values.toList();
Filter & Sort
- List 함수로 처리한다.
// Filter
var filteredUsers = userBox.values.where((user) => user.name.startsWith('s')).toList();
var filtered = box.values
.where((object) => object['country'] == 'GB')
.toList();
// Sort
var items = box.values.toList();
items.sort((a, b) => a.name.compareTo(b.name)); //ASC
items.sort((b, a) => a.name.compareTo(b.name)); //DESC
Update
// * Model에서 HiveObject를 상속받은 경우
Person person = box.get('key'); // 데이터 가져오기
person.age = 0; // 데이터 값 변경
age.save(); // 변경된 데이터 저장
Delete
person.delete(); // Hive Object로 삭제하기
personsBox.clear(); // 데이터 전체 삭제하기
Update Model class
데이터 모델을 수정해야하는 경우 아래의 규칙을 준수하면 기존 코드에 문제 없이 adapter를 업데이트할 수 있다.
- 기존 field의 field number를 변경하면 안된다.
- 새로운 field를 추가하더라도 old apdater가 작성한 객체(데이터)를 new adapter가 읽을 수 있다.
- new code로 생성된 객체를 old code가 읽을 수 있다.
- 새로 추가된 field는 무시하고 parsing 하기 때문이다.
- field number가 동일하다면 field 이름을 변경할 수 있고 공개에서 비공개로 전환 가능하며 그 반대도 가능하다.
- field number가 업데이트된 class에서 다시 사용되지 않는 다면 field를 제거할 수 있다.
- field type 변경은 지원하지 않는다. 새로 만들어야 한다.
- null safety가 활성화한 후 null를 허용하지 않는 field에서는 defaultValue를 제공해야 한다.
ValueListenableBuilder
- Stream처럼 데이터의 변경을 감지해서 자동으로 Re-Build해준다.
- valueListenable : 연결할 데이터에 listenable 설정
- child : 데이터에 따라 변화되지 않는 위젯 입력 -> 그 위젯은 다시 렌더링하지 않는다.
- builder : 데이터를 사용하여 페이지 구성 - 기본 생성자 (BuildContext context, dynamic value, Widget? child)
- value : Hive Box 지정 eg. Box<Model Type> box
- child : child로 받은 위젯 전달용
ValueListenableBuilder(
valueListenable:
HiveRepository.personBox.listenable(), // Listening 설정
builder:
(BuildContext context, Box<Person> box, Widget? child) {
// Box의 데이터를 모두 가져온다.
List<Person> people = box.values.toList();
// 데이터 사용해서 Widget 생성 //
}
Example
간단한 테스트 예제를 보자.
Person이라는 Model Class의 Box를 만들었다. 기본 기능 사용을 위해 모든 기능은 간소화해서 억지스러운 부분도 있지만 단순 테스트를 위한 거니 그러려니 하자.
- Create : TextField에 입력한 name, age로 Person 데이터를 Box에 추가한다.
- Read : 입력한 데이터를 아래에 ListView에 출력한다.
- Update
- 리스트에서 수정 아이콘(2번째 아이콘)을 클릭하면 해당 객체의 값이 TextField에 추가해서 수정할 수 있게 한다.
- 값을 수정한 다음 Update 버튼을 누르면 수정 완료.
- 리스트의 삭제 아이콘(3번째 아이콘)을 클릭하면 해당 데이터가 삭제된다.
- Delete 버튼을 클릭하면 전체 데이터가 삭제된다.
- 리스트의 추가 아이콘(1번째 아이콘)을 클릭하면 1번 데이터와 친구로 설정된다. → HiveList 사용
테스트 끝!
'DEV > Flutter' 카테고리의 다른 글
Flutter - iOS 시스템 설정에 Flutter 앱 설정 추가하기 (0) | 2023.10.19 |
---|---|
Flutter Build Runner - Filesystem Error 대응 방법 (0) | 2023.08.29 |
Flutter State Management, 상태 관리는 어떻게 할까? (0) | 2023.04.20 |
MacOS에 Flutter 설치하기 - feat. Homebrew (0) | 2023.03.31 |
[Firebase & iOS Xcode Build Error] Cloud Firestore Package 설치 후 Xcode Build가 너~~무 느려서 진행이 안되는 문제 해결 방법 (0) | 2022.08.17 |
Android Emulator에서 한글 키보드 사용하기 (0) | 2022.07.23 |
Flutter에서 Firestore Database 사용하기 (0) | 2022.07.20 |
Flutter & Firebase - Authentication State 구독 메소드 (0) | 2022.07.18 |