ðĶ Hive Box Manager
Type-safe, FP-style abstraction layers for Hive's boxes. Provides Managers for almost all conceivable use-cases and scenarios.
ð Table of Contents
ðŊ Understanding Box Types
Box vs LazyBox
The fundamental distinction in Hive (and this library) is between Box and LazyBox. Understanding when to use each is crucial for optimal performance.
ðĶ Box (Eager Loading)
What it does: Loads all data into memory when opened.
â Pros:
- ⥠Instant reads - Data is already in memory (synchronous access)
- ðŊ Simple API - No async/await needed for reads
- ð Best for frequent access - Perfect when you need to read data repeatedly
- ðŠ Predictable performance - No I/O delays during reads
â Cons:
- ðū High memory usage - Entire box contents loaded into RAM
- ð Slower startup - Takes time to load all data when opening
- â ïļ Not suitable for large datasets - Can cause memory issues with >10MB of data
- ðą Mobile unfriendly - Limited RAM on mobile devices
When to use:
- Small datasets (< 1MB)
- Frequently accessed data (e.g., user preferences, app settings)
- Data that needs to be read synchronously
- When memory is not a constraint
ðĪ LazyBox (Lazy Loading)
What it does: Loads data on-demand from disk only when requested.
â Pros:
- ðŠķ Minimal memory footprint - Only loads what you need
- ⥠Fast startup - Opens instantly regardless of data size
- ð Scales well - Can handle large datasets (100MB+)
- ðą Mobile friendly - Conserves precious device memory
â Cons:
- âąïļ Async reads - Every read requires disk I/O (slower)
- ð More complex API - Must use async/await for all operations
- ð Slower for frequent access - Repeated reads hit disk each time
- ðŧ I/O overhead - Each access has file system overhead
When to use:
- Large datasets (> 1MB)
- Infrequently accessed data (e.g., cached images, historical logs)
- Memory-constrained environments (mobile apps)
- When you only need specific items, not the whole dataset
ð ïļ Manager Types
1. Simple Managers (BoxManager & LazyBoxManager)
Purpose: Multi-item storage with custom indices (int or String keys).
Use cases:
- Storing multiple users, products, or entities
- Key-value storage where you control the keys
- Collections that need individual item access
Example:
// Eager loading - all users in memory
final userBox = BoxManager<User, int>(
boxKey: 'users',
defaultValue: User.empty(),
);
// Lazy loading - users loaded on demand
final userLazyBox = LazyBoxManager<User, String>(
boxKey: 'users_lazy',
defaultValue: User.empty(),
);
// Usage
await userBox.init();
await userBox.put(index: 1, value: user1).run(); // Synchronous read later
final user = userBox.get(1); // Instant!
await userLazyBox.init();
await userLazyBox.put(index: 'user_123', value: user2).run();
final user2 = await userLazyBox.get('user_123').run(); // Async read
Trade-offs:
| Aspect | BoxManager | LazyBoxManager |
|---|---|---|
| Read Speed | ⥠Instant | ð Disk I/O |
| Memory | ðū High | ðŠķ Low |
| Startup | ð Slow | ⥠Fast |
| Best for | Small, frequent | Large, occasional |
2. Single Index Managers
Purpose: Store one value per box (like a single setting or configuration).
Why needed: Simplified API when you only need to store a single value without managing indices.
Variants:
SingleIndexBoxManager<T>- Eager loadingSingleIndexLazyBoxManager<T>- Lazy loading
Use cases:
- App theme preference
- User authentication token
- Last sync timestamp
- App configuration object
Example:
final themeBox = SingleIndexBoxManager<AppTheme>(
boxKey: 'app_theme',
defaultValue: AppTheme.light,
);
await themeBox.init();
await themeBox.put(value: AppTheme.dark).run();
final theme = themeBox.get(); // No index needed!
Trade-offs:
- â Simpler API - No index management
- â Clear intent - Obvious it's a single value
- â Limited to one value - Can't store multiple items
- ðĄ Choose Lazy variant if the value is large (e.g., cached JSON)
3. Collection Managers
Purpose: Store collections (Lists, Sets) of custom types as values.
Why needed: Hive has limitations reading Iterable<CustomType> directly (issue #150). This manager works around that limitation.
Variant:
CollectionLazyBoxManager<T, I>- StoresIterable<T>at each index
Use cases:
- Storing lists of items per category
- Multiple tags per entity
- Historical records grouped by date
- Batch data storage
Example:
final tagsBox = CollectionLazyBoxManager<String, int>(
boxKey: 'post_tags',
defaultValue: <String>[],
);
await tagsBox.init();
await tagsBox.put(
index: 1, // Post ID
value: ['flutter', 'dart', 'mobile'],
).run();
final tags = await tagsBox.get(1).run(); // Returns Iterable<String>
Trade-offs:
- â Solves Hive limitation - Enables storing custom type collections
- â Type-safe - Proper generic typing
- â ïļ Lazy only - No eager variant (memory concerns with collections)
- ðĄ Use for grouped data - Perfect for one-to-many relationships
4. Dual Index Managers
Purpose: Store data with two indices (composite keys) - like a 2D grid or matrix.
Why needed: When your data naturally has two dimensions (e.g., user + date, row + column).
Variants:
A. Standard Dual Index Managers
Store and retrieve by two indices simultaneously.
DualIntIndexBoxManager<T>- Eager, int + int indicesDualIntIndexLazyBoxManager<T>- Lazy, int + int indices
Encoding Strategy: Bit-Shift
â Pros:
- ⥠Maximum performance - Bit operations are the fastest CPU operations
- ðŊ Perfect distribution - Uses all 32 bits efficiently (16 bits each)
- ðū No wasted space - Can represent 0 to 65,536 for both indices
- ð Collision-free - Mathematically proven unique encoding
â Cons:
- ð Fixed range - Limited to 16-bit integers (0-65,536)
- â No negative numbers - Without additional handling
- ðģïļ Not great for sparse data - Wastes encoding space if data is sparse
- â ïļ Platform dependency - Potential issues if Dart's integer behavior changes
Use cases:
- Grid-based games (x, y coordinates)
- Time-series data (user ID + day index)
- Matrix storage (row + column)
- Relational data with two keys
Example:
final gameBoard = DualIntIndexLazyBoxManager<Tile>.bitShift(
boxKey: 'game_tiles',
defaultValue: Tile.empty(),
);
await gameBoard.init();
await gameBoard.put(
primaryIndex: 5, // X coordinate
secondaryIndex: 10, // Y coordinate
value: tile,
).run();
final tile = await gameBoard.get(
primaryIndex: 5,
secondaryIndex: 10,
).run();
B. Query Dual Index Managers
Purpose: Retrieve data by either index independently (reverse lookups).
QueryDualIntIndexBoxManager<T>- Eager variantQueryDualIntIndexLazyBoxManager<T>- Lazy variant
Why needed: Sometimes you need to find all items matching one index:
- "Give me all tiles at X=5" (any Y)
- "Give me all events on day 10" (any user)
â Pros:
- â Perfect accuracy - Always reflects current box state
- ðū Zero storage overhead - No additional boxes or memory structures
- ðŠķ Memory efficient - Only stores seen indices during iteration
- ðĄïļ Simple & robust - Less code, fewer failure points
- ⥠Leverages Hive optimizations - Uses Hive's built-in key indexing
â Cons:
- ð O(K) per query - Performance degrades with total records
- ð No pre-computation - Each query scans all keys
- â ïļ Not optimal for very large datasets - >100K records may cause UI jank
- ð No indexing benefits - Each decomposition is a full scan
Use cases:
- Finding all events for a specific user
- Getting all data points for a specific date
- Querying one dimension of a 2D dataset
Example:
final userEvents = QueryDualIntIndexLazyBoxManager<Event>.bitShift(
boxKey: 'user_events',
defaultValue: Event.empty(),
);
await userEvents.init();
// Store events
await userEvents.put(
primaryIndex: userId,
secondaryIndex: dayIndex,
value: event,
).run();
// Query all events for a user (any day)
final allUserEvents = await userEvents
.getAllByPrimaryIndex(userId)
.run();
// Query all events on a specific day (any user)
final dailyEvents = await userEvents
.getAllBySecondaryIndex(dayIndex)
.run();
Trade-offs:
| Aspect | Standard Dual | Query Dual |
|---|---|---|
| Storage | Single box | Single box |
| Retrieval | By both indices | By either index |
| Performance | O(1) lookup | O(K) scan |
| Use case | Exact lookups | Partial queries |
| Complexity | Simple | Moderate |
ðĻ Quick Decision Guide
Need to store...
ââ Single value?
â ââ Small (< 100KB)? â SingleIndexBoxManager
â ââ Large (> 100KB)? â SingleIndexLazyBoxManager
â
ââ Multiple items with one key?
â ââ Small dataset (< 1MB), frequent access? â BoxManager
â ââ Large dataset (> 1MB), occasional access? â LazyBoxManager
â ââ Collections of custom types? â CollectionLazyBoxManager
â
ââ Multiple items with two keys?
ââ Only need exact lookups (both keys)? â DualIntIndexBoxManager/LazyBoxManager
ââ Need to query by either key? â QueryDualIntIndexBoxManager/LazyBoxManager
ð Additional Resources
- Hive Documentation
- fpdart Documentation - For understanding
Taskand functional patterns
ð License
See LICENSE file for details.