Mobile App Architecture skill for Codex

Structure sized to the app, enforced by the build. Codex sizes the architecture to the team and app before adding layers, then applies one-way screen state, repositories over a local source of truth, typed navigation routes and constructor injection using each platform's default stack. For larger codebases it plans an api/impl feature-module split one feature at a time, measures build impact, enforces dependency rules in CI and records the decision as an ADR.

  • Mobile & Desktop
  • $mobile-app-architecture
  • 158 lines
  • 2 files
  • ~1,850 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- mobile-app-architecture

Installs to ~/.agents/skills/mobile-app-architecture/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $mobile-app-architecture, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs and reviews mobile app architecture across iOS, Android, Flutter and React Native - UI, presentation, domain and data layers, unidirectional state, repositories and single sources of truth, navigation as typed routes, dependency injection and composition roots, feature modules with enforced dependency rules, and testing seams. Produces architecture decision records and module graphs. Use when starting a new app, when a codebase has god view models or tangled modules, when splitting into feature modules, or when choosing between MVVM, MVI or TCA-style patterns.

Use it when

  • Starting a new app and choosing layers, state and navigation patterns
  • View models or controllers have grown into untestable god objects
  • Build times or merge conflicts push toward feature modules
  • Writing an architecture decision record for a mobile codebase

Use something else when

How to check the result

View models and repositories are unit-testable on the host with fakes, the CI dependency rule check passes, and measured incremental build times improve after the module split.

The mistake it prevents

Adding use cases, mappers and DI frameworks to a small app before any pain demands them, tripling the code per feature with no isolation gained.

~/.agents/skills/mobile-app-architecture/SKILL.md

--- name: mobile-app-architecture description: Designs and reviews mobile app architecture across iOS, Android, Flutter and React Native - UI, presentation, domain and data layers, unidirectional… license: MIT ---

Done well means: each screen’s state comes from one place and flows one way, business rules are testable without a device, features can be built and tested in isolation, dependencies point inward, and the structure is no heavier than the app’s size and team count justify.

Before changing anything

  1. Read AGENTS.md and any existing ADRs or architecture docs.
  2. Map what exists: modules or packages, the largest files (find . -name "*.swift" -o -name "*.kt" | xargs wc -l | sort -n | tail -20), how screens get data, how navigation happens, and where DI is configured.
  3. Ask about scale: number of engineers and teams, number of features, build times, platforms, and whether code is shared (KMP, Flutter, React Native).
  4. Identify the pain driving the change (slow builds, merge conflicts, untestable logic, crashes from state bugs). Architecture work without a named pain is a rewrite risk.

Size the architecture to the app

App / teamStructure
1-3 engineers, under ~20 screensSingle module/target, folders by feature, MVVM, manual DI
4-15 engineers, growing feature setFeature modules + core modules, DI container, typed navigation
Multiple teams, 100k+ lines, slow buildsapi/impl module split, ownership per module, build-time dependency rules

Do not introduce a domain layer, use-case classes or a DI framework before something needs them. Add a use case when logic is shared by several view models or is complex enough to deserve its own tests.

Layers

UI (views, composables, widgets)       renders state, sends user intents
Presentation (ViewModel / store)       holds screen state, calls domain/data, no platform UI types
Domain (optional: use cases, models)   pure business rules, no framework imports
Data (repositories, data sources)      single source of truth: local DB, network, caches

Rules:

  • Dependencies point downward only. Data never imports presentation; domain imports nothing platform-specific.
  • Repositories expose streams (Flow, AsyncSequence, Stream) from the local source of truth; network results write into it rather than going straight to the UI.
  • One immutable state value per screen; the UI renders it and sends events back.
  • Platform services (location, camera, notifications) sit behind interfaces in data or a core module, so presentation tests use fakes.

Platform defaults

ConcernAndroidiOSFlutterReact Native
State holderViewModel + StateFlow@Observable model (or TCA reducer)Riverpod Notifier or BlocZustand or Redux Toolkit store, TanStack Query for server state
DIHilt (Koin in KMP)Initializer injection from a composition root; Environment for viewsRiverpod providersModule-level factories or context
Local truthRoom / DataStoreSwiftData / Core Data / GRDBDrift / IsarSQLite / MMKV / WatermelonDB
NavigationNavigation Compose with type-safe routesNavigationStack with a path of route valuesgo_routerReact Navigation / Expo Router

Use the platform default unless the team has a strong reason; non-default stacks raise onboarding cost for every hire.

State and events

data class CartState(val items: List<Line> = emptyList(), val loading: Boolean = false, val error: String? = null)
sealed interface CartEvent { data class Remove(val id: String) : CartEvent; data object Checkout : CartEvent }

class CartViewModel(private val repo: CartRepository) : ViewModel() {
    val state: StateFlow<CartState> = repo.observeCart()
        .map { CartState(items = it) }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), CartState(loading = true))

    fun onEvent(e: CartEvent) = viewModelScope.launch {
        when (e) { is CartEvent.Remove -> repo.remove(e.id); CartEvent.Checkout -> repo.checkout() }
    }
}

The same shape works in Swift (@Observable final class CartModel with a state property and func send(_ event:)) and Dart. One-off effects (navigate, show toast) are modelled as state that the UI consumes and clears, or as a dedicated effect stream; never as LiveData that replays on rotation.

  • Routes are typed values (Route.product(id:), @Serializable data class Product(val id: String)), not string concatenation spread across screens.
  • Features expose an entry route; they do not import each other’s screens. The app module (or a navigation module) wires routes to destinations.
  • Deep links, notifications and widgets all parse into the same route type; see mobile-deep-links.
  • Navigation decisions live in the presentation layer as events; the UI layer performs them.

Feature modules

:app                      composition root, navigation graph, DI wiring
:feature:cart:api         route + public interfaces (tiny)
:feature:cart:impl        screens, view models, feature-private data
:core:data                repositories shared by features
:core:network, :core:database, :core:designsystem, :core:testing
  • feature:*:impl may depend on core:* and other features’ :api only. Never impl-to-impl.
  • iOS equivalent: Swift packages or local SPM targets per feature with the same rule.
  • Enforce rules in the build: Gradle module graph checks (a custom task or a plugin such as a dependency-analysis tool) and SPM target dependencies, plus a CI check, so rules do not decay into wiki pages.
  • Measure before and after: clean and incremental build times, and the number of modules rebuilt for a one-line change in a feature.

Migration plan for an existing tangle

  • Write the target diagram and an ADR; agree on it with the team
  • Extract core:designsystem and core:network first (least coupled)
  • Move one feature into api/impl, fix the leaks it reveals, measure build impact
  • Add the dependency rule check in CI once the first feature passes it
  • Repeat feature by feature; never freeze feature work for a big-bang rewrite

Testing seams

LayerTest typeRuns on
Domain and repositoriesUnit tests with fakesJVM / host machine, fast
View models / storesUnit tests feeding events, asserting stateJVM / host
UIScreenshot tests and a few UI tests per featureEmulator/simulator or Paparazzi/Roborazzi-style JVM rendering
End to endA handful of critical journeysDevice farm or CI emulators

A design where view models need a device or network to test is the clearest smell.

Gotchas

  • “Clean architecture” with mappers between identical models in every layer adds code without isolation; map only where shapes truly differ (network DTO to domain model).
  • Singletons reached through static accessors defeat DI; inject through constructors.
  • Base classes for all view models accumulate unrelated behaviour; prefer composition.
  • Shared mutable caches between features become hidden coupling; go through repositories.

Output

Write decisions as an ADR in the repo’s ADR folder (or docs/adr/):

# ADR-012: Feature modules with api/impl split
Status: proposed | accepted
Context: <pain, measured: build time, merge conflicts, test gaps>
Decision: <structure, layers, DI, navigation, dependency rules>
Consequences: <costs, migration order, how rules are enforced in CI>
Alternatives considered: <and why rejected>

Include a module graph (Mermaid graph TD) of the target state.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Mobile App Architecture"
  short_description: "Structure sized to the app, enforced by the build"
  default_prompt: "$mobile-app-architecture Propose an architecture for our new iOS and Android app with a team of six"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
New

Introducing Codex Skills Pro

79 professional skills that take Codex through complete engagements, each with its own scripts, references and a report that verifies the result.

All-AccessOne-time payment, no subscription
$49
Get All-AccessOr browse individual skills from $2.99 →

Secure checkout by Creem