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
  • SavedStateHandle stores 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.