Skip to content

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 routing
  • Platform.isAndroid / Platform.isIOS for platform-specific behaviours
  • flutter_localizations for 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/await with Future and Stream
  • 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):

ScriptCommandDescription
initmelos run initFirst-time setup — bootstrap + code generation
getmelos run getInstall / update all package dependencies
runmelos run runRun the app on a connected device
build-apkmelos run build-apkBuild release APK
build-ipamelos run build-ipaBuild release IPA (macOS only)
testmelos run testRun all tests across packages
lintmelos run lintRun flutter analyze across packages
genmelos run genRegenerate 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 production

State 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:

InterceptorPurpose
AuthInterceptorAttach Bearer token from Keycloak
LoggingInterceptorLog request/response (debug builds only)
RetryInterceptorRetry on 503 / network timeout (max 3)
TenantInterceptorInject 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.


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

ToolVersionPurpose
build_runner2.xRuns all code generators
freezed2.xImmutable data classes with copyWith, ==, toString
json_serializable6.xJSON serialisation for API models
riverpod_generator2.xGenerate Riverpod providers from @riverpod annotations
openapi-generator-cli7.xGenerate 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-outputs

Shared Submodule Packages

PackagePathPurpose
MobileDesignSystemMobileDesignSystem/UI component library — typography, colours, custom widgets
MobileAPIClientsMobileAPIClients/OpenAPI-generated REST clients for all ERP services
MobileSharedCompMobileSharedComp/Shared utilities — base classes, constants, helpers

Build Toolchain

Android

ToolPurpose
Gradle 8.xAndroid build system
flutter build apkDebug / release APK
flutter build appbundleAAB for Play Store submission
Signing keystoreandroid/keystore.jks — password in CI secrets

iOS

ToolPurpose
Xcode 15+iOS build toolchain (macOS only)
flutter build ipaRelease IPA for App Store / ad-hoc
FastlaneAutomated code signing and upload (planned)
CocoaPods 1.xManages native iOS pod dependencies

CI/CD for Mobile

PlatformTriggerArtefacts
GitHub ActionsPush to main, PRsAPK, IPA, test results
Azure DevOps (planned)Release tagsStore submissions

Key GitHub Actions jobs:

  1. flutter analyze — static analysis
  2. melos run test — unit + widget tests
  3. flutter build apk --release — Android build
  4. flutter build ipa --no-codesign — iOS build (Linux runner, no signing)

Testing

LibraryVersionPurpose
flutter_testBundledUnit and widget tests
mockito5.xMock generation for Dart
integration_testBundledOn-device integration tests
patrol2.xFull-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);
});

Internal Documentation — Microtec Platform Team