Mobile App Architecture
How the Flutter app is organized -small feature modules plugged into a shared shell.
Last updated Thu Jul 16 2026 00:00:00 GMT+0000 (Coordinated Universal Time)
TL;DR -The phone app is built from small, self-contained "modules" (wallet, chat, live room, and so on) that each plug into a shared app shell. Strict layering rules keep features from tangling into each other, and money/auth decisions always live on the backend, never in the app.
Who this is for -Product & business: the first two sections tell you how the app is shaped and why it stays maintainable. Mobile & engineering: everything, including the module contract, dependency rules, and testing conventions.
The Social Live phone app is written in Flutter (one codebase that ships to both iOS and Android). Instead of one giant program, it is assembled from many small pieces called modules -think of them like plug-in cartridges. There's a wallet module, a chat module, a live-room module, and so on. Each one is responsible for a single slice of the product and knows as little as possible about the others.
Why build it this way? Because a big app that everyone edits at once turns into a knot no one can safely change. Small modules with clear boundaries let different people work on different features without stepping on each other, and let you reason about (or delete) one feature without touching the rest.
In plain terms
The whole app is composed from three kinds of code:
- The shell -the frame that holds everything: the bottom tab bar, the startup sequence, and the master list of which modules are switched on.
- Core and shared infrastructure -plumbing that every feature reuses (the thing that talks to the server, the color palette, the reusable buttons and cards). This code knows nothing about any specific feature.
- The feature modules -the actual product: wallet, chat, calls, live rooms, profile, search, and the rest.
The governing rule is simple: the shared plumbing never reaches up into features, and features never secretly wire into each other's guts -they only use each other's official "front door." And any rule that involves money or identity is decided by the backend server, never by the app on the phone. The app asks; the server decides.
How it works
The app (lib/) uses a Prism-style modular composition: small feature modules contribute routes and services to an app shell, with strict layering rules.
Layout
The folder structure mirrors that three-part split -shell (app/), plumbing (core/, shared/), and features (modules/):
lib/
main.dart entry point: system UI config, bootstrap, run app
app/ composition root
app_bootstrap.dart initializes registered modules
app_shell.dart navigation shell (bottom tabs)
module_registry.dart the list of active modules
routes/app_router.dart GoRouter composed from module routes
core/ app-wide infrastructure (no feature knowledge)
config/ AppConfig (API base URL, dart-defines)
network/ ApiClient (REST), error types
utils/ logging, helpers
modules/ feature modules (the real app)
auth/ chat/ calls/ follow/ home/ live_room/ media_webrtc/
notifications/ profile/ safety/ search/ signaling/ vip/ wallet/
shared/ cross-feature UI kit (LiveColors, LiveDecorations, widgets)
The module contract
Every module presents the same tiny, standard "plug shape" so the shell can register it without knowing its internals. Each module exports an AppModule -deliberately tiny:
class AppModule {
const AppModule({
required this.name,
this.routes = const [], // GoRouter routes this module owns
this.initialize, // startup hook
this.dispose, // cleanup hook
});
}
module_registry.dart lists active modules; AppRouter composes their
routes. App-level files never import feature screens directly -a module's
index.dart is its public API. (That index.dart is the "front door"
mentioned above: everything else in the module is private.)
Inside a module
Every module is organized the same way internally, so once you've learned one you can navigate them all. The layers go from UI at the top down to dumb data at the bottom. Modules follow a consistent internal shape (see lib/modules/wallet/ as the reference example):
wallet/
index.dart public exports
wallet_module.dart AppModule definition + routes
application/ controllers (StateNotifier) -logic layer
coin_purchase_controller.dart
data/ repositories -API access + models
wallet_repository.dart
domain/models/ pure model types (where present)
presentation/screens|widgets/ UI only
Rule of thumb: screens render state and dispatch to controllers; controllers orchestrate; repositories talk HTTP; models are dumb. Business invariants that involve money or auth live on the backend -the client never decides them.
Dependency rules
These three rules are what keep the layering from eroding over time:
core/andshared/know nothing about features.- Modules may depend on
core/,shared/, and other modules'index.dartexports (e.g. wallet reads the auth session provider for its bearer token). app/depends only on module registration surfaces.
Typography
One shared font keeps the app looking identical on both platforms. The app font is Inter (weights 400/500/600/700), bundled at
assets/fonts/ and declared in pubspec.yaml; AppTheme
(lib/app/theme.dart) sets it as the global fontFamily, so both
platforms render identical type. Inter is SIL OFL licensed
(assets/fonts/OFL-Inter.txt). Glyphs outside Inter's coverage (emoji,
non-Latin scripts in user content) fall back to the platform font
per-glyph. Live-room styled titles keep their own decorative
TitleFontFamily options; their default option inherits Inter.
Testing
Tests are laid out to mirror the app, and the modules are built to be testable without a real phone or real network. test/ mirrors lib/ (test/modules/wallet/...). Controllers are designed
for testability: external effects (HTTP, store plugins) sit behind injected
seams -e.g. CoinPurchaseStore wraps in_app_purchase so purchase logic
tests run without platform channels. Run everything with flutter test.
Related pages
- State & Routing Conventions -how modules manage state (Riverpod) and screens (GoRouter).
- Networking Layer -the
ApiClientevery repository talks through. - Authentication & Identity -where money/auth decisions actually get made.
- Architecture overview -how the app fits into the wider system.