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
- What is Hive? — Hive is a lightweight, blazing-fast key-value database for Flutter. Data is stored on the device as binary files, so it works completely offline. It is perfect for caching API responses, storing user preferences, saving auth tokens, and any data you need to persist between app launches.
- Setting Up Hive — Initialize Hive in main() before runApp(). On Flutter, use Hive.initFlutter() from hive_flutter package which automatically sets the correct storage directory. Then open the boxes you need. In team_mvp_kit, Hive init happens inside the dependency injection setup.
- Boxes - Storage Containers — A Box is a named storage container. You can have multiple boxes for different types of data: one for settings, one for cached users, one for auth tokens. Each box is independent. Open a box before using it, and use Hive.box() to access an already-opened box from anywhere.
- Put, Get, Delete Operations — Use put(key, value) to store data, get(key) to retrieve it, delete(key) to remove it, and clear() to empty the entire box. Keys are strings, values can be primitives (String, int, bool, double), Lists, Maps, or custom Hive objects.
- Type Adapters for Custom Objects — Hive can only store primitives and basic collections by default. To store custom Dart objects, create a TypeAdapter or use @HiveType and @HiveField annotations with hive_generator. Each type needs a unique typeId number that never changes once assigned.
- The LocalDatabaseService in team_mvp_kit — team_mvp_kit wraps Hive in a LocalDatabaseService class registered as a @singleton. This service provides typed get/put/delete methods and handles box initialization. Other services depend on LocalDatabaseService instead of using Hive directly, keeping Hive as an implementation detail.
- Caching API Responses — A common pattern is to cache API responses in Hive for offline access. When the user has internet, fetch from the API and save to Hive. When offline, serve the cached data. This gives instant loading on repeat visits and graceful offline support.
- Encrypted Boxes — For sensitive data like tokens and user credentials, use Hive's encrypted boxes. Generate an encryption key, store it securely with flutter_secure_storage, and pass it when opening the box. team_mvp_kit uses encrypted boxes for auth token storage.
- Listening to Changes — Hive boxes support listenable() to watch for changes reactively. Use ValueListenableBuilder in Flutter to automatically rebuild widgets when box data changes. This is great for settings screens or any UI that should update when local data changes.
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. Import hive_flutter for Flutter-specific Hive features
- 2. Import injectable for DI annotations
- 3.
- 4. Comment: LocalDatabaseService wraps Hive
- 5. @singleton ensures one instance for the entire app
- 6. Class declaration for LocalDatabaseService
- 7. Constant for the regular box name
- 8. Constant for the encrypted box name
- 9. Late field for the regular box (initialized in init)
- 10. Late field for the encrypted box
- 11.
- 12. init() method to set up Hive and open boxes
- 13. Initialize Hive with Flutter's document directory
- 14. Open the regular box for general data
- 15. Generate or get the encryption key for secure box
- 16. Open the encrypted box with AES cipher
- 17. Closing init method
- 18.
- 19. Comment: Regular data operations section
- 20. Generic get method casts stored value to type T
- 21. Returns null if key does not exist
- 22.
- 23. put stores any value with a string key
- 24. Delegates to the Hive box put method
- 25.
- 26. delete removes a value by key
- 27.
- 28. containsKey checks if a key exists in the box
- 29.
- 30. clear removes all data from the regular box
- 31.
- 32. Comment: Secure data operations for tokens
- 33. getSecure reads from the encrypted box
- 34. Returns null if key does not exist
- 35.
- 36. putSecure writes to the encrypted box
- 37.
- 38. deleteSecure removes from the encrypted box
- 39.
- 40. clearSecure wipes the entire encrypted box
- 41.
- 42. Private helper to get or create the encryption key
- 43. Check if an encryption key already exists
- 44. If it exists, return it
- 45. Generate a new secure key using Hive
- 46. Save it for future use
- 47. Return the key
- 48. Closing the helper and LocalDatabaseService class
- 49.
- 50. Comment: Using the service in a repository
- 51. @LazySingleton registers this as UserRepository
- 52. UserRepositoryImpl uses both API and local database
- 53. ApiService for network calls
- 54. LocalDatabaseService for caching
- 55.
- 56. Constructor receives both via DI
- 57.
- 58. getUsers tries network first, falls back to cache
- 59. Try block for network request
- 60. Fetch users from API
- 61. Cache the response locally
- 62. Parse and return users from API response
- 63. Catch block for network failures
- 64. Try to get cached data
- 65. If cache exists, parse and return it
- 66. If no cache either, rethrow the original error
- 67. Closing getUsers
- 68.
- 69. Helper to parse user data from a map
- 70. Convert JSON list to UserDto list
- 71. Map DTOs to domain User entities
- 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
- Hive Package (pub.dev)
- Hive Flutter Package (pub.dev)
- Hive Documentation (Hive Official)