🎯 Exemplos recomendados
Balanced sample collections from various categories for you to explore
Exemplos de Banco de Dados Hive Flutter
Exemplos de implementação de banco de dados Hive Flutter com armazenamento offline, modelagem de dados e otimização de performance
💻 Banco de Dados Hive Básico dart
🟢 simple
⭐⭐
Implementação completa do banco de dados Hive Flutter com modelagem de dados, operações CRUD e armazenamento offline
⏱️ 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 };
💻 Otimização de Desempenho Hive dart
🟡 intermediate
⭐⭐⭐⭐
Técnicas avançadas de otimização de Hive com lazy loading, indexação e gerenciamento eficiente de dados
⏱️ 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,
};
💻 Sincronização em Tempo Real Hive dart
🔴 complex
⭐⭐⭐⭐⭐
Sincronização de dados em tempo real com APIs REST, suporte offline e resolução de conflitos
⏱️ 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,
};