runable
This commit is contained in:
478
lib/core/database/README.md
Normal file
478
lib/core/database/README.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# Hive CE Database Setup
|
||||
|
||||
This directory contains the Hive CE (Community Edition) database configuration and services for the Worker Flutter app.
|
||||
|
||||
## Overview
|
||||
|
||||
The app uses Hive CE for offline-first local data persistence. Hive is a lightweight, fast NoSQL database written in pure Dart, perfect for Flutter applications.
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Offline-First**: All data is stored locally and synced with the backend
|
||||
- **Fast Performance**: Hive is optimized for speed with minimal overhead
|
||||
- **Type-Safe**: Uses type adapters for strong typing
|
||||
- **Encryption Support**: Optional AES encryption for sensitive data
|
||||
- **Auto-Compaction**: Automatic database maintenance and cleanup
|
||||
- **Migration Support**: Built-in schema versioning and migrations
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
lib/core/database/
|
||||
├── README.md # This file
|
||||
├── hive_service.dart # Main Hive initialization and lifecycle management
|
||||
├── database_manager.dart # High-level database operations
|
||||
└── models/
|
||||
├── cached_data.dart # Generic cache wrapper model
|
||||
└── enums.dart # All enum type adapters
|
||||
```
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install Dependencies
|
||||
|
||||
The required packages are already in `pubspec.yaml`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
hive_ce: ^2.6.0
|
||||
hive_ce_flutter: ^2.1.0
|
||||
|
||||
dev_dependencies:
|
||||
hive_ce_generator: ^1.6.0
|
||||
build_runner: ^2.4.11
|
||||
```
|
||||
|
||||
Run:
|
||||
```bash
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
### 2. Generate Type Adapters
|
||||
|
||||
After creating Hive models with `@HiveType` annotations, run:
|
||||
|
||||
```bash
|
||||
dart run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
Or for continuous watching during development:
|
||||
```bash
|
||||
dart run build_runner watch --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
### 3. Initialize Hive in main.dart
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'core/database/hive_service.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Initialize Hive
|
||||
final hiveService = HiveService();
|
||||
await hiveService.initialize();
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
## Creating Hive Models
|
||||
|
||||
### Basic Model Example
|
||||
|
||||
```dart
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import '../../constants/storage_constants.dart';
|
||||
|
||||
part 'user_model.g.dart'; // Generated file
|
||||
|
||||
@HiveType(typeId: HiveTypeIds.user)
|
||||
class UserModel extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
@HiveField(2)
|
||||
final String email;
|
||||
|
||||
UserModel({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.email,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Enum Example
|
||||
|
||||
```dart
|
||||
@HiveType(typeId: HiveTypeIds.memberTier)
|
||||
enum MemberTier {
|
||||
@HiveField(0)
|
||||
gold,
|
||||
|
||||
@HiveField(1)
|
||||
platinum,
|
||||
|
||||
@HiveField(2)
|
||||
diamond,
|
||||
}
|
||||
```
|
||||
|
||||
### Important Rules
|
||||
|
||||
1. **Type IDs must be unique** across the entire app (0-223 for user types)
|
||||
2. **Never change field numbers** once assigned - it will break existing data
|
||||
3. **Use `part` directive** to include generated adapter file
|
||||
4. **Extend HiveObject** for model classes (optional but recommended for auto-save)
|
||||
5. **Register adapters** before opening boxes (handled by HiveService)
|
||||
|
||||
## Box Management
|
||||
|
||||
### Available Boxes
|
||||
|
||||
The app uses these pre-configured boxes (see `storage_constants.dart`):
|
||||
|
||||
- `user_box` - User profile and auth data (encrypted)
|
||||
- `product_box` - Product catalog cache
|
||||
- `cart_box` - Shopping cart items (encrypted)
|
||||
- `order_box` - Order history (encrypted)
|
||||
- `project_box` - Construction projects (encrypted)
|
||||
- `loyalty_box` - Loyalty transactions (encrypted)
|
||||
- `rewards_box` - Rewards catalog
|
||||
- `settings_box` - App settings
|
||||
- `cache_box` - Generic API cache
|
||||
- `sync_state_box` - Sync timestamps
|
||||
- `notification_box` - Notifications
|
||||
- `address_box` - Delivery addresses (encrypted)
|
||||
- `offline_queue_box` - Failed API requests queue (encrypted)
|
||||
|
||||
### Using Boxes
|
||||
|
||||
```dart
|
||||
// Using DatabaseManager (recommended)
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
// Save data
|
||||
await dbManager.save(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
value: product,
|
||||
);
|
||||
|
||||
// Get data
|
||||
final product = dbManager.get(
|
||||
boxName: HiveBoxNames.productBox,
|
||||
key: 'product_123',
|
||||
);
|
||||
|
||||
// Get all
|
||||
final products = dbManager.getAll(boxName: HiveBoxNames.productBox);
|
||||
```
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
### Save to Cache
|
||||
|
||||
```dart
|
||||
final dbManager = DatabaseManager();
|
||||
|
||||
await dbManager.saveToCache(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
data: products,
|
||||
);
|
||||
```
|
||||
|
||||
### Get from Cache
|
||||
|
||||
```dart
|
||||
final products = dbManager.getFromCache<List<Product>>(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
maxAge: CacheDuration.products, // 6 hours
|
||||
);
|
||||
|
||||
if (products == null) {
|
||||
// Cache miss or expired - fetch from API
|
||||
final freshProducts = await api.getProducts();
|
||||
await dbManager.saveToCache(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
data: freshProducts,
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Check Cache Validity
|
||||
|
||||
```dart
|
||||
final isValid = dbManager.isCacheValid(
|
||||
key: HiveKeys.productsCacheKey,
|
||||
maxAge: CacheDuration.products,
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
// Refresh cache
|
||||
}
|
||||
```
|
||||
|
||||
## Offline Queue
|
||||
|
||||
Handle failed API requests when offline:
|
||||
|
||||
```dart
|
||||
// Add to queue when API call fails
|
||||
await dbManager.addToOfflineQueue({
|
||||
'endpoint': '/api/orders',
|
||||
'method': 'POST',
|
||||
'body': orderData,
|
||||
});
|
||||
|
||||
// Process queue when back online
|
||||
final queue = dbManager.getOfflineQueue();
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
try {
|
||||
await api.request(queue[i]);
|
||||
await dbManager.removeFromOfflineQueue(i);
|
||||
} catch (e) {
|
||||
// Keep in queue for next retry
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Data Synchronization
|
||||
|
||||
Track sync state for different data types:
|
||||
|
||||
```dart
|
||||
// Update sync timestamp
|
||||
await dbManager.updateSyncTime(HiveKeys.productsSyncTime);
|
||||
|
||||
// Get last sync time
|
||||
final lastSync = dbManager.getLastSyncTime(HiveKeys.productsSyncTime);
|
||||
|
||||
// Check if needs sync
|
||||
final needsSync = dbManager.needsSync(
|
||||
dataType: HiveKeys.productsSyncTime,
|
||||
syncInterval: Duration(hours: 6),
|
||||
);
|
||||
```
|
||||
|
||||
## Encryption
|
||||
|
||||
Enable encryption for sensitive data in `storage_constants.dart`:
|
||||
|
||||
```dart
|
||||
class HiveDatabaseConfig {
|
||||
static const bool enableEncryption = true;
|
||||
}
|
||||
```
|
||||
|
||||
Generate and store encryption key securely:
|
||||
|
||||
```dart
|
||||
// Generate key
|
||||
final encryptionKey = HiveService.generateEncryptionKey();
|
||||
|
||||
// Store securely using flutter_secure_storage
|
||||
final secureStorage = FlutterSecureStorage();
|
||||
await secureStorage.write(
|
||||
key: 'hive_encryption_key',
|
||||
value: base64Encode(encryptionKey),
|
||||
);
|
||||
|
||||
// Initialize with key
|
||||
final storedKey = await secureStorage.read(key: 'hive_encryption_key');
|
||||
await hiveService.initialize(
|
||||
encryptionKey: base64Decode(storedKey!),
|
||||
);
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
Handle schema changes:
|
||||
|
||||
```dart
|
||||
// In hive_service.dart, add migration logic:
|
||||
|
||||
Future<void> _migrateToVersion(int version) async {
|
||||
switch (version) {
|
||||
case 2:
|
||||
await _migrateV1ToV2();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateV1ToV2() async {
|
||||
// Example: Add new field to existing data
|
||||
final userBox = Hive.box(HiveBoxNames.userBox);
|
||||
|
||||
for (var key in userBox.keys) {
|
||||
final user = userBox.get(key);
|
||||
// Update user data structure
|
||||
await userBox.put(key, updatedUser);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database Maintenance
|
||||
|
||||
### Clear Expired Cache
|
||||
|
||||
```dart
|
||||
await dbManager.clearExpiredCache();
|
||||
```
|
||||
|
||||
### Compact Boxes
|
||||
|
||||
```dart
|
||||
final hiveService = HiveService();
|
||||
// Compaction happens automatically during initialization
|
||||
```
|
||||
|
||||
### Clear User Data (Logout)
|
||||
|
||||
```dart
|
||||
await hiveService.clearUserData();
|
||||
```
|
||||
|
||||
### Clear All Data
|
||||
|
||||
```dart
|
||||
await hiveService.clearAllData();
|
||||
```
|
||||
|
||||
### Get Statistics
|
||||
|
||||
```dart
|
||||
final stats = dbManager.getStatistics();
|
||||
dbManager.printStatistics();
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always initialize Hive before using any boxes**
|
||||
2. **Use DatabaseManager for common operations**
|
||||
3. **Cache frequently accessed data**
|
||||
4. **Set appropriate cache expiration times**
|
||||
5. **Handle errors gracefully** - Hive operations can fail
|
||||
6. **Use transactions for multiple related updates**
|
||||
7. **Compact boxes periodically** for optimal performance
|
||||
8. **Never store large files in Hive** - use file system instead
|
||||
9. **Test migrations thoroughly** before release
|
||||
10. **Monitor database size** in production
|
||||
|
||||
## Debugging
|
||||
|
||||
### Print Box Contents
|
||||
|
||||
```dart
|
||||
final box = Hive.box(HiveBoxNames.productBox);
|
||||
print('Box length: ${box.length}');
|
||||
print('Keys: ${box.keys}');
|
||||
print('Values: ${box.values}');
|
||||
```
|
||||
|
||||
### Check Box Location
|
||||
|
||||
```dart
|
||||
print('Hive path: ${Hive.box(HiveBoxNames.settingsBox).path}');
|
||||
```
|
||||
|
||||
### View Statistics
|
||||
|
||||
```dart
|
||||
DatabaseManager().printStatistics();
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Box not found" Error
|
||||
- Ensure Hive is initialized before accessing boxes
|
||||
- Check that box name is correct
|
||||
|
||||
### "TypeAdapter not registered" Error
|
||||
- Run `build_runner` to generate adapters
|
||||
- Ensure adapter is registered in `HiveService._registerTypeAdapters()`
|
||||
|
||||
### "Cannot write null values" Error
|
||||
- Make fields nullable with `?` or provide default values
|
||||
- Check that HiveField annotations are correct
|
||||
|
||||
### Data Corruption
|
||||
- Enable backup/restore functionality
|
||||
- Implement data validation before saving
|
||||
- Use try-catch blocks around Hive operations
|
||||
|
||||
## Testing
|
||||
|
||||
```dart
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() async {
|
||||
await Hive.initFlutter();
|
||||
// Register test adapters
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await Hive.deleteFromDisk();
|
||||
});
|
||||
|
||||
test('Save and retrieve user', () async {
|
||||
final box = await Hive.openBox('test_box');
|
||||
await box.put('user', UserModel(id: '1', name: 'Test'));
|
||||
|
||||
final user = box.get('user');
|
||||
expect(user.name, 'Test');
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Hive CE Documentation](https://github.com/IO-Design-Team/hive_ce)
|
||||
- [Original Hive Documentation](https://docs.hivedb.dev/)
|
||||
- [Flutter Offline-First Best Practices](https://flutter.dev/docs/cookbook/persistence)
|
||||
|
||||
## Type Adapter Registry
|
||||
|
||||
### Registered Type IDs (0-223)
|
||||
|
||||
| Type ID | Model | Status |
|
||||
|---------|-------|--------|
|
||||
| 0 | UserModel | TODO |
|
||||
| 1 | ProductModel | TODO |
|
||||
| 2 | CartItemModel | TODO |
|
||||
| 3 | OrderModel | TODO |
|
||||
| 4 | ProjectModel | TODO |
|
||||
| 5 | LoyaltyTransactionModel | TODO |
|
||||
| 10 | OrderItemModel | TODO |
|
||||
| 11 | AddressModel | TODO |
|
||||
| 12 | CategoryModel | TODO |
|
||||
| 13 | RewardModel | TODO |
|
||||
| 14 | GiftModel | TODO |
|
||||
| 15 | NotificationModel | TODO |
|
||||
| 16 | QuoteModel | TODO |
|
||||
| 17 | PaymentModel | TODO |
|
||||
| 18 | PromotionModel | TODO |
|
||||
| 19 | ReferralModel | TODO |
|
||||
| 20 | MemberTier (enum) | Created |
|
||||
| 21 | UserType (enum) | Created |
|
||||
| 22 | OrderStatus (enum) | Created |
|
||||
| 23 | ProjectStatus (enum) | Created |
|
||||
| 24 | ProjectType (enum) | Created |
|
||||
| 25 | TransactionType (enum) | Created |
|
||||
| 26 | GiftStatus (enum) | Created |
|
||||
| 27 | PaymentStatus (enum) | Created |
|
||||
| 28 | NotificationType (enum) | Created |
|
||||
| 29 | PaymentMethod (enum) | Created |
|
||||
| 30 | CachedData | Created |
|
||||
| 31 | SyncState | TODO |
|
||||
| 32 | OfflineRequest | TODO |
|
||||
|
||||
**IMPORTANT**: Never reuse or change these type IDs once assigned!
|
||||
Reference in New Issue
Block a user