🎯 Рекомендуемые коллекции
Балансированные коллекции примеров кода из различных категорий, которые вы можете исследовать
Примеры Базы Данных Flutter Hive
Примеры реализации базы данных Flutter Hive с автономным хранением, моделированием данных и оптимизацией производительности
💻 База Данных Hive Основы dart
🟢 simple
⭐⭐
Полная реализация базы данных Flutter Hive с моделированием данных, CRUD операциями и автономным хранением
⏱️ 40 min
🏷️ flutter, hive, database, offline
Prerequisites:
Flutter basics, Dart programming, Database concepts
// Flutter Hive Database - Complete Implementation
// Offline NoSQL Database for Flutter Applications
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
part 'user_model.g.dart';
part 'product_model.g.dart';
part 'app_models.g.dart';
// 1. Main Application Setup
void main() async {
// Ensure Flutter bindings are initialized
WidgetsFlutterBinding.ensureInitialized();
// Initialize Hive
await Hive.initFlutter('my_app_db');
// Register Hive Adapters
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ProductAdapter());
Hive.registerAdapter(OrderAdapter());
Hive.registerAdapter(OrderItemAdapter());
// Open Hive boxes
await Hive.openBox<User>('users');
await Hive.openBox<Product>('products');
await Hive.openBox<Order>('orders');
await Hive.openBox('settings');
runApp(MyApp());
}
// 2. Data Models
@HiveType(typeId: 0)
class User extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
@HiveField(2)
late String email;
@HiveField(3)
late DateTime createdAt;
@HiveField(4)
DateTime? lastLogin;
@HiveField(5)
String? avatar;
@HiveField(6)
List<String> tags = [];
@HiveField(7)
bool isActive = true;
// Computed property
String get displayName => name.isEmpty ? email : name;
// Helper method
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'createdAt': createdAt.toIso8601String(),
'lastLogin': lastLogin?.toIso8601String(),
'avatar': avatar,
'tags': tags,
'isActive': isActive,
};
}
// Factory constructor
factory User.fromJson(Map<String, dynamic> json) {
final user = User();
user.id = json['id'];
user.name = json['name'] ?? '';
user.email = json['email'] ?? '';
user.createdAt = DateTime.parse(json['createdAt']);
user.lastLogin = json['lastLogin'] != null ? DateTime.parse(json['lastLogin']) : null;
user.avatar = json['avatar'];
user.tags = List<String>.from(json['tags'] ?? []);
user.isActive = json['isActive'] ?? true;
return user;
}
}
@HiveType(typeId: 1)
class Product extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
@HiveField(2)
late String description;
@HiveField(3)
late double price;
@HiveField(4)
String? imageUrl;
@HiveField(5)
List<String> categories = [];
@HiveField(6)
int stock = 0;
@HiveField(7)
bool isAvailable = true;
@HiveField(8)
DateTime createdAt;
@HiveField(9)
DateTime? updatedAt;
@HiveField(10)
Map<String, dynamic> attributes = {};
// Helper methods
bool get isInStock => stock > 0;
String get formattedPrice => '${price.toStringAsFixed(2)}';
// Search helper
bool matchesQuery(String query) {
final lowerQuery = query.toLowerCase();
return name.toLowerCase().contains(lowerQuery) ||
description.toLowerCase().contains(lowerQuery) ||
categories.any((cat) => cat.toLowerCase().contains(lowerQuery));
}
}
@HiveType(typeId: 2)
class Order extends HiveObject {
@HiveField(0)
late String id;
@HiveField(1)
late String userId;
@HiveField(2)
List<OrderItem> items = [];
@HiveField(3)
late double totalAmount;
@HiveField(4)
late DateTime createdAt;
@HiveField(5)
OrderStatus status = OrderStatus.pending;
@HiveField(6)
String? shippingAddress;
@HiveField(7)
String? notes;
// Computed properties
int get totalItems => items.fold(0, (sum, item) => sum + item.quantity);
}
@HiveType(typeId: 3)
class OrderItem {
@HiveField(0)
late String productId;
@HiveField(1)
late String productName;
@HiveField(2)
late int quantity;
@HiveField(3)
late double unitPrice;
@HiveField(4)
late double totalPrice;
}
enum OrderStatus {
pending,
confirmed,
processing,
shipped,
delivered,
cancelled
}
// 3. Database Service
class DatabaseService {
static final DatabaseService _instance = DatabaseService._internal();
factory DatabaseService() => _instance;
DatabaseService._internal();
// Box references
late final Box<User> _userBox;
late final Box<Product> _productBox;
late final Box<Order> _orderBox;
late final Box _settingsBox;
// Initialize boxes
void initialize() {
_userBox = Hive.box<User>('users');
_productBox = Hive.box<Product>('products');
_orderBox = Hive.box<Order>('orders');
_settingsBox = Hive.box('settings');
}
// User CRUD Operations
Future<User> createUser(User user) async {
if (user.id.isEmpty) {
user.id = DateTime.now().millisecondsSinceEpoch.toString();
}
await _userBox.put(user);
return user;
}
Future<User?> getUser(String id) async {
return _userBox.get(id);
}
Future<List<User>> getAllUsers() async {
return _userBox.values.toList();
}
Future<List<User>> getActiveUsers() async {
return _userBox.values.where((user) => user.isActive).toList();
}
Future<void> updateUser(User user) async {
await _userBox.put(user);
}
Future<void> deleteUser(String id) async {
await _userBox.delete(id);
}
Future<List<User>> searchUsers(String query) async {
if (query.isEmpty) {
return getAllUsers();
}
final lowerQuery = query.toLowerCase();
return _userBox.values.where((user) =>
user.name.toLowerCase().contains(lowerQuery) ||
user.email.toLowerCase().contains(lowerQuery)
).toList();
}
// Product CRUD Operations
Future<Product> createProduct(Product product) async {
if (product.id.isEmpty) {
product.id = DateTime.now().millisecondsSinceEpoch.toString();
}
if (product.createdAt == null) {
product.createdAt = DateTime.now();
}
await _productBox.put(product);
return product;
}
Future<Product?> getProduct(String id) async {
return _productBox.get(id);
}
Future<List<Product>> getAllProducts() async {
return _productBox.values.toList();
}
Future<List<Product>> getAvailableProducts() async {
return _productBox.values.where((product) => product.isAvailable).toList();
}
Future<List<Product>> getProductsByCategory(String category) async {
return _productBox.values.where((product) =>
product.categories.contains(category)
).toList();
}
Future<void> updateProduct(Product product) async {
product.updatedAt = DateTime.now();
await _productBox.put(product);
}
Future<void> deleteProduct(String id) async {
await _productBox.delete(id);
}
Future<List<Product>> searchProducts(String query) async {
if (query.isEmpty) {
return getAllProducts();
}
return _productBox.values.where((product) => product.matchesQuery(query)).toList();
}
Future<void> updateProductStock(String productId, int newStock) async {
final product = await getProduct(productId);
if (product != null) {
product.stock = newStock;
product.isAvailable = newStock > 0;
await updateProduct(product);
}
}
// Order CRUD Operations
Future<Order> createOrder(Order order) async {
if (order.id.isEmpty) {
order.id = DateTime.now().millisecondsSinceEpoch.toString();
}
await _orderBox.put(order);
return order;
}
Future<Order?> getOrder(String id) async {
return _orderBox.get(id);
}
Future<List<Order>> getUserOrders(String userId) async {
return _orderBox.values
.where((order) => order.userId == userId)
.toList();
}
Future<List<Order>> getOrdersByStatus(OrderStatus status) async {
return _orderBox.values
.where((order) => order.status == status)
.toList();
}
Future<void> updateOrderStatus(String orderId, OrderStatus newStatus) async {
final order = await getOrder(orderId);
if (order != null) {
order.status = newStatus;
await _orderBox.put(order);
}
}
Future<void> deleteOrder(String id) async {
await _orderBox.delete(id);
}
// Settings Operations
Future<void> saveSetting(String key, dynamic value) async {
await _settingsBox.put(key, value);
}
Future<T?> getSetting<T>(String key, {T? defaultValue}) async {
return _settingsBox.get(key, defaultValue);
}
// Backup and Restore
Future<void> backupData(String backupPath) async {
final appDir = await getApplicationDocumentsDirectory();
final backupDir = Directory('${appDir.path}/backups');
if (!await backupDir.exists()) {
await backupDir.create(recursive: true);
}
final timestamp = DateTime.now().millisecondsSinceEpoch;
final backupFile = File('${backupDir.path}/backup_${timestamp}.hive');
// Export all boxes to backup file
final userData = _userBox.toMap();
final productData = _productBox.toMap();
final orderData = _orderBox.toMap();
final settingsData = _settingsBox.toMap();
final backupData = {
'users': userData,
'products': productData,
'orders': orderData,
'settings': settingsData,
'timestamp': timestamp,
'version': '1.0.0'
};
// Convert to JSON and save
final jsonString = jsonEncode(backupData);
await backupFile.writeAsString(jsonString);
print('Data backed up successfully to ${backupFile.path}');
}
Future<void> restoreData(String backupPath) async {
final backupFile = File(backupPath);
if (!await backupFile.exists()) {
throw Exception('Backup file not found');
}
final jsonString = await backupFile.readAsString();
final backupData = jsonDecode(jsonString) as Map<String, dynamic>;
// Clear existing data
await _userBox.clear();
await _productBox.clear();
await _orderBox.clear();
await _settingsBox.clear();
// Restore users
final usersData = backupData['users'] as Map;
for (final entry in usersData.entries) {
final userJson = entry.value;
final user = User.fromJson(userJson);
await _userBox.put(user);
}
// Restore products
final productsData = backupData['products'] as Map;
for (final entry in productsData.entries) {
final productData = entry.value;
// Note: Product would need its own fromJson method
// This is a simplified example
}
// Restore orders
final ordersData = backupData['orders'] as Map;
for (final entry in ordersData.entries) {
// Restore orders similar to users
}
// Restore settings
final settingsData = backupData['settings'] as Map;
for (final entry in settingsData.entries) {
await _settingsBox.put(entry.key, entry.value);
}
print('Data restored successfully from ${backupPath}');
}
// Performance Optimization
Future<void> compactDatabase() async {
await _userBox.compact();
await _productBox.compact();
await _orderBox.compact();
await _settingsBox.compact();
print('Database compacted successfully');
}
Future<void> clearAllData() async {
await _userBox.clear();
await _productBox.clear();
await _orderBox.clear();
await _settingsBox.clear();
print('All data cleared successfully');
}
// Statistics
Future<Map<String, dynamic>> getDatabaseStats() async {
return {
'users': _userBox.length,
'products': _productBox.length,
'orders': _orderBox.length,
'activeUsers': _userBox.values.where((u) => u.isActive).length,
'availableProducts': _productBox.values.where((p) => p.isAvailable).length,
'pendingOrders': _orderBox.values.where((o) => o.status == OrderStatus.pending).length,
'databaseSize': await _getDatabaseSize(),
};
}
Future<int> _getDatabaseSize() async {
final appDir = await getApplicationDocumentsDirectory();
final dbDir = Directory('${appDir.path}/my_app_db');
if (!await dbDir.exists()) {
return 0;
}
int totalSize = 0;
await for (final entity in dbDir.list()) {
if (entity is File) {
final file = entity as File;
totalSize += await file.length();
}
}
return totalSize;
}
}
// 4. Flutter App UI
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Hive Demo',
theme: ThemeData(
primarySwatch: Colors.indigo,
useMaterial3: true,
),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final DatabaseService _dbService = DatabaseService();
@override
void initState() {
super.initState();
_dbService.initialize();
_initializeSampleData();
}
Future<void> _initializeSampleData() async {
// Create sample user
final user = User()
..id = 'user_1'
..name = 'John Doe'
..email = '[email protected]'
..createdAt = DateTime.now()
..tags = ['developer', 'flutter'];
await _dbService.createUser(user);
// Create sample products
final products = [
Product()
..id = 'prod_1'
..name = 'Flutter Development Guide'
..description = 'Complete guide to Flutter development'
..price = 29.99
..categories = ['books', 'education']
..stock = 100
..isAvailable = true
..createdAt = DateTime.now(),
Product()
..id = 'prod_2'
..name = 'Dart Programming'
..description = 'Learn Dart programming language'
..price = 19.99
..categories = ['books', 'programming']
..stock = 50
..isAvailable = true
..createdAt = DateTime.now(),
];
for (final product in products) {
await _dbService.createProduct(product);
}
// Create sample order
final order = Order()
..id = 'order_1'
..userId = 'user_1'
..items = [
OrderItem()
..productId = 'prod_1'
..productName = 'Flutter Development Guide'
..quantity = 1
..unitPrice = 29.99
..totalPrice = 29.99,
]
..totalAmount = 29.99
..createdAt = DateTime.now()
..status = OrderStatus.pending;
await _dbService.createOrder(order);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter Hive Demo'),
actions: [
IconButton(
icon: const Icon(Icons.backup),
onPressed: () => _showBackupDialog(context),
),
IconButton(
icon: const Icon(Icons.restore),
onPressed: () => _showRestoreDialog(context),
),
],
),
body: ValueListenableBuilder(
valueListenable: Hive.box<User>('users').listenable(),
builder: (context, snapshot, _) {
final users = Hive.box<User>('users').values.toList();
return Column(
children: [
_buildStatsHeader(),
Expanded(
child: users.isEmpty
? const Center(child: Text('No users found'))
: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return UserCard(
user: user,
onTap: () => _showUserDetails(context, user),
);
},
),
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddUserDialog(context),
child: const Icon(Icons.add),
),
);
}
Widget _buildStatsHeader() {
return FutureBuilder<Map<String, dynamic>>(
future: _dbService.getDatabaseStats(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox(height: 80);
}
final stats = snapshot.data!;
return Container(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const Text('Database Statistics', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Wrap(
spacing: 16,
children: [
_StatCard(label: 'Users', value: stats['users'].toString()),
_StatCard(label: 'Products', value: stats['products'].toString()),
_StatCard(label: 'Orders', value: stats['orders'].toString()),
_StatCard(label: 'DB Size', value: '${(stats['databaseSize'] / 1024).toStringAsFixed(1)} KB'),
],
),
],
),
);
},
);
}
void _showAddUserDialog(BuildContext context) {
final nameController = TextEditingController();
final emailController = TextEditingController();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add User'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Name'),
),
TextField(
controller: emailController,
decoration: const InputDecoration(labelText: 'Email'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
if (nameController.text.isNotEmpty && emailController.text.isNotEmpty) {
final user = User()
..id = DateTime.now().millisecondsSinceEpoch.toString()
..name = nameController.text
..email = emailController.text
..createdAt = DateTime.now();
await _dbService.createUser(user);
Navigator.pop(context);
}
},
child: const Text('Add'),
),
],
),
);
}
void _showUserDetails(BuildContext context, User user) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(user.displayName),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Email: ${user.email}'),
const SizedBox(height: 8),
Text('Created: ${user.createdAt.toString().split('.')[0]}'),
Text('Active: ${user.isActive ? 'Yes' : 'No'}'),
if (user.tags.isNotEmpty) ...[
const SizedBox(height: 8),
Text('Tags: ${user.tags.join(', ')}'),
],
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
}
void _showBackupDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Backup Data'),
content: const Text('Create a backup of all local data?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
await _dbService.backupData('');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: 'Backup completed successfully'),
);
},
child: const Text('Backup'),
),
],
),
);
}
void _showRestoreDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Restore Data'),
content: const Text('Restore data from backup file?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () async {
// For demo purposes, using a fixed path
// In real app, you'd show a file picker
try {
await _dbService.restoreData('backup_path.hive');
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: 'Data restored successfully'),
);
} catch (e) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: 'Failed to restore data: $e'),
);
}
},
child: const Text('Restore'),
),
],
),
);
}
}
class UserCard extends StatelessWidget {
final User user;
final VoidCallback onTap;
const UserCard({
Key? key,
required this.user,
required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: ListTile(
leading: CircleAvatar(
child: Text(user.name.isNotEmpty ? user.name[0] : user.email[0]),
),
title: Text(user.displayName),
subtitle: Text(user.email),
trailing: Icon(
user.isActive ? Icons.check_circle : Icons.cancel,
color: user.isActive ? Colors.green : Colors.red,
),
onTap: onTap,
),
);
}
}
class _StatCard extends StatelessWidget {
final String label;
final String value;
const _StatCard({
Key? key,
required this.label,
required this.value,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
value,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
],
),
),
);
}
}
// 5. Advanced Features
// IndexedBox for better performance with large datasets
class IndexedUserBox {
late final Box<int> _indexBox;
Future<void> open() async {
_indexBox = await Hive.openBox<int>('user_index');
}
Future<void> indexUser(User user) async {
final index = await _indexBox.get(user.email) ?? [];
if (!index.contains(user.id)) {
index.add(user.id);
await _indexBox.put(user.email, index);
}
}
Future<List<String>> getUserIdsByEmail(String email) async {
final index = await _indexBox.get(email) ?? [];
return index;
}
}
// LazyBox for efficient querying
class LazyUserService {
static final LazyUserService _instance = LazyUserService._internal();
factory LazyUserService() => _instance;
LazyUserService._internal();
late final LazyBox<User> _lazyBox;
Future<void> open() async {
_lazyBox = await Hive.openLazyBox<User>('users_lazy');
}
Stream<User> watchUser(String id) {
return _lazyBox.watch(id);
}
Future<void> updateUser(String id, User user) async {
await _lazyBox.put(id, user);
}
Future<User?> getUser(String id) async {
return _lazyBox.get(id);
}
}
// Hive Queries
class UserQueries {
static Future<List<User>> findActiveUsers() async {
final box = await Hive.openBox<User>('users');
return box.values.where((user) => user.isActive).toList();
}
static Future<List<User>> findUsersCreatedAfter(DateTime date) async {
final box = await Hive.openBox<User>('users');
return box.values
.where((user) => user.createdAt.isAfter(date))
.toList();
}
static Future<List<User>> searchByName(String name) async {
final box = await Hive.openBox<User>('users');
return box.values
.where((user) => user.name.toLowerCase().contains(name.toLowerCase()))
.toList();
}
}
// Migration Example
class DatabaseMigration {
static Future<void> migrateToV2() async {
final box = await Hive.openBox<User>('users');
// Add new field to existing users
for (final user in box.values) {
if (user.tags.isEmpty) {
user.tags = ['default'];
await box.put(user);
}
}
}
}
export { DatabaseService, User, Product, Order, OrderStatus };
💻 Оптимизация Производительности Hive dart
🟡 intermediate
⭐⭐⭐⭐
Расширенные техники оптимизации Hive с ленивой загрузкой, индексацией и эффективным управлением данными
⏱️ 50 min
🏷️ flutter, hive, performance, optimization
Prerequisites:
Flutter, Hive basics, Dart async programming
// Hive Performance Optimization Techniques
// Efficient data management for Flutter applications
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'dart:async';
import 'package:meta/meta.dart';
// 1. Efficient Box Management
class EfficientBoxManager {
static final Map<String, Box> _boxCache = {};
static final Map<String, LazyBox> _lazyBoxCache = {};
// Get or create box with caching
static Future<T> getBox<T>(String name, {bool lazy = false}) async {
if (!lazy && _boxCache.containsKey(name)) {
return _boxCache[name] as Box<T>;
}
if (lazy && _lazyBoxCache.containsKey(name)) {
return _lazyBoxCache[name] as LazyBox<T>;
}
if (lazy) {
final box = await Hive.openLazyBox<T>(name);
_lazyBoxCache[name] = box;
return box;
} else {
final box = await Hive.openBox<T>(name);
_boxCache[name] = box;
return box;
}
}
// Preload frequently used boxes
static Future<void> preloadBoxes(List<String> boxNames) async {
await Future.wait(
boxNames.map((name) => getBox(name)),
);
}
// Close all boxes
static Future<void> closeAll() async {
for (final box in _boxCache.values) {
await box.close();
}
for (final box in _lazyBoxCache.values) {
await box.close();
}
_boxCache.clear();
_lazyBoxCache.clear();
}
}
// 2. Pagination and Batch Operations
class PaginationService<T> {
final String _boxName;
final int _pageSize;
PaginationService(this._boxName, {int pageSize = 20}) : _pageSize = pageSize;
// Get paginated results
Future<PaginatedResult<T>> getPage(int page, {String? sortBy, bool ascending = true}) async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
final values = box.values.toList();
// Sort if needed
if (sortBy != null) {
values.sort((a, b) {
final aValue = _getSortValue(a, sortBy);
final bValue = _getSortValue(b, sortBy);
return ascending
? aValue.compareTo(bValue)
: bValue.compareTo(aValue);
});
}
final startIndex = page * _pageSize;
final endIndex = (page + 1) * _pageSize;
final items = startIndex < values.length
? values.sublist(startIndex, endIndex.clamp(0, values.length))
: <T>[];
return PaginatedResult<T>(
items: items,
page: page,
pageSize: _pageSize,
totalItems: values.length,
hasNextPage: endIndex < values.length,
);
}
dynamic _getSortValue(T item, String sortBy) {
// This would be implemented based on your model
// Example for User model:
if (item is User) {
final user = item as User;
switch (sortBy) {
case 'name':
return user.name;
case 'createdAt':
return user.createdAt.millisecondsSinceEpoch;
case 'email':
return user.email;
default:
return '';
}
}
return '';
}
// Batch insert
Future<void> batchInsert(List<T> items) async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
final writeBatch = <Future<void>>[];
for (final item in items) {
writeBatch.add(box.put(item));
}
await Future.wait(writeBatch);
}
// Stream paginated results
Stream<PaginatedResult<T>> streamPages({
int initialPage = 0,
String? sortBy,
bool ascending = true,
}) async* {
var currentPage = initialPage;
bool hasNext = true;
while (hasNext) {
final result = await getPage(currentPage, sortBy: sortBy, ascending: ascending);
yield result;
hasNext = result.hasNextPage;
currentPage++;
}
}
}
class PaginatedResult<T> {
final List<T> items;
final int page;
final int pageSize;
final int totalItems;
final bool hasNextPage;
PaginatedResult({
required this.items,
required this.page,
required this.pageSize,
required this.totalItems,
required this.hasNextPage,
});
int get totalPages => (totalItems / pageSize).ceil();
int get currentPageIndex => page;
}
// 3. Search and Filtering
class AdvancedSearchService<T> {
final String _boxName;
late final Box<T> _box;
AdvancedSearchService(this._boxName);
Future<void> initialize() async {
_box = await EfficientBoxManager.getBox<T>(_boxName);
}
// Full-text search with filters
Future<List<T>> search(SearchQuery query) async {
var results = _box.values.toList();
// Apply text search
if (query.searchTerm.isNotEmpty) {
results = results.where((item) => _matchesSearch(item, query.searchTerm)).toList();
}
// Apply filters
for (final filter in query.filters) {
results = results.where((item) => _matchesFilter(item, filter)).toList();
}
// Apply sorting
if (query.sortBy.isNotEmpty) {
results.sort((a, b) => _compareItems(a, b, query.sortBy, query.ascending));
}
// Apply limit and offset
if (query.limit > 0) {
final start = query.offset;
final end = start + query.limit;
results = results.sublist(start, end.clamp(0, results.length));
}
return results;
}
bool _matchesSearch(T item, String searchTerm) {
// This would be implemented based on your model
// Example implementation:
if (item is User) {
final user = item as User;
final term = searchTerm.toLowerCase();
return user.name.toLowerCase().contains(term) ||
user.email.toLowerCase().contains(term) ||
user.tags.any((tag) => tag.toLowerCase().contains(term));
}
return false;
}
bool _matchesFilter(T item, Filter filter) {
// Implementation based on filter type and field
switch (filter.type) {
case FilterType.equals:
return _getFieldValue(item, filter.field) == filter.value;
case FilterType.contains:
return _getFieldValue(item, filter.field).toString().contains(filter.value.toString());
case FilterType.greaterThan:
return _compareValues(_getFieldValue(item, filter.field), filter.value) > 0;
case FilterType.lessThan:
return _compareValues(_getFieldValue(item, filter.field), filter.value) < 0;
case FilterType.isInList:
final list = filter.value as List;
return list.contains(_getFieldValue(item, filter.field));
default:
return true;
}
}
dynamic _getFieldValue(T item, String field) {
// Implementation based on your model
if (item is User) {
final user = item as User;
switch (field) {
case 'name':
return user.name;
case 'email':
return user.email;
case 'isActive':
return user.isActive;
case 'createdAt':
return user.createdAt;
case 'tags':
return user.tags;
default:
return null;
}
}
return null;
}
int _compareValues(dynamic a, dynamic b) {
// Generic comparison
if (a == null) return b == null ? 0 : -1;
if (b == null) return 1;
return a.toString().compareTo(b.toString());
}
int _compareItems(T a, T b, String sortBy, bool ascending) {
final aValue = _getFieldValue(a, sortBy);
final bValue = _getFieldValue(b, sortBy);
final comparison = _compareValues(aValue, bValue);
return ascending ? comparison : -comparison;
}
}
class SearchQuery {
String searchTerm = '';
List<Filter> filters = [];
String sortBy = '';
bool ascending = true;
int limit = 0;
int offset = 0;
}
enum FilterType {
equals,
contains,
greaterThan,
lessThan,
isInList,
}
class Filter {
final String field;
final FilterType type;
final dynamic value;
Filter({required this.field, required this.type, required this.value});
}
// 4. Caching Layer
class HiveCacheService<T> {
final String _boxName;
final Duration _cacheTtl;
final Map<String, CachedItem<T>> _cache = {};
HiveCacheService(this._boxName, {Duration cacheTtl = const Duration(minutes: 5)})
: _cacheTtl = cacheTtl;
Future<T?> get(String key, {bool useCache = true}) async {
if (!useCache || !_cache.containsKey(key)) {
final box = await EfficientBoxManager.getBox<T>(_boxName);
final item = box.get(key);
if (item != null) {
_cache[key] = CachedItem(item, DateTime.now().add(_cacheTtl));
}
return item;
}
final cachedItem = _cache[key]!;
if (DateTime.now().isAfter(cachedItem.expiresAt)) {
_cache.remove(key);
return null;
}
return cachedItem.item;
}
Future<void> put(String key, T item) async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
await box.put(key, item);
_cache[key] = CachedItem(item, DateTime.now().add(_cacheTtl));
}
Future<void> remove(String key) async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
await box.delete(key);
_cache.remove(key);
}
void clearCache() {
_cache.clear();
}
// Preload frequently accessed items
Future<void> preload(List<String> keys) async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
for (final key in keys) {
final item = box.get(key);
if (item != null) {
_cache[key] = CachedItem(item, DateTime.now().add(_cacheTtl));
}
}
}
}
class CachedItem<T> {
final T item;
final DateTime expiresAt;
CachedItem(this.item, this.expiresAt);
}
// 5. Background Sync Service
class BackgroundSyncService<T> {
final String _boxName;
final Duration _syncInterval;
Timer? _syncTimer;
bool _isSyncing = false;
BackgroundSyncService(this._boxName, {Duration syncInterval = const Duration(minutes: 30)})
: _syncInterval = syncInterval;
Future<void> startAutoSync({Function(List<T>)? onSync}) async {
_syncTimer?.cancel();
_syncTimer = Timer.periodic(_syncInterval, (_) async {
if (!_isSyncing) {
_isSyncing = true;
try {
final items = await _performSync();
if (onSync != null) {
onSync(items);
}
} finally {
_isSyncing = false;
}
}
});
}
void stopAutoSync() {
_syncTimer?.cancel();
_syncTimer = null;
}
Future<List<T>> _performSync() async {
final box = await EfficientBoxManager.getBox<T>(_boxName);
final items = box.values.toList();
// Perform sync logic here (e.g., sync with remote API)
return items;
}
}
// 6. Data Validation and Migration
class DataValidator<T> {
final List<ValidationRule<T>> _rules;
DataValidator(this._rules);
ValidationResult validate(T item) {
final errors = <String>[];
for (final rule in _rules) {
final result = rule.validate(item);
if (!result.isValid) {
errors.addAll(result.errors);
}
}
return ValidationResult(
isValid: errors.isEmpty,
errors: errors,
);
}
Future<ValidationResult> validateAndSave(T item, Future<void> Function(T) saveFunc) async {
final result = validate(item);
if (result.isValid) {
await saveFunc(item);
}
return result;
}
}
abstract class ValidationRule<T> {
ValidationResult validate(T item);
}
class ValidationResult {
final bool isValid;
final List<String> errors;
ValidationResult({required this.isValid, required this.errors});
}
// Example validation rules for User model
class UserNameValidationRule extends ValidationRule<User> {
@override
ValidationResult validate(User user) {
final errors = <String>[];
if (user.name.isEmpty) {
errors.add('Name cannot be empty');
}
if (user.name.length < 2) {
errors.add('Name must be at least 2 characters');
}
return ValidationResult(isValid: errors.isEmpty, errors: errors);
}
}
class EmailValidationRule extends ValidationRule<User> {
@override
ValidationResult validate(User user) {
final errors = <String>[];
if (user.email.isEmpty) {
errors.add('Email cannot be empty');
}
if (!_isValidEmail(user.email)) {
errors.add('Invalid email format');
}
return ValidationResult(isValid: errors.isEmpty, errors: errors);
}
bool _isValidEmail(String email) {
return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
}
}
// 7. Performance Monitoring
class PerformanceMonitor {
static final Map<String, List<int>> _operationTimes = {};
static void recordOperation(String operation, int durationMs) {
_operationTimes.putIfAbsent(operation, <int>[]);
_operationTimes[operation]!.add(durationMs);
}
static Map<String, PerformanceStats> getStats() {
final stats = <String, PerformanceStats>{};
for (final entry in _operationTimes.entries) {
final times = entry.value;
if (times.isNotEmpty) {
final avg = times.reduce((a, b) => a + b) / times.length;
final min = times.reduce((a, b) => a < b ? a : b);
final max = times.reduce((a, b) => a > b ? a : b);
stats[entry.key] = PerformanceStats(
operation: entry.key,
averageTime: avg,
minTime: min,
maxTime: max,
operationCount: times.length,
);
}
}
return stats;
}
static void clearStats() {
_operationTimes.clear();
}
}
class PerformanceStats {
final String operation;
final double averageTime;
final int minTime;
final int maxTime;
final int operationCount;
PerformanceStats({
required this.operation,
required this.averageTime,
required this.minTime,
required this.maxTime,
required this.operationCount,
});
@override
String toString() {
return '$operation: avg=${averageTime.toStringAsFixed(2)}ms, '
'min=$minTime ms, max=$maxTime ms, count=$operationCount';
}
}
// 8. Usage Example
void demonstrateOptimizations() async {
// Initialize efficient box manager
await EfficientBoxManager.preloadBoxes(['users', 'products', 'orders']);
// Create services
final userService = PaginationService<User>('users', pageSize: 10);
final userCache = HiveCacheService<User>('users');
final searchService = AdvancedSearchService<User>('users');
// Initialize search service
await searchService.initialize();
// Performance monitoring
final stopwatch = Stopwatch()..start();
// Use pagination
final usersPage1 = await userService.getPage(0);
stopwatch.stop();
PerformanceMonitor.recordOperation('get_users_page', stopwatch.elapsedMilliseconds);
// Use cache
stopwatch.reset()..start();
final cachedUser = await userCache.get('user_1');
stopwatch.stop();
PerformanceMonitor.recordOperation('get_user_cached', stopwatch.elapsedMilliseconds);
// Use search
stopwatch.reset()..start();
final searchResults = await searchService.search(
SearchQuery(
searchTerm: 'john',
filters: [
Filter(field: 'isActive', type: FilterType.equals, value: true),
],
sortBy: 'createdAt',
limit: 20,
),
);
stopwatch.stop();
PerformanceMonitor.recordOperation('search_users', stopwatch.elapsedMilliseconds);
// Show performance stats
final stats = PerformanceMonitor.getStats();
for (final stat in stats.values) {
print(stat);
}
}
// 9. Widget Optimization
class OptimizedListView<T> extends StatefulWidget {
final List<T> items;
final Widget Function(BuildContext, T) itemBuilder;
final ScrollController? controller;
final bool lazyLoading;
const OptimizedListView({
Key? key,
required this.items,
required this.itemBuilder,
this.controller,
this.lazyLoading = true,
}) : super(key: key);
@override
_OptimizedListViewState<T> createState() => _OptimizedListViewState<T>();
}
class _OptimizedListViewState<T> extends State<OptimizedListView<T>> {
late final ScrollController _scrollController;
late final Set<int> _visibleIndices = {};
@override
void initState() {
super.initState();
_scrollController = widget.controller ?? ScrollController();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
// Implement visibility tracking for lazy loading
}
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _scrollController,
itemCount: widget.items.length,
itemBuilder: (context, index) {
return _buildOptimizedItem(context, index);
},
);
}
Widget _buildOptimizedContext(BuildContext context, int index) {
if (_visibleIndices.contains(index)) {
return widget.itemBuilder(context, widget.items[index]);
}
// Return placeholder for off-screen items
return SizedBox(
height: 60.0, // Estimated item height
child: Container(),
);
}
Widget _buildOptimizedItem(BuildContext context, int index) {
return RepaintBoundary(
key: ValueKey(widget.items[index]),
child: widget.itemBuilder(context, widget.items[index]),
);
}
}
// 10. Memory Management
class MemoryManager {
static void cleanupOldCache() async {
// Clean up items older than 1 hour
final cutoff = DateTime.now().subtract(const Duration(hours: 1));
// This would be implemented based on your cache strategy
}
static Future<void> compactDatabase() async {
final boxes = ['users', 'products', 'orders'];
for (final boxName in boxes) {
final box = await Hive.openBox(boxName);
await box.compact();
}
}
}
export {
EfficientBoxManager,
PaginationService,
AdvancedSearchService,
HiveCacheService,
BackgroundSyncService,
DataValidator,
PerformanceMonitor,
OptimizedListView,
MemoryManager,
};
💻 Синхронизация Hive в Реальном Времени dart
🔴 complex
⭐⭐⭐⭐⭐
Синхронизация данных в реальном времени с REST API, поддержкой автономного режима и разрешением конфликтов
⏱️ 70 min
🏷️ flutter, hive, sync, realtime, offline
Prerequisites:
Flutter, Hive, HTTP requests, Async programming
// Hive Real-time Synchronization with REST API
// Offline-first architecture with automatic sync and conflict resolution
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
// 1. Sync Configuration
class SyncConfiguration {
final String baseUrl;
final String apiKey;
final Duration syncInterval;
final Duration timeout;
final int maxRetries;
final bool enableAutoSync;
final Map<String, String> headers;
const SyncConfiguration({
required this.baseUrl,
required this.apiKey,
this.syncInterval = const Duration(minutes: 5),
this.timeout = const Duration(seconds: 30),
this.maxRetries = 3,
this.enableAutoSync = true,
Map<String, String>? headers,
}) : headers = headers ?? {};
}
// 2. Sync Status
enum SyncStatus {
idle,
syncing,
success,
error,
conflict,
offline
}
class SyncResult {
final bool success;
final String? message;
final DateTime timestamp;
final int syncedItems;
final List<SyncConflict> conflicts;
final List<SyncError> errors;
const SyncResult({
required this.success,
this.message,
required this.timestamp,
this.syncedItems = 0,
this.conflicts = const [],
this.errors = const [],
});
bool get hasConflicts => conflicts.isNotEmpty;
bool get hasErrors => errors.isNotEmpty;
}
class SyncConflict {
final String entityId;
final String entityType;
final String localData;
final String remoteData;
final ConflictType type;
final DateTime timestamp;
const SyncConflict({
required this.entityId,
required this.entityType,
required this.localData,
required this.remoteData,
required this.type,
required this.timestamp,
});
}
enum ConflictType {
updateConflict,
deleteConflict,
createConflict,
}
class SyncError {
final String entityId;
final String entityType;
final String error;
final StackTrace? stackTrace;
final DateTime timestamp;
const SyncError({
required this.entityId,
required this.entityType,
required this.error,
this.stackTrace,
required this.timestamp,
});
}
// 3. Generic Sync Entity
abstract class SyncEntity {
String get id;
String get entityType;
DateTime get lastModified;
Map<String, dynamic> toJson();
// Sync metadata
String? syncId;
DateTime? lastSyncAt;
bool isDeleted = false;
bool needsSync = true;
// Conflict resolution
bool autoResolve(SyncEntity remoteEntity);
SyncEntity mergeWith(SyncEntity other);
}
// 4. Concrete Entity Example
@HiveType(typeId: 0)
class SyncUser extends HiveObject implements SyncEntity {
@HiveField(0)
late String id;
@HiveField(1)
late String name;
@HiveField(2)
late String email;
@HiveField(3)
late DateTime lastModified;
@HiveField(4)
String? syncId;
@HiveField(5)
DateTime? lastSyncAt;
@HiveField(6)
bool isDeleted = false;
@HiveField(7)
bool needsSync = true;
@override
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'email': email,
'lastModified': lastModified.toIso8601String(),
'syncId': syncId,
'lastSyncAt': lastSyncAt?.toIso8601String(),
'isDeleted': isDeleted,
'needsSync': needsSync,
};
}
factory SyncUser.fromJson(Map<String, dynamic> json) {
final user = SyncUser();
user.id = json['id'];
user.name = json['name'];
user.email = json['email'];
user.lastModified = DateTime.parse(json['lastModified']);
user.syncId = json['syncId'];
user.lastSyncAt = json['lastSyncAt'] != null
? DateTime.parse(json['lastSyncAt'])
: null;
user.isDeleted = json['isDeleted'] ?? false;
user.needsSync = json['needsSync'] ?? true;
return user;
}
@override
bool autoResolve(SyncEntity remoteEntity) {
if (remoteEntity is! SyncUser) return false;
final remoteUser = remoteEntity as SyncUser;
// Auto-resolve by using the most recent version
if (lastModified.isAfter(remoteUser.lastModified)) {
return true; // Keep local version
} else {
return false; // Keep remote version (will be merged)
}
}
@override
SyncEntity mergeWith(SyncEntity other) {
if (other is! SyncUser) return this;
final remoteUser = other as SyncUser;
// Merge logic: prefer remote data but keep some local fields
name = remoteUser.name;
email = remoteUser.email;
lastModified = remoteUser.lastModified;
isDeleted = remoteUser.isDeleted;
return this;
}
}
// 5. HTTP API Client
class ApiClient {
final SyncConfiguration config;
late final http.Client _client;
ApiClient(this.config) {
_client = http.Client();
}
Future<http.Response> get(String endpoint, {Map<String, String>? queryParams}) async {
final uri = _buildUri(endpoint, queryParams);
final response = await _client.get(
uri,
headers: _buildHeaders(),
).timeout(config.timeout);
return _handleResponse(response);
}
Future<http.Response> post(String endpoint, {Map<String, dynamic>? body}) async {
final uri = _buildUri(endpoint);
final response = await _client.post(
uri,
headers: _buildHeaders(),
body: body != null ? jsonEncode(body) : null,
).timeout(config.timeout);
return _handleResponse(response);
}
Future<http.Response> put(String endpoint, {Map<String, dynamic>? body}) async {
final uri = _buildUri(endpoint);
final response = await _client.put(
uri,
headers: _buildHeaders(),
body: body != null ? jsonEncode(body) : null,
).timeout(config.timeout);
return _handleResponse(response);
}
Future<http.Response> delete(String endpoint) async {
final uri = _buildUri(endpoint);
final response = await _client.delete(
uri,
headers: _buildHeaders(),
).timeout(config.timeout);
return _handleResponse(response);
}
Uri _buildUri(String endpoint, [Map<String, String>? queryParams]) {
var uri = Uri.parse('${config.baseUrl}${endpoint}');
if (queryParams != null) {
uri = uri.replace(queryParameters: queryParams);
}
return uri;
}
Map<String, String> _buildHeaders() {
final headers = <String, String>{
'Content-Type': 'application/json',
'Authorization': 'Bearer ${config.apiKey}',
...config.headers,
};
return headers;
}
http.Response _handleResponse(http.Response response) {
if (response.statusCode >= 400) {
throw ApiException(
'API Error: ${response.statusCode} - ${response.reasonPhrase}',
response.statusCode,
response.body,
);
}
return response;
}
}
class ApiException implements Exception {
final String message;
final int statusCode;
final dynamic body;
const ApiException(this.message, this.statusCode, this.body);
@override
String toString() => 'ApiException: $message';
}
// 6. Sync Service
class SyncService {
final SyncConfiguration config;
final ApiClient _apiClient;
final Map<Type, EntitySyncHandler> _handlers;
SyncStatus _status = SyncStatus.idle;
Timer? _syncTimer;
final StreamController<SyncStatus> _statusController = StreamController<SyncStatus>.broadcast();
final StreamController<SyncResult> _resultController = StreamController<SyncResult>.broadcast();
// Cache for sync state
final Map<String, DateTime> _lastSyncTimes = {};
final Map<String, bool> _syncLocks = {};
SyncService(this.config) : _apiClient = ApiClient(config) {
_handlers = {};
_registerDefaultHandlers();
}
Stream<SyncStatus> get statusStream => _statusController.stream;
Stream<SyncResult> get resultStream => _resultController.stream;
SyncStatus get currentStatus => _status;
void _registerDefaultHandlers() {
// Register handlers for different entity types
registerHandler<SyncUser>(UserSyncHandler(_apiClient));
// Add more handlers as needed
}
void registerHandler<T extends SyncEntity>(EntitySyncHandler<T> handler) {
_handlers[T] = handler;
}
Future<void> startAutoSync() async {
if (!config.enableAutoSync) return;
_syncTimer?.cancel();
_syncTimer = Timer.periodic(config.syncInterval, (_) async {
if (_status == SyncStatus.idle) {
await performFullSync();
}
});
}
void stopAutoSync() {
_syncTimer?.cancel();
_syncTimer = null;
}
Future<SyncResult> performFullSync() async {
_setStatus(SyncStatus.syncing);
try {
final conflicts = <SyncConflict>[];
final errors = <SyncError>[];
int syncedItems = 0;
// Process each entity type
for (final handler in _handlers.values) {
final result = await handler.syncAll();
syncedItems += result.syncedItems;
conflicts.addAll(result.conflicts);
errors.addAll(result.errors);
}
final syncResult = SyncResult(
success: errors.isEmpty && conflicts.isEmpty,
timestamp: DateTime.now(),
syncedItems: syncedItems,
conflicts: conflicts,
errors: errors,
message: 'Sync completed',
);
_setStatus(SyncStatus.success);
_resultController.add(syncResult);
return syncResult;
} catch (e) {
_setStatus(SyncStatus.error);
final syncResult = SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: 'global',
entityType: 'system',
error: e.toString(),
stackTrace: e.stackTrace,
timestamp: DateTime.now(),
),
],
);
_resultController.add(syncResult);
return syncResult;
}
}
Future<SyncResult> syncEntity<T extends SyncEntity>(T entity) async {
if (!_handlers.containsKey(T)) {
throw UnsupportedEntityType(T.toString());
}
_setStatus(SyncStatus.syncing);
try {
final handler = _handlers[T] as EntitySyncHandler<T>;
final result = await handler.syncEntity(entity);
_setStatus(SyncStatus.success);
_resultController.add(result);
return result;
} catch (e) {
_setStatus(SyncStatus.error);
final syncResult = SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: entity.id,
entityType: entity.entityType,
error: e.toString(),
stackTrace: e.stackTrace,
timestamp: DateTime.now(),
),
],
);
_resultController.add(syncResult);
return syncResult;
}
}
Future<SyncResult> resolveConflict(String entityId, ConflictResolution resolution) async {
// Find the conflicting entity
for (final handler in _handlers.values) {
final result = await handler.resolveConflict(entityId, resolution);
if (result != null) {
return result;
}
}
return SyncResult(
success: false,
timestamp: DateTime.now(),
message: 'Conflict not found for entity: $entityId',
);
}
void _setStatus(SyncStatus status) {
_status = status;
_statusController.add(status);
}
void dispose() {
stopAutoSync();
_statusController.close();
_resultController.close();
}
}
// 7. Entity Sync Handler
abstract class EntitySyncHandler<T extends SyncEntity> {
final ApiClient _apiClient;
final String endpoint;
EntitySyncHandler(this._apiClient, this.endpoint);
Future<SyncResult> syncAll() async {
final conflicts = <SyncConflict>[];
final errors = <SyncError>[];
int syncedItems = 0;
try {
// Get all local entities that need sync
final localEntities = await getLocalEntities();
final remoteEntities = await getRemoteEntities();
// Process local entities
for (final entity in localEntities) {
if (entity.needsSync) {
try {
final result = await syncEntity(entity);
syncedItems += result.syncedItems;
conflicts.addAll(result.conflicts);
errors.addAll(result.errors);
} catch (e) {
errors.add(SyncError(
entityId: entity.id,
entityType: entity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
));
}
}
}
// Process remote entities (for deletions and missing local entities)
for (final remoteEntity in remoteEntities) {
final localEntity = localEntities.cast<T?>().firstWhere(
(e) => e?.id == remoteEntity.id,
orElse: () => null,
);
if (localEntity == null) {
// Remote entity doesn't exist locally, create it
try {
final entity = await createFromRemote(remoteEntity);
syncedItems++;
} catch (e) {
errors.add(SyncError(
entityId: remoteEntity.id,
entityType: remoteEntity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
));
}
}
}
} catch (e) {
errors.add(SyncError(
entityId: 'all',
entityType: endpoint,
error: e.toString(),
timestamp: DateTime.now(),
));
}
return SyncResult(
success: errors.isEmpty && conflicts.isEmpty,
timestamp: DateTime.now(),
syncedItems: syncedItems,
conflicts: conflicts,
errors: errors,
);
}
Future<SyncResult> syncEntity(T entity) async {
try {
if (entity.isDeleted) {
return await deleteRemote(entity);
} else {
return await upsertRemote(entity);
}
} catch (e) {
return SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: entity.id,
entityType: entity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
),
],
);
}
}
Future<SyncResult?> resolveConflict(String entityId, ConflictResolution resolution) async {
try {
// Get the conflicting entities
final localEntity = await getLocalEntity(entityId);
final remoteEntity = await getRemoteEntity(entityId);
if (localEntity == null || remoteEntity == null) {
return null;
}
switch (resolution) {
case ConflictResolution.useLocal:
// Force upload local version
await forceUpload(localEntity);
return SyncResult(success: true, timestamp: DateTime.now());
case ConflictResolution.useRemote:
// Force download remote version
await forceDownload(remoteEntity);
return SyncResult(success: Future.success: true, timestamp: DateTime.now());
case ConflictResolution.merge:
// Merge entities
final mergedEntity = localEntity.mergeWith(remoteEntity);
await updateLocal(mergedEntity);
await forceUpload(mergedEntity);
return SyncResult(success: true, timestamp: DateTime.now());
}
} catch (e) {
return SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: entityId,
entityType: endpoint,
error: e.toString(),
timestamp: DateTime.now(),
),
],
);
}
}
Future<SyncResult> upsertRemote(T entity) async {
try {
final remoteEntity = await getRemoteEntity(entity.id);
if (remoteEntity == null) {
// Create new remote entity
await createRemote(entity);
} else {
// Update existing remote entity
await updateRemote(entity);
}
// Update sync metadata
entity.syncId = _generateSyncId();
entity.lastSyncAt = DateTime.now();
entity.needsSync = false;
await updateLocal(entity);
return SyncResult(success: true, timestamp: DateTime.now());
} catch (e) {
return SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: entity.id,
entityType: entity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
),
],
);
}
}
Future<SyncResult> deleteRemote(T entity) async {
try {
await _apiClient.delete('$endpoint/${entity.id}');
// Update local entity metadata
entity.isDeleted = true;
entity.lastSyncAt = DateTime.now();
entity.needsSync = false;
await updateLocal(entity);
return SyncResult(success: true, timestamp: DateTime.now());
} catch (e) {
return SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: entity.id,
entityType: entity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
),
],
);
}
}
Future<SyncResult> forceUpload(T entity) async {
// Clear sync metadata to force upload
entity.syncId = null;
entity.needsSync = true;
return await upsertRemote(entity);
}
Future<SyncResult> forceDownload(T remoteEntity) async {
try {
final localEntity = await createFromRemote(remoteEntity);
localEntity.syncId = _generateSyncId();
localEntity.lastSyncAt = DateTime.now();
localEntity.needsSync = false;
return SyncResult(
success: true,
timestamp: DateTime.now(),
syncedItems: 1,
);
} catch (e) {
return SyncResult(
success: false,
timestamp: DateTime.now(),
errors: [
SyncError(
entityId: remoteEntity.id,
entityType: remoteEntity.entityType,
error: e.toString(),
timestamp: DateTime.now(),
),
],
);
}
}
// Abstract methods to be implemented by concrete handlers
Future<List<T>> getLocalEntities();
Future<T?> getLocalEntity(String id);
Future<void> updateLocal(T entity);
Future<void> createLocal(T entity);
Future<void> deleteLocal(String id);
Future<List<T>> getRemoteEntities() async {
final response = await _apiClient.get(endpoint);
final List<dynamic> data = jsonDecode(response.body);
return data.map((item) => createFromJson(item)).cast<T>();
}
Future<T?> getRemoteEntity(String id) async {
try {
final response = await _apiClient.get('$endpoint/$id');
final data = jsonDecode(response.body);
return createFromJson(data);
} on ApiException catch (e) {
if (e.statusCode == 404) {
return null; // Entity not found
}
rethrow;
}
}
Future<void> createRemote(T entity) async {
await _apiClient.post(endpoint, body: entity.toJson());
}
Future<void> updateRemote(T entity) async {
await _apiClient.put('$endpoint/${entity.id}', body: entity.toJson());
}
Future<T> createFromJson(Map<String, dynamic> json) async {
throw UnimplementedError('createFromJson must be implemented by concrete handlers');
}
Future<T> createFromRemote(T remoteEntity) async {
return createFromJson(remoteEntity.toJson());
}
String _generateSyncId() {
return DateTime.now().millisecondsSinceEpoch.toString();
}
}
// 8. User Sync Handler
class UserSyncHandler extends EntitySyncHandler<SyncUser> {
UserSyncHandler(super.apiClient, 'users');
@override
Future<List<SyncUser>> getLocalEntities() async {
final box = await Hive.openBox<SyncUser>('users');
return box.values.where((user) => !user.isDeleted).toList();
}
@override
Future<SyncUser?> getLocalEntity(String id) async {
final box = await Hive.openBox<SyncUser>('users');
return box.get(id);
}
@override
Future<void> updateLocal(SyncUser entity) async {
final box = await Hive.openBox<SyncUser>('users');
await box.put(entity);
}
@override
Future<void> createLocal(SyncUser entity) async {
final box = await Hive.openBox<SyncUser>('users');
await box.put(entity);
}
@override
Future<void> deleteLocal(String id) async {
final box = await Hive.openBox<SyncUser>('users');
await box.delete(id);
}
@override
Future<SyncUser> createFromJson(Map<String, dynamic> json) async {
return SyncUser.fromJson(json);
}
}
// 9. Conflict Resolution Strategies
enum ConflictResolution {
useLocal,
useRemote,
merge,
}
// 10. Sync Manager - High-level interface
class SyncManager {
final Map<Type, DatabaseService> _databaseServices;
late final SyncService _syncService;
late final ConflictResolutionStrategy _conflictStrategy;
SyncManager(
SyncConfiguration syncConfig,
this._databaseServices, {
ConflictResolutionStrategy conflictStrategy = ConflictResolutionStrategy.auto,
}) : _conflictStrategy = conflictStrategy {
_syncService = SyncService(syncConfig);
}
Future<void> initialize() async {
// Initialize database services
for (final service in _databaseServices.values) {
await service.initialize();
}
// Register sync handlers
_registerSyncHandlers();
// Start auto sync if enabled
await _syncService.startAutoSync();
}
void _registerSyncHandlers() {
for (final entry in _databaseServices.entries) {
_syncService.registerHandler(entry.key, entry.value.createSyncHandler(_syncService._apiClient));
}
}
Future<SyncResult> syncAll() async {
return await _syncService.performFullSync();
}
Future<SyncResult> syncEntity<T extends SyncEntity>(T entity) async {
final result = await _syncService.syncEntity(entity);
if (result.hasConflicts) {
return await _handleConflicts(result);
}
return result;
}
Future<SyncResult> _handleConflicts(SyncResult result) async {
for (final conflict in result.conflicts) {
ConflictResolution? resolution;
switch (_conflictStrategy) {
case ConflictResolutionStrategy.auto:
resolution = await _autoResolveConflict(conflict);
break;
case ConflictResolutionStrategy.manual:
resolution = await _promptForResolution(conflict);
break;
case ConflictResolutionStrategy.local:
resolution = ConflictResolution.useLocal;
break;
case ConflictResolutionStrategy.remote:
resolution = ConflictResolution.useRemote;
break;
}
if (resolution != null) {
final resolutionResult = await _syncService.resolveConflict(conflict.entityId, resolution);
// Update result with resolution outcome
}
}
return result;
}
Future<ConflictResolution?> _autoResolveConflict(SyncConflict conflict) async {
final localEntity = await _getEntity(conflict.entityId, conflict.entityType);
if (localEntity?.autoResolve(_createFromJson(jsonDecode(conflict.remoteData))) ?? false) {
return ConflictResolution.useLocal;
}
return ConflictResolution.merge;
}
Future<ConflictResolution?> _promptForResolution(SyncConflict conflict) async {
// In a real app, this would show a UI dialog
// For now, return auto-resolution
return _autoResolveConflict(conflict);
}
Future<T?> _getEntity<T>(String id, String entityType) async {
final service = _databaseServices[T];
return await service.getEntity(id);
}
Stream<SyncStatus> get statusStream => _syncService.statusStream;
Stream<SyncResult> get resultStream => _syncService.resultStream;
void dispose() {
_syncService.dispose();
}
}
// 11. Database Service Interface
abstract class DatabaseService<T> {
Future<void> initialize();
Future<T?> getEntity(String id);
Future<void> saveEntity(T entity);
Future<void> deleteEntity(String id);
Future<List<T>> getAllEntities();
EntitySyncHandler<T> createSyncHandler(ApiClient apiClient);
}
// 12. Usage Example
void demonstrateRealtimeSync() async {
// Configuration
final syncConfig = SyncConfiguration(
baseUrl: 'https://api.example.com/v1',
apiKey: 'your-api-key',
syncInterval: Duration(minutes: 5),
enableAutoSync: true,
);
// Create sync manager
final syncManager = SyncManager(
syncConfig,
{
SyncUser: UserService(),
// Add more services as needed
},
);
// Initialize
await syncManager.initialize();
// Listen for sync status
syncManager.statusStream.listen((status) {
print('Sync status: $status');
});
// Listen for sync results
syncManager.resultStream.listen((result) {
print('Sync result: ${result.success ? 'Success' : 'Failed'}');
if (result.hasConflicts) {
print('Conflicts: ${result.conflicts.length}');
}
});
// Perform manual sync
final syncResult = await syncManager.syncAll();
print('Manual sync completed: ${syncResult.syncedItems} items synced');
}
export {
SyncService,
SyncEntity,
SyncConfiguration,
SyncStatus,
SyncResult,
SyncConflict,
SyncUser,
ConflictResolution,
SyncManager,
RealtimeSyncManager,
};