Lesson 45 of 51 intermediate

Hive Local Database

Local CRUD — Create, Read, Update & Delete on Device

Open interactive version (quiz + challenge)

Real-world analogy

Hive is like a set of labeled filing cabinets in your office. Each cabinet is a 'box' with a name like 'settings' or 'user_cache'. You open a cabinet, put documents in (put), take them out (get), or throw them away (delete). The documents stay there even if you turn off the lights and come back tomorrow -- that is persistence!

What is it?

Hive is a lightweight, fast, NoSQL key-value database for Flutter that stores data on the device as encrypted binary files. It works completely offline and persists data between app launches. In team_mvp_kit, a LocalDatabaseService singleton wraps Hive to provide caching, token storage, and offline data access throughout the app.

Real-world relevance

team_mvp_kit uses Hive for three critical features: 1) Token storage (encrypted box for access/refresh tokens), 2) API response caching (users can see data even offline), 3) User preferences (theme, language, settings). The LocalDatabaseService abstracts Hive behind a clean interface so it could be swapped for another database without changing any other code.

Key points

Code example

import 'package:hive_flutter/hive_flutter.dart';
import 'package:injectable/injectable.dart';

// 1. LocalDatabaseService wraps Hive (from team_mvp_kit)
@singleton
class LocalDatabaseService {
  static const _boxName = 'app_data';
  static const _secureBoxName = 'secure_data';
  late Box _box;
  late Box _secureBox;

  Future<void> init() async {
    await Hive.initFlutter();
    _box = await Hive.openBox(_boxName);
    // Encrypted box for sensitive data
    final encKey = await _getOrCreateEncryptionKey();
    _secureBox = await Hive.openBox(
      _secureBoxName,
      encryptionCipher: HiveAesCipher(encKey),
    );
  }

  // Regular data operations
  T? get<T>(String key) => _box.get(key) as T?;

  Future<void> put(String key, dynamic value) =>
      _box.put(key, value);

  Future<void> delete(String key) => _box.delete(key);

  bool containsKey(String key) => _box.containsKey(key);

  Future<void> clear() => _box.clear();

  // Secure data operations (for tokens)
  String? getSecure(String key) => _secureBox.get(key) as String?;

  Future<void> putSecure(String key, String value) =>
      _secureBox.put(key, value);

  Future<void> deleteSecure(String key) =>
      _secureBox.delete(key);

  Future<void> clearSecure() => _secureBox.clear();

  Future<List<int>> _getOrCreateEncryptionKey() async {
    // In production: store key in flutter_secure_storage
    final existing = _box.get('_enc_key') as List<int>?;
    if (existing != null) return existing;
    final key = Hive.generateSecureKey();
    await _box.put('_enc_key', key);
    return key;
  }
}

// 2. Using LocalDatabaseService for caching
@LazySingleton(as: UserRepository)
class UserRepositoryImpl implements UserRepository {
  final ApiService _api;
  final LocalDatabaseService _db;

  UserRepositoryImpl(this._api, this._db);

  @override
  Future<List<User>> getUsers() async {
    try {
      final json = await _api.get('/users');
      await _db.put('cached_users', json);
      return _parseUsers(json);
    } catch (e) {
      final cached = _db.get<Map>('cached_users');
      if (cached != null) return _parseUsers(cached);
      rethrow;
    }
  }

  List<User> _parseUsers(Map data) {
    return (data['data'] as List)
        .map((e) => UserDto.fromJson(
            Map<String, dynamic>.from(e)))
        .map((dto) => User(
            id: dto.id, name: dto.fullName))
        .toList();
  }
}

Line-by-line walkthrough

  1. 1. Import hive_flutter for Flutter-specific Hive features
  2. 2. Import injectable for DI annotations
  3. 3.
  4. 4. Comment: LocalDatabaseService wraps Hive
  5. 5. @singleton ensures one instance for the entire app
  6. 6. Class declaration for LocalDatabaseService
  7. 7. Constant for the regular box name
  8. 8. Constant for the encrypted box name
  9. 9. Late field for the regular box (initialized in init)
  10. 10. Late field for the encrypted box
  11. 11.
  12. 12. init() method to set up Hive and open boxes
  13. 13. Initialize Hive with Flutter's document directory
  14. 14. Open the regular box for general data
  15. 15. Generate or get the encryption key for secure box
  16. 16. Open the encrypted box with AES cipher
  17. 17. Closing init method
  18. 18.
  19. 19. Comment: Regular data operations section
  20. 20. Generic get method casts stored value to type T
  21. 21. Returns null if key does not exist
  22. 22.
  23. 23. put stores any value with a string key
  24. 24. Delegates to the Hive box put method
  25. 25.
  26. 26. delete removes a value by key
  27. 27.
  28. 28. containsKey checks if a key exists in the box
  29. 29.
  30. 30. clear removes all data from the regular box
  31. 31.
  32. 32. Comment: Secure data operations for tokens
  33. 33. getSecure reads from the encrypted box
  34. 34. Returns null if key does not exist
  35. 35.
  36. 36. putSecure writes to the encrypted box
  37. 37.
  38. 38. deleteSecure removes from the encrypted box
  39. 39.
  40. 40. clearSecure wipes the entire encrypted box
  41. 41.
  42. 42. Private helper to get or create the encryption key
  43. 43. Check if an encryption key already exists
  44. 44. If it exists, return it
  45. 45. Generate a new secure key using Hive
  46. 46. Save it for future use
  47. 47. Return the key
  48. 48. Closing the helper and LocalDatabaseService class
  49. 49.
  50. 50. Comment: Using the service in a repository
  51. 51. @LazySingleton registers this as UserRepository
  52. 52. UserRepositoryImpl uses both API and local database
  53. 53. ApiService for network calls
  54. 54. LocalDatabaseService for caching
  55. 55.
  56. 56. Constructor receives both via DI
  57. 57.
  58. 58. getUsers tries network first, falls back to cache
  59. 59. Try block for network request
  60. 60. Fetch users from API
  61. 61. Cache the response locally
  62. 62. Parse and return users from API response
  63. 63. Catch block for network failures
  64. 64. Try to get cached data
  65. 65. If cache exists, parse and return it
  66. 66. If no cache either, rethrow the original error
  67. 67. Closing getUsers
  68. 68.
  69. 69. Helper to parse user data from a map
  70. 70. Convert JSON list to UserDto list
  71. 71. Map DTOs to domain User entities
  72. 72. Return the list

Spot the bug

void main() async {
  final box = Hive.box('settings');
  await box.put('theme', 'dark');
  final theme = box.get('theme');
  print(theme);
}
Need a hint?
Think about the lifecycle of a Hive box...
Show answer
The box is accessed with Hive.box('settings') but was never opened first. You must call await Hive.initFlutter() and then await Hive.openBox('settings') before using Hive.box(). Also WidgetsFlutterBinding.ensureInitialized() is needed before any async operations in main().

Explain like I'm 5

Imagine you have a magic notebook that never forgets. When you write 'favorite color = blue' on a page (put), the notebook remembers it forever -- even if you close it and open it tomorrow. You can read any page (get), erase a page (delete), or rip out all pages (clear). Hive is that magic notebook for your app. It remembers things even after you close and reopen the app!

Fun fact

Hive is named after the beehive because, like bees storing honey in hexagonal cells, Hive stores your data in efficient binary containers. It was created by Simon Leier from Germany and is one of the fastest local databases available for Flutter -- up to 100x faster than SQLite for simple key-value operations!

Hands-on challenge

Create a simple SettingsService that uses Hive to store and retrieve user preferences: theme (String), fontSize (int), and notificationsEnabled (bool). Include methods for getTheme(), setTheme(), getFontSize(), setFontSize(), and resetAll(). Initialize Hive in main() and test your service.

More resources

Open interactive version (quiz + challenge) ← Back to course: Flutter & Dart