Eight modules, one app: how to stop a super-app feeling like eight apps stapled together
We shipped a Flutter super-app with a social feed, live rooms, messaging, a roommate finder, a marketplace and three more modules — on both stores in under five months. The hard part was never the feature count. It was coherence.

Super-apps don't fail on features. They fail on seams.
Every super-app brief arrives as a list: feed, chat, video, marketplace, wallet. The list is the easy part. What sinks these products is that each module gets built like its own app — its own navigation idioms, its own notion of "user", its own loading spinner — and the result feels like a launcher wrapped around six half-products.
We built Wikolo with eight modules on iOS and Android in under five months. Here's what actually mattered.
Decide what is shared before you write a screen
Four things must be owned centrally, on day one, or you will pay for them repeatedly:
| Concern | Why it has to be shared |
|---|---|
| Identity | A user is one person across feed, marketplace and housing. Per-module auth means per-module bugs. |
| Social graph | Follows, blocks and mutes must apply everywhere. A user blocked in chat must vanish from the marketplace. |
| Notifications | One inbox, one badge count, one deep-link resolver. |
| Wallet / balance state | If two modules can move money, exactly one of them owns the balance. |
That last row is worth dwelling on. The moment two modules can debit a balance, you need a single source of truth and idempotent writes, or you will ship double-spends. Make it one service, and make every caller pass an idempotency key.
Blocking is the test case that finds every seam
Pick the cross-cutting rule that touches the most modules and implement it first. For a social product, that's blocking.
When user A blocks user B, B should disappear from A's feed, search, marketplace listings, housing results, chat, and live-room audiences. If your architecture makes that a six-ticket change, the architecture is wrong. It should be one predicate applied at the data layer.
We treat this as an integration test that runs against every list endpoint. Any new module has to pass it before it ships.
A design system, not a style guide
A style guide is a document people ignore under deadline. A design system is code they cannot avoid.
Concretely, that meant shipping the primitives before the modules:
- One button, one card, one avatar, one empty state, one error state
- Spacing and typography as tokens, never literals
- A single list-with-pagination widget every feed-shaped module reuses
The reuse is the point, but the real payoff is consistency under time pressure. When a module is running late, whoever picks it up builds from the same parts. Nobody invents a new card at 11pm.
Feature-first folders, not layer-first
Layer-first structure (/models, /widgets, /services) puts every module's code in every folder. Feature-first keeps a module's screens, state and data access together, with a shared core/ for the things above.
lib/
core/ # identity, notifications, design system, networking
features/
feed/
chat/
housing/
marketplace/
The rule we enforce: features may depend on core, never on each other. When housing needs to message a landlord, it calls a core messaging interface — it does not reach into the chat feature. That single rule is what lets modules ship in parallel without merge pain.
Real-time is a shared layer, not a per-module choice
Chat needs sockets. Live rooms need sockets. Notification badges need sockets. If each module opens its own connection, you get battery drain, race conditions and three different reconnection behaviours.
One connection, one reconnect-with-backoff policy, one place that knows how to resume after the app returns from background. Modules subscribe to topics.
What this bought us
Eight modules — feed with stories and live video, messaging with audio and video calls, a mental-health space, a roommate finder, a marketplace, an academics hub, a wallet, and discovery — on both platforms, in under five months, against a funding runway tied to a demo date.
The modules shipped in parallel because the shared layer landed first. That order is the whole lesson.
If you're scoping something similar, the Wikolo case study covers the module-by-module build in more detail.

