Skip to content

Mobile Delivery — The Super App

The mobile experience for tenant workers (managers, supervisors, and workers) is delivered through a single Flutter application called The Super App.

Flutter lets you write one codebase that runs on both iOS and Android. More importantly for our architecture, Flutter provides a mechanism called federated plugins (and more broadly, a module system) that lets you split a single app into several independent feature modules. Each module has its own:

  • Screens (pages)
  • State management / controllers
  • Dependencies and packages
  • Business logic

These modules are combined into one deliverable app at build time, but their code stays isolated — a change in the Safety module cannot accidentally break the Productivity module.

The Super App is composed of two layers: the Main Module and the Feature Modules.

graph TB
subgraph SuperApp["The Super App (Flutter)"]
direction TB
subgraph Main["Main Module — App Shell"]
Auth["Authentication\n(login, token refresh, logout)"]
Config["Remote Configuration\n(feature flags, environment)"]
Prefs["User Preferences\n(language, theme, notifications)"]
Company["Company Selection\n& License Validation"]
end
subgraph Features["Feature Modules (one per domain)"]
WF["Workforce\nModule"]
Prod["Productivity\nModule"]
Safety["Safety & Compliance\nModule"]
Monitor["Worker Monitoring\nModule"]
Asset["Asset Management\nModule"]
end
Main -->|"provides auth context,\ncompany context, config"| Features
end
User(["Worker / Supervisor / Manager"]) --> SuperApp

The Main Module is the entry point of the app. Think of it as the frame that holds everything together. It is responsible for:

  • Authentication — handling login flows, token storage, automatic token refresh, and logout.
  • Configuration — loading remote feature flags and environment settings at startup.
  • Preferences — persisting user preferences like language and notification settings.
  • Company selection & licensing — allowing a user who belongs to multiple companies to switch context, and enforcing which features are available based on the company’s active license.

Once the main module has resolved all of this, it makes the context available to all feature modules. Feature modules can read the current user, current company, and active license, but they do not manage these themselves.

Each feature module owns the end-to-end flow for its domain. For example, the Safety & Compliance module handles everything from listing safety forms to submitting them and viewing results — it does not delegate to other modules.

The key benefit of this structure is blast radius reduction: a bug introduced in one module, or a dependency upgrade inside one module, cannot crash or break another module. This means:

  • Teams can work on different modules in parallel without merge conflicts.
  • You can ship a fix to the Productivity module without touching the Safety module code.
  • Each module can pin its own dependency versions independently.

All modules are bundled together when the app is submitted to the App Store / Google Play. There is one app, one version number, one release. The module boundaries are a code organisation and team isolation concern — not a runtime isolation concern (unlike the web strategy).

sequenceDiagram
participant Dev as Developer
participant CI as CI/CD Pipeline
participant Store as App Store / Google Play
participant Device as Worker's Phone
Dev->>CI: Push changes to any module
CI->>CI: Build all modules together
CI->>Store: Submit single .ipa / .aab
Store->>Store: Review (1–3 days)
Store->>Device: App update available
Device->>Device: User updates app