Hive
Hiveは「Dart向けの軽量で高速なキー・バリューデータベース」として開発された、FlutterアプリケーションでのローカルデータストレージのためのNoSQLデータベースです。純粋なDartで書かれており、Flutter、Dart VM、Dart2JSで動作し、外部依存関係を持ちません。従来のSQLiteやCoreDataと比較して大幅に高速で、型安全性を維持しながら簡単なAPI設計を実現しています。アダプター機能により任意のDartオブジェクトを永続化でき、暗号化、圧縮、バックアップ機能を標準装備した現代的なFlutterアプリケーション開発における主要なローカルストレージソリューションです。
GitHub概要
isar/hive
Lightweight and blazing fast key-value database written in pure Dart.
トピックス
スター履歴
ライブラリ
Hive
概要
Hiveは「Dart向けの軽量で高速なキー・バリューデータベース」として開発された、FlutterアプリケーションでのローカルデータストレージのためのNoSQLデータベースです。純粋なDartで書かれており、Flutter、Dart VM、Dart2JSで動作し、外部依存関係を持ちません。従来のSQLiteやCoreDataと比較して大幅に高速で、型安全性を維持しながら簡単なAPI設計を実現しています。アダプター機能により任意のDartオブジェクトを永続化でき、暗号化、圧縮、バックアップ機能を標準装備した現代的なFlutterアプリケーション開発における主要なローカルストレージソリューションです。
詳細
Hive 2025年版は、Dart 3.0とFlutter 3.16以降に完全対応し、null safetyの恩恵を最大限活用した高性能データベースエンジンです。遅延ローディング、ウォッチ機能によるリアクティブな状態管理、スマートキャッシングにより、モバイルアプリケーションに最適化されたパフォーマンスを提供します。JSON形式でのデータ保存により人間にも読みやすく、カスタムアダプターによって複雑なオブジェクト構造も効率的に保存可能。AES暗号化のサポート、コンパクトなディスク使用量、高速な読み書き性能により、オフラインファーストなFlutterアプリケーション開発の標準的選択肢となっています。
主な特徴
- 高速パフォーマンス: SQLiteより最大10倍高速な読み書き性能
- 純粋Dart実装: 外部依存関係なし、全プラットフォーム対応
- 型安全: TypeAdapterによる強力な型システム統合
- リアクティブ: Streamベースのデータ変更監視機能
- 暗号化対応: AES-256暗号化によるセキュアなデータ保存
- 軽量設計: 最小限のメモリ使用量とディスク容量
メリット・デメリット
メリット
- SQLiteより大幅に高速で、メモリ効率が優秀
- セットアップが非常に簡単で、学習コストが低い
- Flutter/Dartエコシステムとの完璧な統合
- リアクティブプログラミングとの親和性が高い
- 暗号化とセキュリティ機能が標準装備
- オフライン対応とローカルファーストアーキテクチャに最適
デメリット
- 関係型データベースのような複雑な関係性表現には不向き
- 大容量データや複雑なクエリには制限がある
- トランザクション機能が基本的でACIDプロパティが限定的
- 複数アプリケーション間でのデータ共有は困難
- バックアップ・復元機能は手動実装が必要
- SQLライクなクエリ言語は提供されない
参考ページ
書き方の例
セットアップ
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.9
// main.dart - 初期化
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
void main() async {
// Hiveの初期化
await Hive.initFlutter();
// カスタムアダプターの登録
Hive.registerAdapter(PersonAdapter());
Hive.registerAdapter(AddressAdapter());
runApp(MyApp());
}
基本的な使い方
// モデルクラスの定義
import 'package:hive/hive.dart';
part 'person.g.dart'; // 生成されるファイル
@HiveType(typeId: 0)
class Person extends HiveObject {
@HiveField(0)
String name;
@HiveField(1)
int age;
@HiveField(2)
String email;
@HiveField(3)
List<String> hobbies;
@HiveField(4)
Address? address;
Person({
required this.name,
required this.age,
required this.email,
this.hobbies = const [],
this.address,
});
@override
String toString() {
return 'Person(name: $name, age: $age, email: $email)';
}
}
@HiveType(typeId: 1)
class Address {
@HiveField(0)
String street;
@HiveField(1)
String city;
@HiveField(2)
String postalCode;
@HiveField(3)
String country;
Address({
required this.street,
required this.city,
required this.postalCode,
required this.country,
});
@override
String toString() {
return 'Address(street: $street, city: $city, postalCode: $postalCode, country: $country)';
}
}
// アダプター生成コマンド:
// dart run build_runner build
データ操作
// データベース操作クラス
class PersonDatabase {
static const String _boxName = 'persons';
// ボックスを開く
static Future<Box<Person>> _getBox() async {
if (!Hive.isBoxOpen(_boxName)) {
return await Hive.openBox<Person>(_boxName);
}
return Hive.box<Person>(_boxName);
}
// 人物を追加
static Future<void> addPerson(Person person) async {
final box = await _getBox();
await box.add(person);
}
// 人物を更新(キーで指定)
static Future<void> updatePerson(int key, Person person) async {
final box = await _getBox();
await box.put(key, person);
}
// 人物を削除
static Future<void> deletePerson(int key) async {
final box = await _getBox();
await box.delete(key);
}
// 全ての人物を取得
static Future<List<Person>> getAllPersons() async {
final box = await _getBox();
return box.values.toList();
}
// 特定の人物を取得
static Future<Person?> getPerson(int key) async {
final box = await _getBox();
return box.get(key);
}
// 検索機能
static Future<List<Person>> searchPersons(String query) async {
final box = await _getBox();
return box.values.where((person) {
return person.name.toLowerCase().contains(query.toLowerCase()) ||
person.email.toLowerCase().contains(query.toLowerCase());
}).toList();
}
// 年齢でフィルタ
static Future<List<Person>> getPersonsByAgeRange(int minAge, int maxAge) async {
final box = await _getBox();
return box.values.where((person) {
return person.age >= minAge && person.age <= maxAge;
}).toList();
}
// ボックスをクリア
static Future<void> clearAll() async {
final box = await _getBox();
await box.clear();
}
// ボックスを閉じる
static Future<void> closeBox() async {
final box = await _getBox();
await box.close();
}
}
// 使用例
void demonstrateBasicOperations() async {
// データ追加
final person1 = Person(
name: '田中太郎',
age: 30,
email: '[email protected]',
hobbies: ['読書', 'プログラミング'],
address: Address(
street: '渋谷1-1-1',
city: '東京',
postalCode: '150-0002',
country: '日本',
),
);
await PersonDatabase.addPerson(person1);
// 複数のデータ追加
final persons = [
Person(name: '佐藤花子', age: 25, email: '[email protected]'),
Person(name: '鈴木次郎', age: 35, email: '[email protected]'),
Person(name: '高橋美咲', age: 28, email: '[email protected]'),
];
for (final person in persons) {
await PersonDatabase.addPerson(person);
}
// データ取得
final allPersons = await PersonDatabase.getAllPersons();
print('Total persons: ${allPersons.length}');
// 検索
final searchResults = await PersonDatabase.searchPersons('田中');
print('Search results: $searchResults');
// 年齢フィルタ
final youngAdults = await PersonDatabase.getPersonsByAgeRange(20, 30);
print('Young adults: $youngAdults');
}
ウォッチ機能とリアクティブUI
// リアクティブUIの実装
class PersonListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Person List')),
body: ValueListenableBuilder<Box<Person>>(
valueListenable: Hive.box<Person>('persons').listenable(),
builder: (context, box, _) {
final persons = box.values.toList();
if (persons.isEmpty) {
return Center(
child: Text('No persons found'),
);
}
return ListView.builder(
itemCount: persons.length,
itemBuilder: (context, index) {
final person = persons[index];
final key = box.keyAt(index);
return ListTile(
title: Text(person.name),
subtitle: Text('${person.age}歳 - ${person.email}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () => _editPerson(context, key, person),
),
IconButton(
icon: Icon(Icons.delete),
onPressed: () => _deletePerson(key),
),
],
),
onTap: () => _showPersonDetails(context, person),
);
},
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _addPerson(context),
child: Icon(Icons.add),
),
);
}
void _addPerson(BuildContext context) {
showDialog(
context: context,
builder: (context) => PersonFormDialog(),
);
}
void _editPerson(BuildContext context, dynamic key, Person person) {
showDialog(
context: context,
builder: (context) => PersonFormDialog(key: key, person: person),
);
}
void _deletePerson(dynamic key) async {
await PersonDatabase.deletePerson(key);
}
void _showPersonDetails(BuildContext context, Person person) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(person.name),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('年齢: ${person.age}'),
Text('メール: ${person.email}'),
if (person.hobbies.isNotEmpty)
Text('趣味: ${person.hobbies.join(', ')}'),
if (person.address != null)
Text('住所: ${person.address.toString()}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('閉じる'),
),
],
),
);
}
}
// フォームダイアログ
class PersonFormDialog extends StatefulWidget {
final dynamic key;
final Person? person;
PersonFormDialog({this.key, this.person});
@override
_PersonFormDialogState createState() => _PersonFormDialogState();
}
class _PersonFormDialogState extends State<PersonFormDialog> {
final _formKey = GlobalKey<FormState>();
late TextEditingController _nameController;
late TextEditingController _ageController;
late TextEditingController _emailController;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.person?.name ?? '');
_ageController = TextEditingController(text: widget.person?.age.toString() ?? '');
_emailController = TextEditingController(text: widget.person?.email ?? '');
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(widget.person == null ? '新しい人物を追加' : '人物を編集'),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
controller: _nameController,
decoration: InputDecoration(labelText: '名前'),
validator: (value) {
if (value == null || value.isEmpty) {
return '名前を入力してください';
}
return null;
},
),
TextFormField(
controller: _ageController,
decoration: InputDecoration(labelText: '年齢'),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return '年齢を入力してください';
}
if (int.tryParse(value) == null) {
return '有効な数値を入力してください';
}
return null;
},
),
TextFormField(
controller: _emailController,
decoration: InputDecoration(labelText: 'メールアドレス'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) {
return 'メールアドレスを入力してください';
}
return null;
},
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('キャンセル'),
),
ElevatedButton(
onPressed: _savePerson,
child: Text('保存'),
),
],
);
}
void _savePerson() async {
if (_formKey.currentState!.validate()) {
final person = Person(
name: _nameController.text,
age: int.parse(_ageController.text),
email: _emailController.text,
);
if (widget.key == null) {
await PersonDatabase.addPerson(person);
} else {
await PersonDatabase.updatePerson(widget.key, person);
}
Navigator.of(context).pop();
}
}
@override
void dispose() {
_nameController.dispose();
_ageController.dispose();
_emailController.dispose();
super.dispose();
}
}
暗号化とセキュリティ
// 暗号化ボックスの作成と使用
import 'dart:convert';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
class SecurePersonDatabase {
static const String _boxName = 'secure_persons';
// 暗号化キーの生成
static Uint8List _generateKey(String password) {
final bytes = utf8.encode(password);
final digest = sha256.convert(bytes);
return Uint8List.fromList(digest.bytes);
}
// 暗号化されたボックスを開く
static Future<Box<Person>> _getSecureBox(String password) async {
final key = _generateKey(password);
if (!Hive.isBoxOpen(_boxName)) {
return await Hive.openBox<Person>(
_boxName,
encryptionCipher: HiveAesCipher(key),
);
}
return Hive.box<Person>(_boxName);
}
// セキュアなデータ追加
static Future<void> addSecurePerson(Person person, String password) async {
final box = await _getSecureBox(password);
await box.add(person);
}
// セキュアなデータ取得
static Future<List<Person>> getSecurePersons(String password) async {
try {
final box = await _getSecureBox(password);
return box.values.toList();
} catch (e) {
print('Wrong password or corrupted data: $e');
return [];
}
}
// バックアップ作成
static Future<void> createBackup(String password, String backupPath) async {
final box = await _getSecureBox(password);
final persons = box.values.toList();
final backupData = {
'timestamp': DateTime.now().toIso8601String(),
'version': '1.0',
'data': persons.map((p) => {
'name': p.name,
'age': p.age,
'email': p.email,
'hobbies': p.hobbies,
'address': p.address != null ? {
'street': p.address!.street,
'city': p.address!.city,
'postalCode': p.address!.postalCode,
'country': p.address!.country,
} : null,
}).toList(),
};
// ここで実際のファイル書き込み処理を行う
// File(backupPath).writeAsString(jsonEncode(backupData));
print('Backup created: ${jsonEncode(backupData)}');
}
// バックアップからの復元
static Future<void> restoreFromBackup(String backupPath, String password) async {
// ここで実際のファイル読み込み処理を行う
// final backupContent = await File(backupPath).readAsString();
// final backupData = jsonDecode(backupContent);
// デモ用のサンプルデータ
final backupData = {
'data': [
{
'name': 'Restored User',
'age': 30,
'email': '[email protected]',
'hobbies': ['復元', 'テスト'],
}
]
};
final box = await _getSecureBox(password);
await box.clear(); // 既存データをクリア
for (final personData in backupData['data']) {
final person = Person(
name: personData['name'],
age: personData['age'],
email: personData['email'],
hobbies: List<String>.from(personData['hobbies'] ?? []),
);
await box.add(person);
}
}
}
// セキュリティ機能の使用例
void demonstrateSecurityFeatures() async {
const password = 'my_secure_password_123';
// 暗号化されたデータの保存
final securePerson = Person(
name: 'セキュアユーザー',
age: 35,
email: '[email protected]',
hobbies: ['セキュリティ', '暗号化'],
);
await SecurePersonDatabase.addSecurePerson(securePerson, password);
// 暗号化されたデータの取得
final securePersons = await SecurePersonDatabase.getSecurePersons(password);
print('Secure persons: $securePersons');
// バックアップ作成
await SecurePersonDatabase.createBackup(password, 'backup.json');
// バックアップからの復元
await SecurePersonDatabase.restoreFromBackup('backup.json', password);
}
エラーハンドリング
// エラーハンドリングを含む包括的なデータベースマネージャー
class RobustPersonDatabase {
static const String _boxName = 'robust_persons';
// 安全なボックス取得
static Future<Box<Person>?> _getSafeBox() async {
try {
if (!Hive.isBoxOpen(_boxName)) {
return await Hive.openBox<Person>(_boxName);
}
return Hive.box<Person>(_boxName);
} catch (e) {
print('Error opening box: $e');
return null;
}
}
// 安全なデータ追加
static Future<bool> addPersonSafely(Person person) async {
try {
final box = await _getSafeBox();
if (box == null) return false;
await box.add(person);
return true;
} catch (e) {
print('Error adding person: $e');
return false;
}
}
// 安全なデータ更新
static Future<bool> updatePersonSafely(int key, Person person) async {
try {
final box = await _getSafeBox();
if (box == null) return false;
if (box.containsKey(key)) {
await box.put(key, person);
return true;
} else {
print('Key $key not found');
return false;
}
} catch (e) {
print('Error updating person: $e');
return false;
}
}
// 安全なデータ削除
static Future<bool> deletePersonSafely(int key) async {
try {
final box = await _getSafeBox();
if (box == null) return false;
if (box.containsKey(key)) {
await box.delete(key);
return true;
} else {
print('Key $key not found');
return false;
}
} catch (e) {
print('Error deleting person: $e');
return false;
}
}
// 安全なデータ取得
static Future<List<Person>> getPersonsSafely() async {
try {
final box = await _getSafeBox();
if (box == null) return [];
return box.values.toList();
} catch (e) {
print('Error getting persons: $e');
return [];
}
}
// データベースの状態チェック
static Future<Map<String, dynamic>> getDatabaseStatus() async {
try {
final box = await _getSafeBox();
if (box == null) {
return {
'status': 'error',
'message': 'Cannot open database',
'count': 0,
'size': 0,
};
}
return {
'status': 'healthy',
'message': 'Database is operational',
'count': box.length,
'size': box.values.length,
'path': box.path,
'isOpen': box.isOpen,
};
} catch (e) {
return {
'status': 'error',
'message': 'Error checking database: $e',
'count': 0,
'size': 0,
};
}
}
// データ整合性チェック
static Future<Map<String, dynamic>> performIntegrityCheck() async {
try {
final box = await _getSafeBox();
if (box == null) {
return {'passed': false, 'errors': ['Cannot open database']};
}
final errors = <String>[];
int validCount = 0;
for (int i = 0; i < box.length; i++) {
try {
final person = box.getAt(i);
if (person == null) {
errors.add('Null person at index $i');
continue;
}
// 基本的な検証
if (person.name.isEmpty) {
errors.add('Empty name at index $i');
}
if (person.age < 0 || person.age > 150) {
errors.add('Invalid age at index $i: ${person.age}');
}
if (!person.email.contains('@')) {
errors.add('Invalid email at index $i: ${person.email}');
}
validCount++;
} catch (e) {
errors.add('Error reading person at index $i: $e');
}
}
return {
'passed': errors.isEmpty,
'errors': errors,
'validCount': validCount,
'totalCount': box.length,
};
} catch (e) {
return {
'passed': false,
'errors': ['Integrity check failed: $e'],
'validCount': 0,
'totalCount': 0,
};
}
}
// クリーンアップとメンテナンス
static Future<void> performMaintenance() async {
try {
final box = await _getSafeBox();
if (box == null) return;
// 重複データの除去
final seen = <String>{};
final toDelete = <int>[];
for (int i = 0; i < box.length; i++) {
final person = box.getAt(i);
if (person != null) {
final key = '${person.name}_${person.email}';
if (seen.contains(key)) {
toDelete.add(i);
} else {
seen.add(key);
}
}
}
// 重複データの削除
for (final index in toDelete.reversed) {
await box.deleteAt(index);
}
// データベースの圧縮
await box.compact();
print('Maintenance completed. Removed ${toDelete.length} duplicates.');
} catch (e) {
print('Maintenance failed: $e');
}
}
}
// エラーハンドリングの使用例
void demonstrateErrorHandling() async {
// データベース状態の確認
final status = await RobustPersonDatabase.getDatabaseStatus();
print('Database status: $status');
// 安全なデータ操作
final person = Person(
name: 'Test User',
age: 25,
email: '[email protected]',
);
final added = await RobustPersonDatabase.addPersonSafely(person);
if (added) {
print('Person added successfully');
} else {
print('Failed to add person');
}
// 整合性チェック
final integrityResult = await RobustPersonDatabase.performIntegrityCheck();
print('Integrity check: $integrityResult');
// メンテナンス実行
await RobustPersonDatabase.performMaintenance();
}