Overview
To sync with your backend, implement theSyncApiClient interface. This guide shows how to create a production-ready implementation.
Interface
Copy
abstract class SyncApiClient {
// Wallets
Future<SyncResult<Wallet>> createWallet(Wallet wallet);
Future<SyncResult<Wallet>> updateWallet(Wallet wallet);
Future<void> deleteWallet(String serverId);
// Categories
Future<SyncResult<Category>> createCategory(Category category);
Future<SyncResult<Category>> updateCategory(Category category);
Future<void> deleteCategory(String serverId);
// Transactions
Future<SyncResult<Transaction>> createTransaction(Transaction transaction);
Future<SyncResult<Transaction>> updateTransaction(Transaction transaction);
Future<void> deleteTransaction(String serverId);
}
Basic Implementation
Copy
import 'package:http/http.dart' as http;
import 'dart:convert';
class ZeusApiClient implements SyncApiClient {
final http.Client _client;
final String _baseUrl;
final String _authToken;
ZeusApiClient({
required String baseUrl,
required String authToken,
http.Client? client,
}) : _baseUrl = baseUrl,
_authToken = authToken,
_client = client ?? http.Client();
Map<String, String> get _headers => {
'Content-Type': 'application/json',
'Authorization': 'Bearer $_authToken',
};
@override
Future<SyncResult<Wallet>> createWallet(Wallet wallet) async {
try {
final response = await _client.post(
Uri.parse('$_baseUrl/wallets'),
headers: _headers,
body: jsonEncode({
'name': wallet.name,
'currency': wallet.currency,
'balance': wallet.balance,
}),
);
if (response.statusCode == 201) {
final data = jsonDecode(response.body);
return SyncResult(
success: true,
serverId: data['id'],
data: wallet.copyWith(serverId: data['id']),
);
} else {
return SyncResult(
success: false,
error: 'Failed to create wallet: ${response.body}',
);
}
} catch (e) {
return SyncResult(success: false, error: e.toString());
}
}
// ... implement other methods
}
Configuration
In yourbootstrap.dart:
Copy
Future<void> bootstrap(...) async {
final cache = await CacheManager.initialize();
// Configure with your API client
final apiClient = ZeusApiClient(
baseUrl: 'https://api.zeus.finance',
authToken: await getAuthToken(),
);
cache.configureSync(apiClient);
await cache.initializeSync();
runApp(...);
}
Error Handling
Retry Logic
The sync service automatically retries failed operations. Handle these error types:Copy
Future<SyncResult<Wallet>> createWallet(Wallet wallet) async {
try {
final response = await _client.post(...);
switch (response.statusCode) {
case 201:
// Success
return SyncResult(success: true, ...);
case 401:
// Auth error - don't retry
return SyncResult(
success: false,
error: 'Unauthorized',
);
case 422:
// Validation error - don't retry
return SyncResult(
success: false,
error: 'Validation failed: ${response.body}',
);
default:
// Server error - will retry
throw Exception('Server error: ${response.statusCode}');
}
} on SocketException {
// Network error - will retry
rethrow;
} catch (e) {
return SyncResult(success: false, error: e.toString());
}
}
Testing
Use theStubSyncApiClient for testing:
Copy
void main() {
test('create wallet', () async {
final cache = await CacheManager.initialize();
cache.configureSync(StubSyncApiClient());
final wallet = await cache.createWallet(
name: 'Test Wallet',
currency: 'USD',
);
expect(wallet.syncStatus, equals(SyncStatus.pendingCreate));
// Sync
await cache.sync();
// Check synced
final synced = await cache.wallets.findById(wallet.id);
expect(synced?.syncStatus, equals(SyncStatus.synced));
expect(synced?.serverId, isNotNull);
});
}
Best Practices
- Always return SyncResult - Don’t throw exceptions
- Handle auth errors - Don’t retry 401s
- Validate before sending - Catch 422s early
- Network errors - Let the sync service retry
- Log everything - Helpful for debugging

