Hive

Hiveは「Dart向けの軽量で高速なキー・バリューデータベース」として開発された、FlutterアプリケーションでのローカルデータストレージのためのNoSQLデータベースです。純粋なDartで書かれており、Flutter、Dart VM、Dart2JSで動作し、外部依存関係を持ちません。従来のSQLiteやCoreDataと比較して大幅に高速で、型安全性を維持しながら簡単なAPI設計を実現しています。アダプター機能により任意のDartオブジェクトを永続化でき、暗号化、圧縮、バックアップ機能を標準装備した現代的なFlutterアプリケーション開発における主要なローカルストレージソリューションです。

NoSQLFlutterDartデータベースキー・バリューローカル

GitHub概要

isar/hive

Lightweight and blazing fast key-value database written in pure Dart.

スター4,280
ウォッチ63
フォーク431
作成日:2019年7月8日
言語:Dart
ライセンス:Apache License 2.0

トピックス

dartdatabaseencryptionflutterhivekey-valuenosql

スター履歴

isar/hive Star History
データ取得日時: 2025/7/17 06:57

ライブラリ

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();
}