Skip to main content

Overview

To sync with your backend, implement the SyncApiClient interface. This guide shows how to create a production-ready implementation.

Interface

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

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 your bootstrap.dart:
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:
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 the StubSyncApiClient for testing:
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

  1. Always return SyncResult - Don’t throw exceptions
  2. Handle auth errors - Don’t retry 401s
  3. Validate before sending - Catch 422s early
  4. Network errors - Let the sync service retry
  5. Log everything - Helpful for debugging