Files
base_flutter/lib/core/database
2025-09-26 20:54:32 +07:00
..
2025-09-26 18:48:14 +07:00
2025-09-26 18:48:14 +07:00
2025-09-26 18:48:14 +07:00
2025-09-26 20:54:32 +07:00
2025-09-26 18:48:14 +07:00
2025-09-26 18:48:14 +07:00

Hive Database Configuration

This directory contains the complete Hive database setup for local storage and caching in the Flutter app.

Architecture Overview

The Hive database system is organized into the following components:

lib/core/database/
├── hive_service.dart              # Hive initialization and box management
├── models/
│   ├── app_settings.dart          # App settings model with Hive adapter
│   ├── app_settings.g.dart        # Generated Hive adapter
│   ├── cache_item.dart            # Generic cache wrapper model
│   ├── cache_item.g.dart          # Generated Hive adapter
│   ├── user_preferences.dart      # User preferences model
│   └── user_preferences.g.dart    # Generated Hive adapter
├── repositories/
│   ├── settings_repository.dart   # Settings repository implementation
│   ├── cache_repository.dart      # Cache repository implementation
│   └── user_preferences_repository.dart # User preferences repository
├── providers/
│   └── database_providers.dart    # Riverpod providers for database access
└── examples/
    └── database_usage_example.dart # Usage examples

Features

1. HiveService

  • Initialization: Sets up Hive for Flutter and registers type adapters
  • Box Management: Manages three main boxes (appSettingsBox, cacheBox, userDataBox)
  • Migration Support: Handles database schema migrations
  • Error Handling: Comprehensive error handling and logging
  • Maintenance: Database compaction and cleanup utilities

2. Models with Type Adapters

AppSettings (TypeId: 0)

  • Application-wide configuration
  • Theme mode, locale, notifications
  • Cache settings and auto-update preferences
  • Custom settings support
  • Expiration logic for settings refresh

CacheItem (TypeId: 1)

  • Generic cache wrapper for any data type
  • Expiration logic with TTL support
  • Metadata support for cache categorization
  • Version control for cache migrations
  • Statistics and performance monitoring

UserPreferences (TypeId: 2)

  • User-specific settings and data
  • Favorites management
  • Last accessed tracking
  • Preference categories with type safety
  • User activity statistics

3. Repository Pattern

  • SettingsRepository: Application settings management
  • CacheRepository: Generic caching with TTL and expiration
  • UserPreferencesRepository: User-specific data management

4. Riverpod Integration

  • State management with providers
  • Reactive updates when data changes
  • Type-safe access to cached data
  • Automatic cache invalidation

Usage Examples

Basic Initialization

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Hive database service
  await HiveService.initialize();

  runApp(const ProviderScope(child: MyApp()));
}

Settings Management

class SettingsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final settings = ref.watch(appSettingsProvider);
    final notifier = ref.read(appSettingsProvider.notifier);

    return SwitchListTile(
      title: const Text('Dark Mode'),
      value: settings.themeMode == 'dark',
      onChanged: (value) => notifier.updateThemeMode(value ? 'dark' : 'light'),
    );
  }
}

Caching Data

class DataService {
  final CacheRepository _cache;

  Future<UserData> getUserData(String userId) async {
    // Try cache first
    final cachedData = _cache.get<UserData>('user_$userId');
    if (cachedData != null) {
      return cachedData;
    }

    // Fetch from API
    final userData = await api.fetchUserData(userId);

    // Cache with 1 hour expiration
    await _cache.put<UserData>(
      key: 'user_$userId',
      data: userData,
      expirationDuration: const Duration(hours: 1),
      metadata: {'type': 'user_data', 'userId': userId},
    );

    return userData;
  }
}

User Preferences

class ProfileScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final preferences = ref.watch(userPreferencesProvider(null));
    final notifier = ref.read(userPreferencesProvider(null).notifier);

    return Column(
      children: [
        SwitchListTile(
          title: const Text('Compact Mode'),
          value: preferences?.getPreference<bool>('compactMode', false) ?? false,
          onChanged: (value) => notifier.setPreference('compactMode', value),
        ),
        // Favorites management
        IconButton(
          icon: Icon(preferences?.isFavorite('item123') == true
              ? Icons.favorite : Icons.favorite_border),
          onPressed: () {
            if (preferences?.isFavorite('item123') == true) {
              notifier.removeFavorite('item123');
            } else {
              notifier.addFavorite('item123');
            }
          },
        ),
      ],
    );
  }
}

Database Boxes

appSettingsBox

  • Purpose: Application-wide settings
  • Key: String-based keys (e.g., 'app_settings')
  • Data: AppSettings objects
  • Usage: Theme, language, cache strategy, feature flags

cacheBox

  • Purpose: Generic caching with TTL
  • Key: String-based cache keys
  • Data: CacheItem objects wrapping any data type
  • Usage: API responses, user data, computed values

userDataBox

  • Purpose: User-specific preferences and data
  • Key: User ID or 'current_user_preferences' for default
  • Data: UserPreferences objects
  • Usage: User settings, favorites, activity tracking

Cache Strategies

Write-Through Cache

Future<void> updateUserData(UserData data) async {
  // Update API
  await api.updateUser(data);

  // Update cache
  await cacheRepository.put<UserData>(
    key: 'user_${data.id}',
    data: data,
    expirationDuration: const Duration(hours: 1),
  );
}

Cache-Aside Pattern

Future<UserData> getUserData(String userId) async {
  // Check cache first
  var userData = cacheRepository.get<UserData>('user_$userId');

  if (userData == null) {
    // Cache miss - fetch from API
    userData = await api.fetchUser(userId);

    // Store in cache
    await cacheRepository.put<UserData>(
      key: 'user_$userId',
      data: userData,
      expirationDuration: const Duration(minutes: 30),
    );
  }

  return userData;
}

Performance Optimizations

1. Lazy Loading

  • Boxes are opened only when needed
  • Data is loaded on-demand

2. Batch Operations

Future<void> cacheMultipleItems(Map<String, dynamic> items) async {
  final futures = items.entries.map((entry) =>
    cacheRepository.put<dynamic>(
      key: entry.key,
      data: entry.value,
      expirationDuration: const Duration(hours: 1),
    )
  );

  await Future.wait(futures);
}

3. Cache Maintenance

// Automatic cleanup of expired items
final result = await cacheRepository.performMaintenance();
print('Cleaned ${result['expiredItemsRemoved']} expired items');

Error Handling

Repository Level

  • All repository methods have comprehensive try-catch blocks
  • Errors are logged with stack traces
  • Graceful degradation (return defaults on errors)

Provider Level

  • State management handles loading/error states
  • Automatic retry mechanisms
  • User-friendly error messages

Service Level

  • Database initialization errors are handled gracefully
  • Box corruption recovery
  • Migration failure handling

Migration Strategy

Version Control

class AppSettings {
  final int version; // Used for migrations

  // Migration logic in repository
  AppSettings _migrateSettings(AppSettings oldSettings) {
    if (oldSettings.version < 2) {
      // Perform migration to version 2
      return oldSettings.copyWith(
        version: 2,
        // Add new fields with defaults
        newField: defaultValue,
      );
    }
    return oldSettings;
  }
}

Data Backup

// Export data before migration
final backup = settingsRepository.exportSettings();
final userBackup = userPreferencesRepository.exportUserPreferences();

// Perform migration
await HiveService.migrate();

// Restore if migration fails
if (migrationFailed) {
  await settingsRepository.importSettings(backup);
  await userPreferencesRepository.importUserPreferences(userBackup);
}

Monitoring and Analytics

Database Statistics

final stats = ref.watch(databaseStatsProvider);
print('Total items: ${stats['totalItems']}');
print('Cache hit rate: ${stats['cache']['hitRate']}%');

Performance Metrics

final cacheStats = await cacheRepository.getStats();
print('Cache performance:');
print('- Hit rate: ${(cacheStats.hitRate * 100).toStringAsFixed(1)}%');
print('- Valid items: ${cacheStats.validItems}');
print('- Expired items: ${cacheStats.expiredItems}');

Best Practices

1. Key Naming Convention

  • Use descriptive, hierarchical keys: user_123_profile
  • Include type information: api_response_movies_popular
  • Use consistent separators: underscores or colons

2. TTL Management

  • Short TTL for frequently changing data (5-30 minutes)
  • Medium TTL for stable data (1-24 hours)
  • Long TTL for static data (1-7 days)
  • Permanent cache only for user preferences

3. Data Validation

  • Validate data before caching
  • Check data integrity on retrieval
  • Handle corrupted data gracefully

4. Memory Management

  • Regular cleanup of expired items
  • Monitor cache size and performance
  • Use appropriate data structures

5. Security

  • Don't cache sensitive data without encryption
  • Clear caches on logout
  • Validate user access to cached data

Testing Strategy

Unit Tests

  • Test repository methods with mock boxes
  • Validate data serialization/deserialization
  • Test error handling scenarios

Integration Tests

  • Test full database workflows
  • Validate Riverpod provider interactions
  • Test migration scenarios

Performance Tests

  • Measure cache hit rates
  • Test with large datasets
  • Monitor memory usage

This Hive database setup provides a robust, scalable foundation for local data storage and caching in your Flutter application.