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)
User Action
User membuka screen โ Bloc emit LoadData event
Service Call
Bloc call service.getData() โ Service read dari Isar
Instant Return
Service return data dari Isar (< 10ms) โ Bloc emit DataLoaded
Background Sync
Service trigger background refresh (non-blocking)
Silent Update
API fetch โ Update Isar โ Isar stream notify UI โ Auto-update
Write Flow (Offline-First)
User Action
User submit form โ Bloc emit SaveData event
Write to Isar
Service write to Isar immediately โ Return success
Queue Sync
Service queue data untuk background sync
Background Sync
When online โ Sync to API โ Update Isar with server response
Conflict Resolution
If conflict โ Show conflict resolution UI
๐ Implementation Status
Data Type Status File Pattern Products โ
Done lib/core/product/product_service.dartSWR Inventory โ
Done lib/core/product/product_service.dartSWR Branches โ
Done lib/core/branches/branches_service.dartPure Local + SWR Transaction History โ
Done lib/core/transaction/transaction_service.dartPure Local + SWR New Transactions โ
Done lib/features/checkout/bloc/checkout_bloc.dartOffline-First Settings โ
Template lib/core/settings/settings_service.dartPure Local + SWR Reports โ
Template lib/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-ur l >
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
# 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-i d >
๐ 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
1. Always Use Pure Local Pattern
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
3. Efficient State Management
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
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.