Mobile app architecture diagrams: iOS / Android / Flutter / React Native modularization

puml.online

Mobile app architecture diagrams differ from server-side ones — the client has UI layer, ViewModel, Domain, Data, Native Module mixing, offline sync, push pipeline. This is the PlantUML template for iOS / Android / Flutter / React Native architectures, plus offline-first design, push integration, monitoring, performance hot spots.

Four mobile architectures overview

1
2
3
4
5
6
7
8
9
10
Mobile app architecture
├── Native dual-platform
│ ├── iOS (Swift / SwiftUI)
│ └── Android (Kotlin / Compose)
├── Cross-platform
│ ├── Flutter (Dart + Skia)
│ ├── React Native (JS + Bridge)
│ └── Kotlin Multiplatform / Swift on Server
└── Hybrid (Web shell)
└── Capacitor / Cordova

How to choose: depends on team, performance requirements, delivery speed.

Native iOS architecture (Clean Architecture + MVVM)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center

title "iOS App Architecture (Clean + MVVM)"

package "Presentation Layer" {
component "SwiftUI Views" as views
component "ViewModels (ObservableObject)" as vms
component "Coordinators" as coord
}

package "Domain Layer" {
component "Use Cases" as uc
component "Entities" as entities
component "Repository Protocols" as repo_proto
}

package "Data Layer" {
component "Repository Impls" as repo_impl
component "Network (URLSession)" as network
component "Local DB (Core Data / SwiftData)" as localdb
component "Keychain" as kc
component "File Cache" as fc
}

package "Cross-cutting" {
component "Analytics" as analytics
component "Logger (OSLog)" as logger
component "Error Handler" as eh
}

views --> vms : "observes state"
coord --> views : "navigation"
vms --> uc : "call"
uc --> entities : "operate on"
uc --> repo_proto : "depend on protocol"
repo_impl ..|> repo_proto : "conform"
repo_impl --> network : "API calls"
repo_impl --> localdb : "persist"
repo_impl --> kc : "tokens"
repo_impl --> fc : "files"

vms --> analytics : "events"
vms --> logger : "logs"
uc --> eh : "throw"

@enduml

Modularization tips:

  • Presentation only cares about UI
  • Domain is pure Swift, doesn’t depend on UIKit/SwiftUI — testable
  • Data implements protocols defined by Domain
  • Cross-cutting is horizontal concerns (logging / analytics / exceptions)

Native Android architecture (MVVM + Hilt + Room)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center

title "Android App Architecture (MVVM + Hilt)"

package "UI Layer (Compose)" {
component "Composables" as composables
component "ViewModel" as vms
component "Navigation" as nav
}

package "Domain Layer" {
component "UseCase" as uc
component "Domain Model" as dm
}

package "Data Layer" {
component "Repository" as repo
component "Retrofit (API)" as api
component "Room (DB)" as room
component "DataStore (Prefs)" as ds
component "WorkManager (Background)" as wm
}

package "DI (Hilt)" {
component "Modules" as hilt
}

composables --> vms : "collectState"
nav --> composables
vms --> uc
uc --> dm
uc --> repo
repo --> api
repo --> room
repo --> ds
repo --> wm : "sync"

hilt ..> vms : "inject"
hilt ..> repo : "inject"
hilt ..> uc : "inject"

@enduml

Hilt injection runs through all three layers — swap fake implementations in tests.

Flutter cross-platform architecture (BLoC + Repository)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@startuml
skinparam componentStyle rectangle
skinparam defaultTextAlignment center

title "Flutter App Architecture (BLoC)"

package "UI" {
component "Widgets" as w
component "Pages" as pages
}

package "BLoC (State Management)" {
component "BLoC" as bloc
component "Events" as events
component "States" as states
}

package "Domain" {
component "UseCases" as uc
component "Entities" as e
component "Repositories (abstract)" as repo_abs
}

package "Data" {
component "Repository Impl" as repo_impl
component "Dio (HTTP)" as dio
component "Drift (SQLite)" as drift
component "Secure Storage" as ss
component "Flutter Secure Storage" as fss
}

package "Platform Channels" {
component "MethodChannel" as mc
}

pages --> w
w --> bloc : "dispatch event"
bloc --> events : "handle"
bloc --> states : "emit"
pages --> states : "listen"

bloc --> uc
uc --> e
uc --> repo_abs
repo_impl ..|> repo_abs
repo_impl --> dio
repo_impl --> drift
repo_impl --> fss

dio ..> mc : "calls native\nfor custom features"

@enduml

Platform Channels — the bridge Flutter uses to call native code (iOS / Android module).

React Native + Native Modules hybrid architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@startuml
title "React Native + Native Modules Hybrid Architecture"

skinparam componentStyle rectangle

package "JavaScript Thread" {
component "React Components" as react
component "Redux Store" as redux
component "JS Business Logic" as js_logic
component "TS SDK (API client)" as sdk
}

package "Bridge (async)" {
component "JSON Serializer" as json_ser
component "Bridge" as bridge
}

package "Native Modules (iOS)" {
component "Camera Module" as ios_cam
component "Bluetooth Module" as ios_bt
component "Apple Pay" as ios_pay
}

package "Native Modules (Android)" {
component "Camera Module" as android_cam
component "Bluetooth Module" as android_bt
component "Google Pay" as android_pay
}

react --> redux : "useSelector"
react --> js_logic : "callbacks"
js_logic --> sdk
sdk ..> bridge : "HTTP"
js_logic ..> bridge : "Native call"
bridge <--> ios_cam
bridge <--> ios_bt
bridge <--> ios_pay
bridge <--> android_cam
bridge <--> android_bt
bridge <--> android_pay

@enduml

Bridge performance bottleneck — frequent cross-bridge calls lag the UI. Batch, async, use JSI / Turbo Modules to replace bridge when needed.

Offline-first sync architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@startuml
title "Offline-First Sync Architecture"

actor "User" as User
participant "UI" as UI
participant "Local DB\n(SQLite/Room/Drift)" as LDB
participant "Sync Engine" as Sync
queue "Outbox Queue" as Outbox
participant "API" as API
queue "Server Event Stream" as SSE

User -> UI : ① create order (no network)
UI -> LDB : ② write to local DB
UI -> Outbox : ③ enqueue pending sync op
UI --> User : ④ show order immediately (local)

note over Sync : background detects network restored
Sync -> Outbox : ⑤ fetch pending
Sync -> API : ⑥ POST /orders
API --> Sync : ⑦ 200 + server_id
Sync -> LDB : ⑧ update server_id, mark synced

note over Sync : server has updates
SSE -> Sync : ⑨ Server-Sent Event
Sync -> LDB : ⑩ apply remote change (merge)
Sync -> UI : ⑪ notify UI to refresh

@enduml

Core idea: local is truth, server is sync target. App still works when network is down.

Conflict resolution:

  • LWW (Last Write Wins) — simple, but can lose updates
  • CRDT — complex, but conflict-free
  • Operational Transform — what Google Docs uses
  • App-layer merge — by business rules

Push pipeline architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@startuml
title "Push Notification Architecture"

participant "App Server" as Server
participant "FCM (Firebase)" as FCM
participant "APNs (Apple)" as APNs
participant "Device" as Device

== Android ==
Server -> FCM : ① POST /messages {token, data, notification}
FCM -> Device : ② Push via Google Play Services
Device -> Device : ③ show notification / background data

== iOS ==
Server -> APNs : ④ POST /push {device_token, payload}
APNs -> Device : ⑤ Push
Device -> Device : ⑥ show or wake background

note over Device
When app opens:
- App → subscribe topic
- Get token from FCM/APNs on launch
- Send to app server for storage
end note

Device -> Server : ⑦ register device token (HTTP)
Server -> Server : ⑧ store in DB (user_id, token, platform)

@enduml

iOS background push:

  • content-available: 1 wakes the app
  • limit: must be silent push, no alert

Android push channels:

  • FCM not usable in China — use Xiaomi Push / Huawei Push / OPPO Push / vivo Push / Meizu Push
  • multi-channel needs SDK integration, or unified via Getui / Jiguang abstraction

Performance monitoring architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@startuml
title "Mobile Performance Monitoring"

participant "App" as App
participant "Firebase Crashlytics" as Crashlytics
participant "Firebase Performance" as Perf
participant "Sentry" as Sentry
participant "Datadog RUM" as DD

App -> Crashlytics : crash stack
App -> Sentry : exception + breadcrumbs
App -> Perf : launch time, network latency
App -> DD : user action traces

note right of App
Key events:
- launch time (cold start)
- first-paint render (TTI)
- network request duration (p50/p95)
- FPS (scroll jank)
- memory peak
- battery drain
end note

@enduml

Key metrics:

  • Cold launch time < 2 sec
  • TTI (Time to Interactive) < 1.5 sec
  • Network p95 < 500 ms
  • Scroll FPS > 55
  • Crash rate < 0.1%

Modular architecture (native + modular)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@startuml
title "Modular iOS/Android Architecture"

package "App Shell" {
component "App Target" as app
component "App Coordinator" as app_coord
}

package "Feature Modules" {
component "User Module" as user_mod
component "Order Module" as order_mod
component "Payment Module" as pay_mod
component "Profile Module" as prof_mod
}

package "Core Modules" {
component "NetworkKit" as nk
component "DesignKit" as dk
component "AnalyticsKit" as ak
component "StorageKit" as sk
}

package "Shared Models" {
component "Domain Models" as dm
}

app --> app_coord
app_coord --> user_mod
app_coord --> order_mod
app_coord --> pay_mod
app_coord --> prof_mod

user_mod --> nk
user_mod --> dk
user_mod --> ak
user_mod --> sk
user_mod --> dm

order_mod --> nk
order_mod --> dk
order_mod --> ak
order_mod --> dm

pay_mod --> nk
pay_mod --> dk
pay_mod --> ak

note right of pay_mod
Each module:
- independent Pod/Gradle target
- independent tests
- exposes API via protocol
end note

@enduml

Benefits:

  • Independent compile — change user module, don’t rebuild order
  • Independent test — each module has its own unit tests
  • Reuse — Core modules shared across apps

Field foot-guns

  • JS Bridge janks UI — frequent Native Module calls, scroll drops frames. Use JSI / Turbo Modules for sync bridge, or batch calls.
  • Local DB schema migration — user upgrades app, local DB schema changes. Room has Migration class, Drift has MigrationStrategy, must write migration tests.
  • Token expiry concurrent refresh — two APIs return 401 simultaneously, trigger two refreshes, second refresh token becomes invalid. Singleton refresh manager + mutex.
  • Push permission denied — after iOS user denies first time, must guide them to Settings. Don’t pop up again, use UIAlertController to guide.
  • Long list janks — RecyclerView/UITableView doesn’t reuse views. ViewHolder pattern + lazy image loading.
  • Flutter Dart isolate jank — CPU-intensive task (image processing) blocks UI isolate. Use compute() to run on background isolate.
  • Network retry avalanche — backend fails, all clients retry simultaneously. Exponential backoff + jitter.
  • Offline write conflict — two devices edit same order. LWW + conflict log, let user merge manually.

Decision tree

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
What to build?
├─ Extreme perf (TikTok / WeChat grade) → native + per-platform optimization
├─ Fast delivery / cross-platform → Flutter
├─ Web team migrating to app → React Native
├─ Old Native project maintenance → keep native, new modules in KMP/Swift Package
└─ Internal tool / H5 → Capacitor shell

Performance requirement?
├─ 60fps non-negotiable → native
├─ Near-native → Flutter
└─ Smoothness slightly relaxed → RN / Hybrid

Team?
├─ iOS / Android specialists → native
├─ Single team maintaining both ends → Flutter / RN
└─ Web team → RN / Capacitor

Minimum viable mobile architecture: MVC + Repository + local cache. Production-grade: Clean Architecture + DI + modular + offline-first + monitoring.

Remember: mobile architecture diagrams differ from server — UI/UX is core, network is supplementary. First make UX smooth, then add complex architecture. Architecture is for solving problems, not for pretty diagrams.

  • Title: Mobile app architecture diagrams: iOS / Android / Flutter / React Native modularization
  • Author: puml.online
  • Created at : 2026-07-30 17:45:00
  • Updated at : 2026-08-14 21:34:29
  • Link: https://puml.online/blog/plantuml-mobile-app-arch-en/
  • License: This work is licensed under CC BY-NC-SA 4.0.