왜 Hive인가
Flutter 로컬 저장소로 가장 많이 쓰이는 건 sqflite다. 그런데 sqflite는 SQL을 알아야 하고, 테이블 스키마 정의, 마이그레이션 관리가 필요하다. Hive는 다르다. Dart 객체를 그대로 저장하는 NoSQL key-value 스토어다. 스키마가 없고, 박스(Box)에 넣고 꺼내면 끝이다.
sqflite와 정면 비교:
| 항목 | sqflite | Hive |
|---|---|---|
| 저장 방식 | 관계형 테이블 | Key-Value Box |
| 쿼리 | SQL 직접 작성 | Dart 코드로 필터링 |
| 스키마 정의 | CREATE TABLE | @HiveType + @HiveField |
| 코드 생성 | 없음 | build_runner (TypeAdapter) |
| 속도 | 보통 | 빠름 (바이너리 직렬화) |
| 학습 곡선 | SQL 지식 필요 | Dart만 알면 됨 |
앱 구조
lib/
├── main.dart # Hive 초기화, 앱 진입
├── models/
│ ├── memo.dart # @HiveType 모델
│ └── memo.g.dart # 자동 생성 TypeAdapter
└── screens/
├── memo_list_screen.dart # 목록 + 검색
└── memo_edit_screen.dart # 추가/수정/삭제
기능:
- 메모 추가 / 수정 / 삭제 (스와이프 or 삭제 버튼)
- 6가지 배경 색상 선택
- 수정일 기준 정렬
- 실시간 검색 (제목 + 내용)
핵심 구현
Hive 모델 정의
import 'package:hive/hive.dart';
part 'memo.g.dart';
@HiveType(typeId: 0)
class Memo extends HiveObject {
@HiveField(0)
late String title;
@HiveField(1)
late String content;
@HiveField(2)
late int colorValue;
@HiveField(3)
late DateTime createdAt;
@HiveField(4)
late DateTime updatedAt;
}
@HiveType(typeId: 0) — Box에 저장될 타입을 등록한다. typeId는 앱 전체에서 유일해야 한다. 나중에 모델을 추가하면 1, 2, 3으로 증가시킨다.
@HiveField(n) — 각 필드에 번호를 붙인다. 이 번호가 바이너리 직렬화 시 필드 식별자가 된다. 한 번 배포한 뒤 기존 필드 번호를 바꾸면 기존 데이터를 못 읽는다. 필드 추가는 새 번호로만 해야 한다.
extends HiveObject — memo.save(), memo.delete() 같은 편의 메서드를 쓸 수 있다. Box를 직접 조작하지 않아도 된다.
TypeAdapter 자동 생성
dart run build_runner build --delete-conflicting-outputs
실행하면 memo.g.dart가 생성된다. MemoAdapter 클래스가 들어있고, Hive가 이걸 이용해 Memo 객체를 바이너리로 직렬화/역직렬화한다. 직접 작성할 필요 없다.
주의: main.dart에 Dart 3 switch expression 같은 신문법이 있으면 hive_generator의 구버전 analyzer가 파싱을 못해 SEVERE 에러가 난다. build_runner 실행 전에 해당 문법을 정리해야 한다.
Hive 초기화 — main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter(); // 플랫폼별 저장 경로 자동 설정
Hive.registerAdapter(MemoAdapter()); // TypeAdapter 등록
await Hive.openBox<Memo>('memos'); // Box 열기
runApp(const MemoApp());
}
Hive.initFlutter()는 hive_flutter 패키지에서 제공한다. iOS는 Documents 디렉터리, Android는 앱 데이터 디렉터리에 .hive 파일을 생성한다.
실시간 반응형 목록 — ValueListenableBuilder
ValueListenableBuilder(
valueListenable: Hive.box<Memo>('memos').listenable(),
builder: (context, Box<Memo> box, _) {
final memos = box.values.toList()
..sort((a, b) => b.updatedAt.compareTo(a.updatedAt));
// ...
},
)
box.listenable()을 ValueListenableBuilder와 연결하면 Box에 변경이 생길 때마다 UI가 자동으로 리빌드된다. setState()나 별도 상태 관리 없이 반응형 목록이 만들어진다.
저장 / 수정 — HiveObject.save()
if (_isEditing) {
widget.memo!
..title = title
..content = content
..colorValue = _selectedColorValue
..updatedAt = now;
await widget.memo!.save(); // HiveObject 메서드 — Box 업데이트
} else {
await box.add(Memo( // 새 항목 추가
title: title,
content: content,
colorValue: _selectedColorValue,
createdAt: now,
updatedAt: now,
));
}
HiveObject.save()는 객체의 현재 상태를 Box에 즉시 반영한다. box.put(key, object) 를 쓰지 않아도 되는 이유가 extends HiveObject 덕분이다.
스와이프 삭제 — Dismissible
Dismissible(
key: Key(memo.key.toString()),
direction: DismissDirection.endToStart,
background: Container(/* 삭제 배경 */),
onDismissed: (_) => memo.delete(),
child: _MemoCard(/* ... */),
)
memo.delete()는 HiveObject 메서드. Box key를 몰라도 객체 자신을 Box에서 제거할 수 있다.
색상을 int로 저장하는 이유
Hive는 Dart의 기본 타입(String, int, double, bool, DateTime, List, Map)을 별도 설정 없이 저장할 수 있다. Color는 기본 타입이 아니다. Color를 직접 저장하려면 별도 TypeAdapter가 필요하다.
가장 간단한 방법은 Color.toARGB32()로 int로 변환해서 저장하고, 꺼낼 때 Color(value)로 복원하는 것이다.
// 저장
colorValue: _colorOptions[0].toARGB32()
// 복원
Color _colorFromValue(int value) =>
_colorOptions.firstWhere(
(c) => c.toARGB32() == value,
orElse: () => _colorOptions[0],
);
비교 테이블: 로컬 저장소 선택 기준
| 상황 | 추천 |
|---|---|
| 단순 key-value (설정값, 토큰) | shared_preferences |
| 구조화된 객체, 빠른 읽기/쓰기 | Hive |
| 복잡한 관계형 쿼리 (JOIN, 인덱스) | sqflite / drift |
| 타입 안전 SQL + 코드 생성 | drift |
| 대용량 파일/바이너리 | 파일시스템 직접 |
메모앱처럼 Dart 객체를 그대로 저장하고 전체 목록을 가져오는 용도엔 Hive가 가장 적합하다.
삽질 기록
build_runner SEVERE: Expected an identifier
hive_generator 2.0.1은 내부적으로 analyzer 6.4.1을 쓴다. Flutter 3.32 기본 프로젝트 템플릿의 main.dart에는 Dart 3.10 switch expression이 포함돼 있는데, 이게 구버전 analyzer로는 파싱이 안 된다.
해결: build_runner build 전에 main.dart를 실제 앱 코드로 교체하면 된다. 기본 카운터 앱 코드에 의존하는 게 아니라 처음부터 실제 코드를 쓰면 발생하지 않는다.
Color.value deprecated
Flutter 3.x에서 Color.value (int 반환)가 deprecated됐다. toARGB32()를 쓰면 동일한 값을 얻을 수 있다. Hive에 색상 int를 저장할 때, 팔레트에서 색상을 비교할 때 모두 toARGB32()로 교체해야 flutter analyze가 깨끗하게 통과한다.
HiveObject.save() vs box.put()
extends HiveObject를 쓰면 object.save(), object.delete()로 Box를 직접 참조하지 않아도 된다. 처음엔 box.put(memo.key, memo)로 업데이트하려다 HiveObject의 편의 메서드가 있다는 걸 알고 코드를 줄였다.
마무리
Hive의 핵심은 "Dart 객체 그대로 저장"이다. SQL도 없고, 스키마 마이그레이션도 없다. @HiveType + @HiveField로 모델을 정의하고, build_runner로 어댑터를 생성하면 끝. box.listenable()로 ValueListenableBuilder와 연결하면 상태 관리 없이도 반응형 UI가 된다.
sqflite와 비교해서 단순한 CRUD 앱엔 Hive가 훨씬 빠르게 개발할 수 있다. 복잡한 관계형 쿼리가 필요해지는 시점에 sqflite나 drift로 넘어가면 된다.
기술 스택: Flutter 3.32 · Dart 3.8 · hive_flutter 1.1.0 · hive_generator 2.0.1 · build_runner · iOS · Android
소스 코드: GitHub
'튜토리얼 > 앱' 카테고리의 다른 글
| [Flutter] 뽀모도로 타이머 앱 만들기 — 로컬 알림 + 커스텀 링 UI (1) | 2026.03.03 |
|---|---|
| [Flutter] QR 코드 생성/스캔 앱 만들기 — qr_flutter + mobile_scanner (0) | 2026.03.03 |
| [Flutter] 가계부 앱 만들기 — sqflite로 로컬 저장 구현 (0) | 2026.03.03 |