Offline-First Quick Start
Get started dengan offline-first implementation dalam 5 menit!
π― What Youβll Build
Setelah mengikuti guide ini, Anda akan memiliki:
β
Flutter app yang bisa create transactions offline
β
Auto-sync ke backend saat online
β
Conflict detection & resolution
β
Real-time sync status UI
π Prerequisites
Backend : MStore Backend v1.0+ sudah running
Flutter : Flutter 3.16+ installed
Database : MySQL 8.0+ dengan schema terbaru
π 5-Minute Setup
Step 1: Add Dependencies (1 min)
# pubspec.yaml
dependencies :
isar : ^3.1.0+1
isar_flutter_libs : ^3.1.0+1
uuid : ^4.2.1
connectivity_plus : ^5.0.2
dev_dependencies :
isar_generator : ^3.1.0+1
build_runner : ^2.4.7
Step 2: Define Isar Model (1 min)
// lib/models/transaction_local.dart
import 'package:isar/isar.dart' ;
part 'transaction_local.g.dart' ;
@collection
class TransactionLocal {
Id id = Isar .autoIncrement;
@Index (unique : true )
late String offlineId;
@Index (unique : true )
late String offlineReference;
late String deviceId;
late double grandTotal;
late String status;
@enumerated
late SyncStatus syncStatus;
int ? serverId;
String ? transactionCode;
}
enum SyncStatus { pending, syncing, synced, failed }
flutter pub run build_runner build
Step 3: Initialize Isar (1 min)
// lib/main.dart
import 'package:isar/isar.dart' ;
import 'package:path_provider/path_provider.dart' ;
Future < void > main () async {
WidgetsFlutterBinding . ensureInitialized ();
final dir = await getApplicationDocumentsDirectory ();
final isar = await Isar . open (
[ TransactionLocalSchema ],
directory : dir.path,
);
runApp ( MyApp (isar : isar));
}
Step 4: Create Transaction Offline (1 min)
// lib/services/transaction_service.dart
import 'package:uuid/uuid.dart' ;
Future < TransactionLocal > createTransaction ( Isar isar) async {
final tx = TransactionLocal ()
..offlineId = const Uuid (). v4 ()
..offlineReference = 'DEVICE001- ${ DateTime . now (). millisecondsSinceEpoch } '
..deviceId = 'DEVICE001'
..grandTotal = 33000
..status = 'paid'
..syncStatus = SyncStatus .pending;
await isar. writeTxn (() async {
await isar.transactionLocals. put (tx);
});
return tx;
}
Step 5: Sync to Backend (1 min)
// lib/services/sync_service.dart
import 'package:dio/dio.dart' ;
Future < void > syncToBackend ( Isar isar) async {
final pending = await isar.transactionLocals
. filter ()
. syncStatusEqualTo ( SyncStatus .pending)
. findAll ();
if (pending.isEmpty) return ;
final dio = Dio ( BaseOptions (baseUrl : 'http://localhost:3002/api/v1' ));
final response = await dio. post ( '/pos/offline/batch-sync' , data : {
'transactions' : pending. map ((tx) => {
'offline_id' : tx.offlineId,
'offline_reference' : tx.offlineReference,
'device_id' : tx.deviceId,
'grand_total' : tx.grandTotal,
'status' : tx.status,
'branch_code' : 'BRN-MST-OSK00001' ,
'payment_method' : 'CASH' ,
'id_user_apps' : 1 ,
'created_at_device' : DateTime . now (). toIso8601String (),
'subtotal' : tx.grandTotal,
'discount_total' : 0 ,
'tax_total' : 0 ,
'payment_status' : 'paid' ,
}). toList (),
'payments' : [],
'voids' : [],
'device_id' : 'DEVICE001' ,
'synced_at' : DateTime . now (). toIso8601String (),
});
// Update sync status
final results = response.data[ 'data' ][ 'results' ] as List ;
await isar. writeTxn (() async {
for ( final result in results) {
final tx = pending. firstWhere ((t) => t.offlineId == result[ 'offline_id' ]);
if (result[ 'success' ]) {
tx.syncStatus = SyncStatus .synced;
tx.serverId = result[ 'server_id' ];
tx.transactionCode = result[ 'transaction_code' ];
} else {
tx.syncStatus = SyncStatus .failed;
}
await isar.transactionLocals. put (tx);
}
});
}
π Done!
Anda sekarang sudah memiliki offline-first app yang working!
Test It
Create transaction offline :
final tx = await createTransaction (isar);
print ( 'Created: ${ tx . offlineReference } ' );
Sync to backend :
await syncToBackend (isar);
print ( 'Synced!' );
Check status :
final synced = await isar.transactionLocals
. filter ()
. syncStatusEqualTo ( SyncStatus .synced)
. findAll ();
print ( 'Synced: ${ synced . length } transactions' );
π Next Steps
π Troubleshooting
Transaction Not Syncing?
Check Internet Connection
final connectivity = await Connectivity (). checkConnectivity ();
print ( 'Connected: ${ connectivity != ConnectivityResult . none } ' );
Check Pending Transactions
final pending = await isar.transactionLocals
. filter ()
. syncStatusEqualTo ( SyncStatus .pending)
. findAll ();
print ( 'Pending: ${ pending . length } ' );
# Check backend logs
tail -f logs/app.log | grep "offline"
-- Check transactions table
SELECT * FROM transactions
WHERE is_offline = 1
ORDER BY created_at DESC
LIMIT 10 ;
-- Check conflicts
SELECT * FROM offline_conflicts
WHERE status = 'pending' ;
π‘ Pro Tips
Batch Size : Sync max 50 transactions per request untuk optimal performance
Auto Sync : Setup Timer untuk auto-sync setiap 30 detikTimer . periodic ( Duration (seconds : 30 ), (_) => syncToBackend (isar));
Conflict Handling : Always show conflicts to user untuk manual resolution
Donβt Block UI : Sync harus non-blocking, jangan tunggu response
π Architecture Overview
βββββββββββββββββββββββββββββββββββββββ
β Flutter App β
β βββββββββββββββββββββββββββββββββ β
β β Create Transaction β β
β β (Offline-First) β β
β βββββββββββββ¬ββββββββββββββββββββ β
β β β
β βββββββββββββΌββββββββββββββββββββ β
β β Save to Isar DB β β
β β (Local Storage) β β
β βββββββββββββ¬ββββββββββββββββββββ β
β β β
β βββββββββββββΌββββββββββββββββββββ β
β β Add to Sync Queue β β
β βββββββββββββ¬ββββββββββββββββββββ β
ββββββββββββββββΌββββββββββββββββββββββββ
β
β Background Sync
β
ββββββββββββββββΌββββββββββββββββββββββββ
β Backend API β
β βββββββββββββββββββββββββββββββββ β
β β POST /batch-sync β β
β βββββββββββββ¬ββββββββββββββββββββ β
β β β
β βββββββββββββΌββββββββββββββββββββ β
β β Check Duplicate β β
β β (offline_reference) β β
β βββββββββββββ¬ββββββββββββββββββββ β
β β β
β βββββββββββββΌββββββββββββββββββββ β
β β Save to MySQL β β
β βββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββ
Congratulations! π Anda sudah berhasil implement offline-first architecture. Check complete documentation untuk production-ready implementation.