Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs-mstore.faisalaffan.com/llms.txt

Use this file to discover all available pages before exploring further.

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
flutter pub get

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

  1. Create transaction offline:
    final tx = await createTransaction(isar);
    print('Created: ${tx.offlineReference}');
    
  2. Sync to backend:
    await syncToBackend(isar);
    print('Synced!');
    
  3. Check status:
    final synced = await isar.transactionLocals
        .filter()
        .syncStatusEqualTo(SyncStatus.synced)
        .findAll();
    print('Synced: ${synced.length} transactions');
    

📖 Next Steps

Complete Flutter Guide

Full implementation dengan UI, error handling, dan testing

Backend Implementation

Understand backend architecture

API Reference

Complete API documentation

Best Practices

Production-ready tips

🆘 Troubleshooting

Transaction Not Syncing?

final connectivity = await Connectivity().checkConnectivity();
print('Connected: ${connectivity != ConnectivityResult.none}');
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 detik
Timer.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.