Appearance
Mobile Technology Reference
Detailed reference for every significant technology used in the Microtec ERP Flutter mobile platform.
Framework
Flutter
Version: 3.x (stable channel)
Role: Cross-platform mobile UI framework for all three ERP mobile applications
Language: Dart 3.x
Targets: Android (API 24+) and iOS (14+)
Key Flutter features used:
- Material 3 theming with custom
ThemeData Navigator 2.0(GoRouter) for deep-linking and declarative routingPlatform.isAndroid/Platform.isIOSfor platform-specific behavioursflutter_localizationsfor Arabic and English locale support- Background isolates for offline data sync (VanSales)
Why Flutter?
A single Dart codebase covering both Android and iOS halves the maintenance burden for three apps. Dart's strong typing and compile-time null safety align with the team's .NET background.
Language
Dart
Version: 3.x (bundled with Flutter 3)
Key features used:
- Sound null safety (all code migrated)
- Records and patterns (Dart 3)
async/awaitwithFutureandStream- Extension methods for domain-specific sugar
- Sealed classes for exhaustive switch (result types)
dart
// Sealed result type — used in all repository return values
sealed class Result<T> {
const Result();
}
final class Success<T> extends Result<T> {
const Success(this.data);
final T data;
}
final class Failure<T> extends Result<T> {
const Failure(this.message);
final String message;
}
// Usage — compiler enforces exhaustiveness
switch (result) {
case Success(:final data) => renderData(data),
case Failure(:final message) => showError(message),
}Monorepo Tooling
Melos
Version: 3.x
Role: Manages multi-package Flutter workspaces — runs commands across all packages, handles dependency bootstrapping
Used in: All three mobile apps (each has its own melos.yaml)
Standard Melos scripts (available in every mobile app):
| Script | Command | Description |
|---|---|---|
init | melos run init | First-time setup — bootstrap + code generation |
get | melos run get | Install / update all package dependencies |
run | melos run run | Run the app on a connected device |
build-apk | melos run build-apk | Build release APK |
build-ipa | melos run build-ipa | Build release IPA (macOS only) |
test | melos run test | Run all tests across packages |
lint | melos run lint | Run flutter analyze across packages |
gen | melos run gen | Regenerate code (Freezed, Riverpod, JSON) |
yaml
# melos.yaml — workspace root
name: bo_mobile_app
packages:
- packages/**
scripts:
get:
run: melos exec -- flutter pub get
test:
run: melos exec --fail-fast -- flutter test
build-apk:
run: flutter build apk --release --flavor productionState Management
Riverpod
Version: 2.x
Role: Compile-safe dependency injection and reactive state management
Code generation: riverpod_generator with @riverpod annotation
Used in: All three mobile apps
dart
// Annotated provider — auto-generates boilerplate
@riverpod
Future<List<Invoice>> invoices(InvoicesRef ref) async {
final repo = ref.watch(invoiceRepositoryProvider);
return repo.getAll();
}
// In widget — watch the provider
class InvoiceListScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final invoicesAsync = ref.watch(invoicesProvider);
return invoicesAsync.when(
data: (invoices) => InvoiceList(invoices: invoices),
loading: () => const CircularProgressIndicator(),
error: (e, _) => ErrorView(message: e.toString()),
);
}
}HTTP Client
Dio
Version: 5.x
Role: HTTP client with interceptors, cancellation tokens, and multipart upload
Used for: All REST API calls to the ERP backend via MobileAPIClients
Interceptors configured:
| Interceptor | Purpose |
|---|---|
AuthInterceptor | Attach Bearer token from Keycloak |
LoggingInterceptor | Log request/response (debug builds only) |
RetryInterceptor | Retry on 503 / network timeout (max 3) |
TenantInterceptor | Inject X-Tenant-Id header |
dart
// Dio client setup via Riverpod provider
@riverpod
Dio dioClient(DioClientRef ref) {
final dio = Dio(BaseOptions(
baseUrl: AppConfig.apiBaseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
));
dio.interceptors.addAll([
AuthInterceptor(ref.watch(authServiceProvider)),
RetryInterceptor(dio: dio, retries: 3),
if (kDebugMode) PrettyDioLogger(),
]);
return dio;
}API Clients
MobileAPIClients (OpenAPI Codegen)
Package path: MobileAPIClients/ (Git submodule)
Generation: OpenAPI Generator → Dart client code from backend Swagger specs
Regeneration command: melos run gen (triggers openapi-generator-cli)
All three mobile apps consume the same generated client package — the backend API contract is the single source of truth.
Navigation
GoRouter
Version: 12.x
Role: Declarative router with deep-link support and nested navigation
Used in: All three mobile apps
dart
// Router configuration
final router = GoRouter(
initialLocation: '/',
redirect: (context, state) {
final isLoggedIn = ref.read(authStateProvider).isAuthenticated;
if (!isLoggedIn) return '/login';
return null;
},
routes: [
GoRoute(path: '/login', builder: (_, __) => const LoginScreen()),
GoRoute(path: '/invoices', builder: (_, __) => const InvoiceListScreen(),
routes: [
GoRoute(path: ':id', builder: (ctx, state) =>
InvoiceDetailScreen(id: state.pathParameters['id']!)),
]),
],
);Local Storage
SQLite (via sqflite)
Version: sqflite 2.x
Role: Local relational storage for offline-capable VanSales app
Used in: VanSalesMobileApp only
shared_preferences
Version: 2.x
Role: Key-value store for user preferences, cached tokens, and app settings
Used in: All three mobile apps
Code Generation
| Tool | Version | Purpose |
|---|---|---|
build_runner | 2.x | Runs all code generators |
freezed | 2.x | Immutable data classes with copyWith, ==, toString |
json_serializable | 6.x | JSON serialisation for API models |
riverpod_generator | 2.x | Generate Riverpod providers from @riverpod annotations |
openapi-generator-cli | 7.x | Generate Dart API clients from OpenAPI specs |
bash
# Regenerate all code in all packages
melos run gen
# Equivalent to:
dart run build_runner build --delete-conflicting-outputsShared Submodule Packages
| Package | Path | Purpose |
|---|---|---|
MobileDesignSystem | MobileDesignSystem/ | UI component library — typography, colours, custom widgets |
MobileAPIClients | MobileAPIClients/ | OpenAPI-generated REST clients for all ERP services |
MobileSharedComp | MobileSharedComp/ | Shared utilities — base classes, constants, helpers |
Build Toolchain
Android
| Tool | Purpose |
|---|---|
| Gradle 8.x | Android build system |
flutter build apk | Debug / release APK |
flutter build appbundle | AAB for Play Store submission |
| Signing keystore | android/keystore.jks — password in CI secrets |
iOS
| Tool | Purpose |
|---|---|
| Xcode 15+ | iOS build toolchain (macOS only) |
flutter build ipa | Release IPA for App Store / ad-hoc |
| Fastlane | Automated code signing and upload (planned) |
| CocoaPods 1.x | Manages native iOS pod dependencies |
CI/CD for Mobile
| Platform | Trigger | Artefacts |
|---|---|---|
| GitHub Actions | Push to main, PRs | APK, IPA, test results |
| Azure DevOps (planned) | Release tags | Store submissions |
Key GitHub Actions jobs:
flutter analyze— static analysismelos run test— unit + widget testsflutter build apk --release— Android buildflutter build ipa --no-codesign— iOS build (Linux runner, no signing)
Testing
| Library | Version | Purpose |
|---|---|---|
flutter_test | Bundled | Unit and widget tests |
mockito | 5.x | Mock generation for Dart |
integration_test | Bundled | On-device integration tests |
patrol | 2.x | Full-app E2E automation (VanSales) |
dart
// Widget test example with Riverpod overrides
testWidgets('InvoiceListScreen shows invoices', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
invoicesProvider.overrideWith((_) async => mockInvoices),
],
child: const MaterialApp(home: InvoiceListScreen()),
),
);
await tester.pumpAndSettle();
expect(find.text('INV-001'), findsOneWidget);
});