[!NOTE] TL;DR: Managing multiple client variants on a single mobile codebase is a major challenge. For our industrial traceability Flutter app, I implemented a strict Clean Architecture decoupled via Riverpod, an asymmetric persistence system using ObjectBox to guarantee offline operation, and a Git “fork” strategy to strictly isolate proprietary code per client.
In logistics environments, a mobile app cannot simply work: it must be resilient to network drops and highly adaptable to specific floor workflows in each factory. During my apprenticeship, I led the development of a Flutter traceability application deployed across clients with contrasting constraints (an aerospace player operating on the Cloud vs. an industrial player with a custom local infrastructure).
Clean Architecture and Decoupling with Riverpod
For a single core application to adapt to vastly different client IT systems, the software architecture must rely on strict layering. I logically segmented the project:
data/: Entities and repository contracts (interfaces).modules/: Isolated business features.services/¬ifiers/: Cross-cutting application state.
The true decoupling occurs via Riverpod. Repositories are defined by abstract classes and injected into our notifiers. This abstraction allows swapping the underlying implementation dynamically depending on the client build. Thus, the app can switch from a remote REST API implementation to local persistence without rewriting UI logic. This same abstract foundation enabled database migrations (from NoSQL to relational backends) for other client projects.
Securing the Field: Offline-First Strategy with ObjectBox
In a warehouse, losing an in-progress inventory session due to Wi-Fi drops is an unacceptable operational risk. To solve this, I chose an asymmetrical approach blending responsiveness and safety:
- High-Performance Local Cache: Reference data is stored in a local ObjectBox database. The UI consumes this data via reactive streams, providing instant displays with zero network latency.
- Application Safety Net: The active scanning session state (detected RFID tags) is automatically auto-saved as JSON to shared preferences exactly two seconds after detecting the latest item. In case of a crash, operators are prompted with an automatic inventory restoration.
- Green IT & Security: Local databases are encrypted by default to protect hardened handheld terminals if misplaced. Furthermore, reducing network requests significantly extends battery life across 8-hour shifts.
The Multi-Client Challenge: Tradeoffs Between Forks and Flavors
The classic multi-client dilemma in Flutter: flavors or separate repositories?
While flavors maximize code sharing, every build potentially includes all source files. This was unacceptable given the strict confidentiality and isolation requirements of our industrial clients. I made the deliberate choice to adopt a Git fork strategy.
I simultaneously managed five Git repositories. From a shared source repository (upstream), generic bug fixes were pulled down, while each client fork remained an isolated fortress for client-specific business features. It was a pragmatic choice: each fork remains a standard, immediately readable Flutter project (essential for collaborative work and onboarding other developers).
Key Takeaways
Architecture is the Art of Deliberate Compromises
This mission proved that every architectural choice carries trade-offs. Abstraction via Riverpod increases the learning curve for new developers; the fork strategy increases sync overhead beyond a certain number of clients. But by documenting and justifying these decisions, one evolves from a developer reacting to a project to an architect guiding it. To explore how I design these architectures, read my feedback on my AI-assisted workflow.