UI/UX Part 03 — Android Jetpack Compose App Shell, Navigation & Lifecycle Architecture
Reusable P2P Communication Platform
Status: UI/UX architecture specification
UI Series: Part 03
Platform: Android
UI framework: Pure Kotlin + Jetpack Compose
Core runtime: Rust
Primary goal: define the Android application shell, Compose navigation, adaptive phone/tablet/foldable layouts, lifecycle ownership, process-death recovery, Rust/JNI presentation bridge, permissions, system intents, notifications, foreground services, incoming calls, Picture-in-Picture, accessibility, and state-restoration architecture.
1. Purpose
The Android application must feel like a first-class Android application rather than a desktop or cross-platform UI forced onto a phone.
Android imposes platform-specific constraints and expectations around:
Activity lifecycle
process death
background execution
notifications
permissions
predictive back
IME
system share sheet
file picker
camera
microphone
Bluetooth
NFC
foreground services
Picture-in-Picture
window size classes
foldables
accessibility
Jetpack Compose should own these interaction mechanics while the Rust runtime continues to own:
messaging truth
security truth
call state
file-transfer state
presence
search
backup
sync
identity
routing
The governing principle is:
Compose owns Android presentation and platform interaction; Rust owns the product state and business rules.
2. Architectural Position
Rust Core
│
Rust Presentation Service
│
JNI Bridge
│
Kotlin Repository
│
ViewModel
│
StateFlow
│
Jetpack Compose
│
Android Platform APIs
3. Android Module Layout
Recommended:
apps/android/
├── app/
│ └── src/main/
│ ├── kotlin/
│ │ ├── app/
│ │ ├── navigation/
│ │ ├── ui/
│ │ ├── feature/
│ │ ├── platform/
│ │ ├── rust/
│ │ └── service/
│ ├── res/
│ └── AndroidManifest.xml
└── build.gradle.kts
4. Kotlin Package Structure
Suggested:
app/
App.kt
MainActivity.kt
navigation/
AppDestination.kt
AppNavHost.kt
DeepLinkRouter.kt
ui/
theme/
common/
adaptive/
feature/
chats/
calls/
contacts/
files/
search/
devices/
security/
settings/
platform/
permissions/
notifications/
share/
picker/
biometric/
bluetooth/
nfc/
pip/
rust/
RustBridge.kt
RustRepository.kt
RustEventAdapter.kt
service/
CallForegroundService.kt
BackgroundWakeService.kt
5. Single Source of Truth
Compose must never create a second independent domain model.
Correct:
Rust State
↓
Kotlin presentation mapping
↓
Compose
Incorrect:
Rust messages
+
separate Kotlin message DB
+
ViewModel business rules
6. Kotlin ViewModel Role
ViewModel owns:
screen UI state
navigation-adjacent state
temporary input state
permission flow state
loading/error presentation
StateFlow lifecycle
ViewModel must not own:
message delivery semantics
security decisions
call state machine
transfer truth
sync conflict resolution
7. Rust Bridge
The JNI boundary should expose semantic operations.
Examples:
listConversations()
openConversation(id)
sendMessage(command)
observeConversation(id)
startCall(peer)
acceptCall(callId)
search(query)
startTransfer(fileHandle)
8. Avoid Fine-Grained JNI
Do not cross JNI for every:
message field
pixel
audio frame
video frame
database row
Use:
coarse DTOs
batched updates
handles
event streams
9. Event Bridge
Recommended:
Rust bounded event channel
↓
JNI event dispatcher
↓
Kotlin coroutine adapter
↓
SharedFlow / StateFlow
↓
ViewModel
↓
Compose
10. Event Threading Rule
Never update Compose state from arbitrary Rust worker threads directly.
JNI adapter hands events into a controlled coroutine context.
11. Event Coalescing
High-frequency events such as:
transfer progress
call quality
audio levels
typing
presence
must be throttled/coalesced before reaching Compose.
12. Android App Shell
Recommended phone shell:
Scaffold
├── TopAppBar
├── NavHost
├── BottomNavigation
└── SnackbarHost
Primary destinations:
Chats
Calls
Contacts
Secondary destinations:
Files
Search
Devices
Settings
13. Bottom Navigation
Use only a small number of primary destinations.
Do not place:
8–10 tabs
in bottom navigation.
14. Primary Destinations
Recommended:
Chats
Calls
Contacts
Optional fourth:
Files
if product usage justifies it.
15. Secondary Destinations
Access through:
top app bar
overflow
drawer/rail on larger devices
search entry
settings hierarchy
16. Tablet / Foldable Navigation
Use adaptive structure:
NavigationRail
+
List Pane
+
Detail Pane
where width allows.
17. Window Size Classes
Android shell should react to:
Compact
Medium
Expanded
using platform window-size/adaptive APIs.
18. Compact Phone
Typical layout:
single main pane
bottom navigation
full-screen destination changes
19. Medium Width
Potential:
rail
+
single content pane
or:
list/detail
depending feature.
20. Expanded Width
Use:
navigation rail
list pane
detail pane
for conversations, contacts, files, settings.
21. Foldables
Respect:
hinge
fold posture
separating fold
Avoid placing critical controls under hinge.
22. Orientation
Support portrait/landscape gracefully.
Do not lock orientation globally except feature-specific reasons such as some camera modes.
23. Typed Navigation
Define stable destinations.
sealed interface AppDestination
Conceptually:
Chats
Conversation(id)
Calls
Call(id)
Contacts
Contact(id)
Files
File(id)
Search
Devices
Device(id)
Security
Settings(page)
24. No Raw Route Strings in Feature Logic
Route strings are navigation implementation detail.
Feature code should use typed destinations.
25. Navigation Ownership
Compose owns back stack.
Rust may emit semantic navigation requests for:
incoming call
security event
notification click
deep link
but should not own Android back stack.
26. Predictive Back
Integrate with Android predictive back.
Do not implement a custom incompatible gesture stack.
27. Back Behavior
Examples:
conversation → chats
contact detail → contacts
search result → previous screen
modal sheet → dismiss
28. Back During Active Call
Back should:
leave full call UI
without hanging up.
Call remains active in foreground service/PiP if policy allows.
29. Deep Links
Potential:
conversation
call
device
file
security event
backup import
plugin package
All external deep links are untrusted input and validated before navigation.
30. Notification Click
Part 31:
notification intent
→ Kotlin parser
→ typed destination
→ Rust state validation
→ navigation
31. Cold Start Deep Link
If app process not running:
Activity created
↓
Rust runtime starts
↓
pending destination stored
↓
core reaches Ready
↓
navigate
32. Pending Launch Action
Use:
sealed interface PendingLaunchAction
Examples:
OpenConversation
OpenCall
OpenSecurityEvent
ImportBackup
33. MainActivity
Should remain thin.
Responsibilities:
Compose root
platform launch intents
window setup
lifecycle handoff
Not business logic.
34. Application Class
Handles:
global Android initialization
Rust library loading
notification channels
dependency graph
Avoid opening full runtime unnecessarily before needed.
35. Rust Library Load
Load native library once:
System.loadLibrary(...)
JNI ABI handshake validates compatibility.
36. Native ABI Handshake
At startup compare:
Android wrapper expected ABI
Rust native actual ABI
Mismatch:
fatal startup error
with clear recovery/update guidance.
37. Rust Core States
Android observes:
#![allow(unused)] fn main() { pub enum CoreState { Starting, Ready, Degraded, Unavailable, } }
38. Starting UI
Render app shell quickly.
Show:
Starting…
only where content unavailable.
39. Ready
Normal operation.
40. Degraded
Examples:
search rebuilding
relay unavailable
plugin failed
backup stale
Do not block core messaging.
41. Unavailable
Serious error.
Show recovery screen:
Retry
Diagnostics
Restore Backup
42. Process Death
Android can kill process at any time.
The UI architecture must assume:
Activity disappears
ViewModel disappears
JNI process state disappears
unless a separate service/process remains.
43. Durable Truth
On process restart:
Rust reloads durable state
Compose reconstructs from new snapshots.
44. SavedStateHandle
Use only for presentation/navigation state such as:
selected conversation ID
draft ID
scroll anchor
active settings page
temporary filter state
45. Do Not Save Stale Ephemeral State
Do not restore:
typing
presence
live decoder handles
call transport connection
Surface
temporary permissions result
as if still valid.
46. Call Survival
If active call must survive Activity recreation/background:
foreground service
+
Rust call runtime
can outlive UI surface.
47. Activity Recreation
Examples:
rotation
theme change
window resize
should not restart:
call
transfer
sync
48. Compose Recomposition
Must not trigger side effects automatically.
Do not call:
sendMessage()
startCall()
registerPush()
from ordinary recomposition.
Use:
event handlers
LaunchedEffect with stable keys
ViewModel init
carefully.
49. Screen State Model
Each screen gets:
data class ScreenUiState(...)
with:
content
loading
error
local presentation state
50. StateFlow
ViewModel exposes:
StateFlow<ScreenUiState>
Compose uses:
collectAsStateWithLifecycle
or equivalent lifecycle-aware collection.
51. One-Off Effects
Use separate effect stream for:
show snackbar
open picker
request permission
navigate
Do not encode one-time events permanently inside UiState.
52. Effect Stream
SharedFlow<UiEffect>
or channel-based equivalent.
53. Effect Examples
RequestCameraPermission
OpenFilePicker
ShowSnackbar
LaunchShareSheet
OpenSystemSettings
EnterPiP
54. Platform Request Pattern
Rust may return semantic need:
CameraPermissionRequired
ViewModel emits:
RequestCameraPermission
Compose/Activity executes platform request.
55. Permission Architecture
Centralize permissions.
Examples:
Camera
Microphone
Notifications
Bluetooth Scan
Bluetooth Connect
Nearby Wi-Fi
NFC
Storage/Media where required
56. Permission State
enum class PermissionState {
Granted,
Denied,
NeedsRequest,
PermanentlyDenied
}
57. Permission UX
Ask contextually.
Examples:
camera → when starting video
microphone → when starting call/voice note
Bluetooth → when using nearby
notifications → when enabling background alerts
58. Avoid Permission Dump
Do not request every possible permission on first launch.
59. Permanently Denied
Show:
Open Settings
with explanation.
60. Permission Denial Degradation
Examples:
camera denied → audio call still possible
microphone denied → receive-only call or cannot call
notifications denied → app works but background alerts may fail
61. IME / Keyboard
Compose composer must handle:
IME actions
multiline text
hardware keyboard
emoji
RTL
selection
62. Composer Bottom Insets
Use proper:
IME insets
navigation bars
gesture areas
so composer stays visible.
63. Keyboard Send Policy
Examples:
phone Enter → newline
send button → send
hardware Ctrl+Enter → send
Configurable later.
64. Message Timeline
Use:
LazyColumn
stable keys
paging
scroll anchoring
65. Stable Key
Always:
MessageId
66. New Messages
At bottom:
follow
Reading history:
show new message chip
67. Read Detection
Part 30 semantics.
Use:
resumed lifecycle
visible conversation
visible message range
68. Conversation List
Use:
LazyColumn
with:
avatar
title
preview
timestamp
unread
mute
presence optional
69. Pull to Refresh
Do not imply network is source of truth.
If present:
trigger sync/refresh
but local data remains visible.
70. Swipe Actions
Optional Android-native interaction for:
archive
mute
mark read
Provide alternate accessible action.
71. Long Press
Use for message/conversation context actions.
72. Bottom Sheets
Good for:
attachment picker
message actions
audio route
conversation options
73. Dialogs
Use for:
destructive/security decisions
not every action.
74. Snackbars
Use for:
undo
short confirmation
retryable minor failure
75. System File Picker
Use Android document/media picker where possible.
Do not create broad storage access if not needed.
76. File Picker Flow
Compose action
→ ActivityResult launcher
→ URI
→ safe FD/content handle
→ Rust file subsystem
77. No Giant ByteArray
Never convert large selected files into:
ByteArray
for JNI.
Pass:
file descriptor
content URI handle
stream bridge
78. Share Sheet — Outgoing
Rust/Compose can request:
share message text
share file
share invite
Kotlin launches system share sheet.
79. Share Intent — Incoming
Android share intent:
ACTION_SEND / ACTION_SEND_MULTIPLE
maps to typed share request.
80. Incoming Share Flow
external app
→ Android intent
→ Kotlin parse
→ safe handles
→ Compose choose conversation
→ Rust send command
81. External Intent Validation
Treat all incoming intent data as untrusted.
82. Notifications
Part 31 Kotlin layer owns platform notification APIs.
Rust owns:
whether notification should exist
privacy level
dedup
semantic actions
83. Notification Channels
Kotlin creates stable channels:
Messages
Calls
Security
Emergency
Transfers
84. Incoming Call Notification
Flow:
push wake
→ Rust fetch/authenticate offer
→ Kotlin show call notification
Do not ring based solely on push metadata.
85. Accept Call from Notification
PendingIntent
→ Android service/activity
→ Rust CallController.accept(callId)
→ foreground service
→ call UI
86. Decline Call
Can often complete headlessly.
87. Foreground Service
Use for:
active audio/video call
long user-visible transfer where justified
Not to keep idle messenger permanently alive.
88. CallForegroundService
Responsibilities:
service lifecycle
foreground notification
audio focus integration
Rust call-runtime binding
It does not own call state.
89. Picture-in-Picture
Active video call can enter PiP.
Rust call remains active.
Compose Activity may:
enterPiPMode
90. PiP State
UI adapts:
minimal controls
video focus
91. Return from PiP
Rebuild full call UI from Rust CallSnapshot.
92. Screen Rotation During Call
Surface may recreate.
Part 25 handles renderer rebinding.
Call stays active.
93. Camera Surface
Compose owns view placement.
Rust owns media session.
No raw frames in Compose state.
94. Audio Route
Compose may expose chooser:
speaker
earpiece
Bluetooth
wired
Rust/Part 26 remains authoritative.
95. Bluetooth Permissions
Handle Android-version differences in Kotlin platform layer.
Rust receives normalized capability/result.
96. NFC
Android NFC intent/reader mode lives in Kotlin adapter.
Rust receives:
bootstrap payload
for Part 15 validation.
97. Nearby Discovery
Compose renders nearby devices.
Rust/proximity layer owns:
discovery state
identity verification
connection policy
98. QR Scanner
Two options:
Compose camera integration
platform camera/scanner component
Parsed payload goes to Rust bootstrap service.
99. Camera Permission
Requested only when scanner/video feature begins.
100. Biometric Authentication
Use Android BiometricPrompt or appropriate platform API.
Rust requests semantic authentication:
AuthenticateForRecoveryKey
AuthenticateForDeviceRevocation
Kotlin runs prompt and returns result.
101. Sensitive Screens
Examples:
recovery key
device revoke
identity reset
backup key
may require re-authentication.
102. Screenshot Security
For highly sensitive screens, Kotlin may apply secure-window policy if product chooses.
103. Clipboard
Use Android clipboard for explicit copy actions.
Avoid auto-copying sensitive secrets.
104. App Lock
Optional future:
biometric/device credential
before opening sensitive UI.
Rust core may remain running while UI locked.
105. Theme
Support:
System
Light
Dark
Compose implementation may build on Material 3.
106. Dynamic Color
Optional Android feature.
Product may support:
system dynamic color
or fixed brand theme.
107. Design Tokens
Map shared semantic design language into Compose:
color scheme
typography
shape
spacing
motion
108. Font Scaling
Must support system font scale.
Avoid fixed-height components that clip at large text.
109. Touch Targets
Respect minimum touch target size.
110. Accessibility Semantics
Use Compose semantics for:
buttons
message rows
status
selection
call controls
security warnings
111. TalkBack Message Row
Announce coherent unit:
Alice, 10:42 AM, Hello, delivered
112. Live Regions
Use carefully for:
new messages
call connection changes
security alert
Do not overwhelm screen reader.
113. Reduced Motion
Respect Android animation scale/reduced motion where available.
114. High Contrast
Do not rely solely on subtle color differences.
115. RTL
Support:
Arabic
Urdu
Hebrew
layout direction.
Message bubbles and icons should adapt correctly.
116. Localization
All strings in Android resources.
Rust returns:
codes
structured context
timestamps
numbers
Compose localizes.
117. Date/Time Formatting
Android UI uses locale/device settings.
118. Error Architecture
ViewModel receives structured UiError.
Maps to:
snackbar
inline error
dialog
full recovery screen
119. Transient Error
Example:
peer unreachable
show small non-blocking status.
120. Permission Error
Show contextual explanation/action.
121. Security Error
Use strong hierarchy and block risky continuation.
122. Storage Error
Example:
Storage full
show:
Manage Storage
123. Offline
Offline is normal.
Conversation list/history remains available.
Queued messages render normally.
124. Connection Status
Use subtle:
Offline
Reconnecting
only when useful.
125. Empty States
Examples:
No conversations
No calls
No contacts
No files
No search results
with one relevant action.
126. Loading
Prefer local data immediately.
Use skeleton/progress only for data actually unavailable.
127. Pulling Rust Snapshot
Screen open:
ViewModel starts
→ repository requests snapshot
→ state renders
→ event subscription begins
128. Lifecycle-Aware Subscription
Collect event streams only while screen lifecycle requires them.
129. Global Events
Keep app-wide:
active call
security alert
core health
130. Screen-Scoped Events
Examples:
typing for current conversation
file transfer detail
contact presence
131. Avoid Subscription Leaks
ViewModel clears/subscription cancels when feature no longer active.
132. Navigation + ViewModel Scope
Conversation ViewModel scoped to conversation destination.
Chats list ViewModel scoped to chats graph.
133. Shared ViewModel
Use only when state truly spans destinations.
Avoid global mega-ViewModel.
134. Recommended ViewModel Pattern
Per feature:
UiState
UiAction
UiEffect
135. UI Action
Example:
sealed interface ConversationAction {
data class Send(val text: String) : ConversationAction
data class Retry(val messageId: String) : ConversationAction
data object StartAudioCall : ConversationAction
}
136. ViewModel Handling
ViewModel translates action into:
Rust semantic command
137. UI Effect
Examples:
OpenPicker
RequestPermission
Navigate
ShowSnackbar
138. No Domain Decision in Composable
Composable renders and emits actions.
139. Previewability
Compose screens should support previews using fake UI state.
No live Rust runtime required.
140. Testkit
Create Kotlin fake repository matching Rust presentation contract.
141. Compose Preview Models
Provide:
empty
loaded
offline
error
large font
RTL
samples.
142. Android Testing
Required:
ViewModel unit tests
Compose UI tests
navigation tests
permission tests
deep-link tests
process-death tests
PiP tests
foreground service tests
JNI adapter tests
143. Process-Death Test
Scenario:
open conversation
type draft
background
kill process
restore
Expected:
navigation/draft recovery according to policy
core state reloaded
144. Call Recreation Test
During call:
rotate
background
return
Call remains.
145. Notification Cold Start Test
Tap message notification from killed app.
Correct conversation opens after Rust ready.
146. Deep-Link Security Test
Malformed external deep link does not crash or access unauthorized data.
147. Permission Denial Test
Camera denied.
Video call degrades gracefully to audio if allowed.
148. Notification Permission Denied
App works.
Diagnostics explain possible background-call/message limitations.
149. Share Intent Test
Large shared file uses handle/FD, not memory copy.
150. Foldable Test
Hinge does not obscure list/detail content.
151. Large Font Test
At large system font:
no clipped buttons
no hidden security text
152. TalkBack Test
Core flows:
open chat
read message
send message
accept call
verify device
restore backup
153. Performance
Measure:
cold app shell startup
conversation open
LazyColumn scroll
search
call UI updates
154. Compose Recomposition Budget
Avoid broad state objects that recompose whole app for:
typing tick
transfer percentage
Split state into stable feature-level flows.
155. Stable Data Models
Use immutable Kotlin UI models.
156. Mapping Layer
Rust DTO:
ConversationSummaryDto
maps once to Kotlin ConversationUiModel.
Avoid repeated expensive conversions in Composable.
157. Pagination
Rust provides page/cursor.
Compose list requests older/newer pages via ViewModel.
158. Load More
Use scrolling thresholds.
159. Search Debounce
ViewModel debounces user query.
Rust search remains authoritative.
160. File Thumbnail Loading
Use thumbnail handle/URI generated by trusted file subsystem.
Avoid reading full original media in Composable.
161. Image Cache
Android image loader can cache UI thumbnails.
Authoritative file state remains Rust.
162. Video Playback
For shared video attachment, Compose may host native player/view.
Do not route frames through Kotlin state.
163. System Back During Modal
Dismiss:
bottom sheet
dialog
selection mode
before leaving destination.
164. Selection Mode
Android contextual selection for:
messages
files
contacts
165. Haptics
Use sparingly for:
long press
successful pairing
destructive confirmation
166. Vibration Policy
Respect system/user settings.
167. Notification vs In-App Banner
Foreground app:
in-app banner/snackbar
Background:
system notification
Rust notification policy decides semantic intent.
168. Screen-Share Privacy
If active screen share:
notification previews may switch to Generic
according to Part 31 policy.
169. App-Specific Status Bar
Keep status bar/system bars integrated with theme and edge-to-edge layout.
170. Edge-to-Edge
Use Android edge-to-edge correctly with insets.
171. Gesture Navigation
Respect system gesture areas.
Do not place tiny critical controls against gesture edges.
172. Safe Insets
Apply to:
composer
call controls
bottom bar
sheets
173. Android Settings Shell
On phone:
settings list
→ detail screen
On tablet:
settings list pane
+
detail pane
174. Android Security Center
Can adapt similarly:
devices list
security events
recovery
175. Backup UI
Background operations report through Rust.
Compose shows:
progress
destination
verification
errors
176. Restore UI
High-risk operation.
Require:
preview
compatibility check
confirmation
progress
177. Emergency UX
Part 17 data.
Android shell should make SOS accessible without accidental triggering.
178. Lock-Screen Emergency
If product later supports:
notification action
shortcut
widget
must route into verified Rust emergency command.
179. App Widget
Optional future.
Widget may show:
unread count
quick contact
SOS
but no sensitive content by default.
180. Shortcuts
Android app shortcuts can include:
New Message
Recent Contact
Scan QR
181. Shortcut Security
External shortcut intent validated.
182. Compose Navigation Restoration
Navigation library should restore back stack where safe.
Do not restore dead CallId as active call without Rust validation.
183. Stale Destination
If restoring:
ConversationId deleted
navigate to safe fallback.
184. Stale Call
If call ended while Activity gone:
show call history/ended state
not active UI.
185. Stale Device
If device revoked/deleted:
open security center
with explanatory message.
186. Background Wake
Part 31 may start headless Rust work while no Activity exists.
When UI launches later:
durable snapshots already include received messages
No special Compose synchronization needed.
187. Service-to-UI Handoff
Foreground call service can expose active call through Rust global state.
Activity/ViewModel reads fresh snapshot.
188. Single Activity
Recommended:
single-activity Compose app
with feature screens in navigation graph.
Use extra Activity only for exceptional platform requirements.
189. Separate Call Activity?
Usually unnecessary if foreground service + Compose navigation suffice.
Consider only if Android call UX/system integration benefits.
190. Activity Result APIs
Use for:
permissions
file picker
document creation
share result
rather than legacy callbacks.
191. Dependency Boundaries
Feature UI depends on:
UI models
repository interfaces
platform abstractions
not directly on JNI internals.
192. Rust Repository Interface
interface RustRepository {
fun conversations(): Flow<List<ConversationUiModel>>
suspend fun sendMessage(command: SendMessageUiCommand)
suspend fun startCall(peer: PeerUiId)
}
Implementation wraps JNI.
193. Test Repository
Fake implementation for:
Compose previews
unit tests
offline fixtures
194. No JNI in Composables
Hard rule.
Composable should never call native methods directly.
195. No Android Context in Rust Core
Hard rule.
Android context remains in Kotlin platform layer.
196. JNI Error Translation
Rust error code:
PermissionRequired
PeerUnavailable
SecurityMismatch
maps to Kotlin sealed error.
197. Kotlin Error Model
sealed interface UiError
with fields:
severity
retryable
action
198. State Restoration Priority
Restore in order:
core truth
navigation
drafts
scroll
temporary filters
199. Draft Architecture
Draft may live:
ViewModel transient
or later:
Rust durable draft service
For process-death resilience, durable draft service is preferable for important conversations.
200. Scroll Restoration
Save:
anchor MessageId
offset
not absolute list index.
201. Conversation List Scroll
Save lightweight list position locally.
202. Search State
Can restore:
query
filters
selected result
if desired.
203. Sensitive Search
High-security profile may not persist search query.
204. Metrics
UI performance metrics may include:
screen render latency
JNI call duration
event queue lag
recomposition count
No message content.
205. Diagnostics
Developer screen can show:
JNI connected
core state
event backlog
foreground service state
notification permission
push registration
206. Crash Reports
Redact:
message body
contact names
file names
recovery material
where possible.
207. Release Quality Gate
Android UI release should verify:
navigation
process death
permissions
notifications
calls
PiP
share intents
foldables
TalkBack
large font
RTL
dark/light
208. Suggested Android Feature Module Layout
feature/
├── chats/
│ ├── ChatsRoute.kt
│ ├── ChatsScreen.kt
│ ├── ChatsViewModel.kt
│ └── ChatsUiState.kt
├── conversation/
├── calls/
├── contacts/
├── files/
├── search/
├── devices/
├── security/
└── settings/
209. Route vs Screen
Route handles:
ViewModel
navigation
effects
Screen handles:
pure UI
This keeps Composables testable.
210. Example Pattern
ConversationRoute
↓
collect ViewModel state
↓
ConversationScreen(
state,
onAction
)
211. Pure Screen
ConversationScreen does not know:
JNI
Rust
repository
navigation controller
212. Previewability
This allows rich Compose previews for:
normal
offline
error
large font
RTL
group
213. Cross-Platform Semantic Parity
Desktop Dioxus and Android Compose should expose equivalent:
message states
call states
security warnings
transfer states
receipt semantics
even if interaction differs.
214. Android-Specific UX Freedom
Compose may use:
swipe
bottom sheet
system back
PiP
system picker
without forcing those concepts into desktop.
215. Definition of Done
UI/UX Part 03 is complete when:
- Android UI is pure Kotlin + Jetpack Compose
- Rust remains the authoritative product/runtime core
- JNI exposes coarse semantic APIs instead of low-level internals
- ViewModels convert Rust snapshots/events into lifecycle-aware
StateFlow - Composables never call JNI directly
- a typed Compose navigation model exists
- predictive back integrates with Android system behavior
- phone, tablet, and foldable adaptive shells are defined
- Activity/process death is treated as normal
SavedStateHandlestores only presentation/navigation state- stale ephemeral state is never restored as truth
- permissions are contextual and platform-native
- file/share flows pass handles/FDs rather than giant byte arrays
- incoming calls are authenticated before UI rings
- active calls can survive Activity recreation and use foreground-service/PiP integration
- raw media never enters ordinary Compose state
- notification, deep-link, and external intent inputs are validated
- accessibility, TalkBack, font scaling, RTL, reduced motion, touch targets, and edge-to-edge insets are first-class
- screen UI follows
UiState + UiAction + UiEffect - process-death, deep-link, call recreation, permission denial, foldable, TalkBack, and large-font tests are release requirements
216. Final Android Architecture
RUST CORE
│
Presentation Service
│
JNI
│
RustRepository
│
ViewModel
┌──────────┴──────────┐
│ │
StateFlow UiEffect
│ │
▼ ▼
Jetpack Compose Android Platform
│ Permissions
│ Picker
│ Notifications
│ PiP
│ Biometrics
▼
Android UI Shell
Phone:
Bottom Navigation
↓
NavHost
↓
Single-pane screen
Tablet/foldable:
Navigation Rail
↓
List Pane
↓
Detail Pane
217. Final Principle
The Android application should feel like a native Android product while remaining a thin presentation client over the Rust system.
The correct split is:
Rust:
state
business rules
networking
storage
security
calls
sync
search
backup
Kotlin:
lifecycle
permissions
Android services
intents
system UI integration
Jetpack Compose:
presentation
interaction
navigation
adaptive layouts
This gives Android the platform quality users expect without duplicating the communication engine outside Rust.