Skip to main content
Platform: Flutter 3.x
State Management: Bloc/Cubit
Local Database: Isar
Last Updated: 2025-10-18

๐ŸŽฏ Architecture Overview

MStore Mobile menggunakan Pure Local + SWR (Stale-While-Revalidate) architecture untuk memastikan instant load dan offline-first experience.

๐Ÿ—๏ธ Layer Architecture

1. Presentation Layer (UI)

Widgets

  • StatelessWidget untuk UI statis
  • StatefulWidget untuk UI dengan state
  • Custom widgets untuk reusability

State Management

  • Bloc/Cubit untuk business logic
  • BlocBuilder untuk reactive UI
  • BlocListener untuk side effects
File Structure:
lib/features/
โ”œโ”€โ”€ home/
โ”‚   โ”œโ”€โ”€ presentation/
โ”‚   โ”‚   โ”œโ”€โ”€ pages/
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ home_page.dart
โ”‚   โ”‚   โ””โ”€โ”€ widgets/
โ”‚   โ”‚       โ””โ”€โ”€ home_widget.dart
โ”‚   โ””โ”€โ”€ bloc/
โ”‚       โ”œโ”€โ”€ home_bloc.dart
โ”‚       โ”œโ”€โ”€ home_event.dart
โ”‚       โ””โ”€โ”€ home_state.dart

2. Business Logic Layer (Bloc)

Responsibilities:
  • Handle user interactions
  • Manage UI state
  • Call service layer
  • Transform data for UI
Example:
class ProductBloc extends Bloc<ProductEvent, ProductState> {
  final ProductService _service;
  
  ProductBloc(this._service) : super(ProductInitial()) {
    on<LoadProducts>(_onLoadProducts);
  }
  
  Future<void> _onLoadProducts(
    LoadProducts event,
    Emitter<ProductState> emit,
  ) async {
    emit(ProductLoading());
    
    final result = await _service.getProducts();
    
    result.fold(
      (failure) => emit(ProductError(failure.message)),
      (products) => emit(ProductLoaded(products)),
    );
  }
}

3. Service Layer (Pure Local + SWR)

Responsibilities:
  • Pure local data access (Isar)
  • Background sync orchestration
  • Connectivity management
  • Error handling
Pattern:
@LazySingleton()
class ProductService {
  final ProductRepository _repository;
  final IsarDb _isarDb;
  
  Future<Either<Failure, List<Product>>> getProducts() async {
    // 1) Return Isar INSTANTLY
    final local = await _getProductsLocalOnly();
    
    // 2) Trigger background refresh
    _triggerBackgroundRefresh();
    
    // 3) Return local
    return local;
  }
  
  void _triggerBackgroundRefresh() {
    unawaited(() async {
      if (!await _isOnline()) return;
      
      final apiResult = await _repository.fetchProducts();
      await _upsertToIsar(apiResult);
    }());
  }
}

4. Repository Layer

Responsibilities:
  • API communication (Dio/Retrofit)
  • Data transformation (DTO โ†’ Model)
  • Error handling
  • Request/Response mapping
Example:
@LazySingleton()
class ProductRepository {
  final ProductApi _api;
  
  Future<Either<Failure, List<ProductDto>>> fetchProducts() async {
    try {
      final response = await _api.getProducts();
      return Right(response.data);
    } catch (e) {
      return Left(ServerFailure(message: e.toString()));
    }
  }
}

5. Data Layer (Isar)

Responsibilities:
  • Local data persistence
  • CRUD operations
  • Query & indexing
  • Reactive streams
Entity Example:
@collection
class ProductEntity {
  Id id = Isar.autoIncrement;
  
  @Index()
  late int apiId;
  
  late String name;
  late String sku;
  late int price;
  late int stock;
  
  @Index()
  late String branchCode;
  
  late DateTime createdAt;
  late DateTime updatedAt;
}

๐Ÿ”„ Data Flow

Read Flow (Pure Local)

1

User Action

User membuka screen โ†’ Bloc emit LoadData event
2

Service Call

Bloc call service.getData() โ†’ Service read dari Isar
3

Instant Return

Service return data dari Isar (< 10ms) โ†’ Bloc emit DataLoaded
4

Background Sync

Service trigger background refresh (non-blocking)
5

Silent Update

API fetch โ†’ Update Isar โ†’ Isar stream notify UI โ†’ Auto-update

Write Flow (Offline-First)

1

User Action

User submit form โ†’ Bloc emit SaveData event
2

Write to Isar

Service write to Isar immediately โ†’ Return success
3

Queue Sync

Service queue data untuk background sync
4

Background Sync

When online โ†’ Sync to API โ†’ Update Isar with server response
5

Conflict Resolution

If conflict โ†’ Show conflict resolution UI

๐Ÿ“Š Implementation Status

Data TypeStatusFilePattern
Productsโœ… Donelib/core/product/product_service.dartSWR
Inventoryโœ… Donelib/core/product/product_service.dartSWR
Branchesโœ… Donelib/core/branches/branches_service.dartPure Local + SWR
Transaction Historyโœ… Donelib/core/transaction/transaction_service.dartPure Local + SWR
New Transactionsโœ… Donelib/features/checkout/bloc/checkout_bloc.dartOffline-First
Settingsโœ… Templatelib/core/settings/settings_service.dartPure Local + SWR
Reportsโœ… Templatelib/core/reports/reports_service.dartPure Local + SWR

๐ŸŽจ UI Patterns

No Loading Spinner

JANGAN show loading spinner untuk data refresh!
// โŒ WRONG: Show loading spinner
BlocBuilder<DataBloc, DataState>(
  builder: (context, state) {
    if (state is DataLoading) {
      return CircularProgressIndicator(); // โŒ Bad UX
    }
    return ListView(children: state.data.map(...));
  },
)

// โœ… CORRECT: Always show data
BlocBuilder<DataBloc, DataState>(
  builder: (context, state) {
    return ListView(children: state.data.map(...));
  },
)

Isar Stream for Auto-Update

StreamBuilder<List<ProductEntity>>(
  stream: isarDb.isar.productEntitys.watchLazy(),
  builder: (context, snapshot) {
    context.read<ProductBloc>().add(LoadProducts());
    
    return BlocBuilder<ProductBloc, ProductState>(
      builder: (context, state) {
        if (state is ProductLoaded) {
          return ListView(children: state.products.map(...));
        }
        return EmptyState();
      },
    );
  },
)

Pull-to-Refresh

RefreshIndicator(
  onRefresh: () async {
    context.read<ProductBloc>().add(RefreshProducts(force: true));
    await Future.delayed(Duration(milliseconds: 500));
  },
  child: ListView(...),
)

๐Ÿ”ง Key Technologies

Flutter 3.x

Cross-platform mobile framework

Bloc/Cubit

State management dengan reactive pattern

Isar Database

Fast, local NoSQL database

Dio + Retrofit

HTTP client dengan interceptors

Get_it

Dependency injection container

Freezed

Code generation untuk immutable models

Dartz

Functional programming (Either, Option)

Connectivity Plus

Network connectivity detection

๐Ÿ“ฑ Development Setup

Prerequisites

# Check Flutter version
flutter --version  # Flutter 3.x required

# Check Dart version
dart --version     # Dart 3.x required

Installation

# Clone repository
git clone <repo-url>
cd mstore_mobile

# Install dependencies
flutter pub get

# Generate code (Isar, Freezed, Retrofit)
flutter pub run build_runner build --delete-conflicting-outputs

# Run app
flutter run

Code Generation

# Watch mode (auto-generate on file changes)
flutter pub run build_runner watch --delete-conflicting-outputs

# One-time generation
flutter pub run build_runner build --delete-conflicting-outputs

๐Ÿงช Testing

Unit Tests

# Run all unit tests
flutter test

# Run specific test file
flutter test test/core/product/product_service_test.dart

# Run with coverage
flutter test --coverage

Widget Tests

# Run widget tests
flutter test test/features/home/presentation/pages/home_page_test.dart

Integration Tests

# Run integration tests
flutter test integration_test/

# Run on specific device
flutter test integration_test/ -d <device-id>


๐Ÿ“š Project Structure

lib/
โ”œโ”€โ”€ core/                      # Core functionality
โ”‚   โ”œโ”€โ”€ auth/                 # Authentication
โ”‚   โ”œโ”€โ”€ branches/             # Branches service
โ”‚   โ”œโ”€โ”€ network/              # Dio client, interceptors
โ”‚   โ”œโ”€โ”€ observers/            # Bloc observer
โ”‚   โ”œโ”€โ”€ product/              # Product service
โ”‚   โ”œโ”€โ”€ settings/             # Settings service
โ”‚   โ””โ”€โ”€ transaction/          # Transaction service
โ”œโ”€โ”€ database/                  # Isar database
โ”‚   โ””โ”€โ”€ isar/
โ”‚       โ”œโ”€โ”€ entities/         # Isar entities
โ”‚       โ””โ”€โ”€ isar_db.dart      # Database instance
โ”œโ”€โ”€ di/                        # Dependency injection
โ”‚   โ””โ”€โ”€ injection.dart        # Get_it setup
โ”œโ”€โ”€ features/                  # Feature modules
โ”‚   โ”œโ”€โ”€ auth/                 # Auth feature
โ”‚   โ”œโ”€โ”€ checkout/             # Checkout feature
โ”‚   โ”œโ”€โ”€ home/                 # Home feature
โ”‚   โ””โ”€โ”€ ...
โ”œโ”€โ”€ pkg/                       # Shared packages
โ”‚   โ”œโ”€โ”€ constants/            # App constants
โ”‚   โ”œโ”€โ”€ utils/                # Utilities
โ”‚   โ””โ”€โ”€ widgets/              # Shared widgets
โ””โ”€โ”€ main.dart                  # App entry point

๐ŸŽฏ Best Practices

  • Read dari Isar first (instant)
  • Trigger background sync (non-blocking)
  • Never wait for API response
  • Use Either<Failure, Success> pattern
  • Silent fail untuk background sync
  • Show error hanya untuk user actions
  • Keep Bloc logic simple
  • Use Cubit untuk simple state
  • Avoid nested BlocBuilders
  • Use indexes untuk frequent queries
  • Limit query results
  • Use lazy loading untuk large lists
  • Always run build_runner after model changes
  • Use freezed untuk immutable models
  • Use retrofit untuk type-safe API

๐Ÿš€ Performance Tips

Lazy Loading

Load data incrementally dengan pagination

Image Caching

Use cached_network_image untuk images

Widget Optimization

Use const constructors, avoid rebuilds

Background Isolates

Heavy computation di isolate terpisah

Dokumentasi ini adalah living document. Update sesuai dengan evolusi architecture dan best practices.