AquaX Mobile Architecture
Owner: Mobile & Architecture
Reviewers: Backend, Product Design, QA, Security, DevOps
Status: Draft - Current-Code Baseline
Version: 0.1
Last Updated: 2026-09-17
Review Cycle: Per mobile navigation, auth, API, storage, notification or release change
1. Purpose And Scope
This document describes the current AquaX mobile architecture as implemented in mobile/. It focuses on runtime composition, role-based navigation, state management, API integration, authentication/session storage, notifications, feature boundaries, design-system usage, build/release behavior and known mobile risks.
This document covers the Expo React Native mobile app only. Web frontend architecture is documented in docs/04-architecture/FE_Architecture.md.
Sources reviewed:
mobile/package.jsonmobile/app.jsonmobile/babel.config.jsmobile/tsconfig.jsonmobile/src/main.tsxmobile/src/App.tsxmobile/src/core/components/**mobile/src/core/config/**mobile/src/core/navigation/**mobile/src/core/store/**mobile/src/core/hooks/**mobile/src/core/services/**mobile/src/core/types/**mobile/src/features/**mobile/scripts/deploy_android_playstore.shdocs/current/architecture/mobile-architecture.mddocs/07_Design System.md
2. High-Level Mobile Context
AquaX mobile is an Expo React Native app for role-based farm operations. It supports public auth flows and authenticated role surfaces for ADMIN, MANAGER, TECHNICIAN and OWNER.
Current stack:
| Area | Current implementation |
|---|---|
| Runtime | Expo SDK 56 |
| React | React 19 |
| Native runtime | React Native 0.85 |
| Language | TypeScript |
| Navigation | React Navigation native stack + bottom tabs |
| Server state | TanStack Query |
| Auth/client state | Zustand |
| HTTP client | Axios |
| Forms | React Hook Form |
| Validation | Zod |
| UI library | Ant Design React Native |
| Icons | lucide-react-native and Ant Design React Native icons |
| Storage | Expo SecureStore and AsyncStorage |
| Notifications | Expo Notifications, REST/SSE notification sync |
| Toast | react-native-toast-message |
Primary runtime flow:
AppProviders
-> RootNavigator
-> AuthNavigator or MainNavigator
-> role navigator
-> feature screen
-> domain hook
-> API service
-> apiClient
-> backend /api
3. Source Layout
Current source layout:
mobile/src/
core/
components/
config/
constants/
hooks/
libs/
navigation/
services/
store/
theme/
types/
features/
admin/
auth/
manager/
owner/
shared/
technician/
App.tsx
main.tsx
Boundaries:
| Folder | Responsibility |
|---|---|
core/components/ |
App-wide reusable primitives and providers |
core/config/ |
API client, env, i18n and query client |
core/constants/ |
API URLs, query keys, app constants and ticket configs |
core/hooks/ |
Domain hooks and cross-cutting hooks |
core/navigation/ |
Auth, root, role and shared tab navigators |
core/services/api/ |
Domain API service wrappers |
core/services/storage/ |
SecureStore and AsyncStorage abstractions |
core/services/toast/ |
Toast abstraction |
core/store/ |
Zustand auth store |
core/theme/ |
Theme provider, colors, spacing, layout and typography tokens |
core/types/ |
API, auth, navigation and domain DTO types |
features/ |
Role and feature screens/components |
Important alignment note:
- Older docs described a flatter
navigation/andscreens/root layout. Current code usescore/plusfeatures/. - Current code uses role
OWNER, notVIEWER, for owner/viewer mobile flows.
4. App Bootstrap
mobile/src/main.tsx registers the Expo root component.
registerRootComponent(App)
mobile/src/App.tsx composes:
AppProviders
-> RootNavigator
AppProviders composes:
GestureHandlerRootView
-> QueryClientProvider
-> SafeAreaProvider
-> ThemeProvider
-> Ant Design Provider
-> app children
-> AppToast
It also loads Ant Design icon fonts and shows a custom branded launch loading screen while fonts are loading.
5. Build And Runtime Configuration
5.1 Expo App Config
mobile/app.json confirms:
| Item | Current value |
|---|---|
| App name | AquaX |
| Slug | aquax-mobile |
| Version | 1.0.0 |
| Scheme | aquax |
| Orientation | portrait |
| iOS bundle id | com.aquax.app |
| Android package | com.aquax.app |
| UI style | light |
| Expo updates | disabled |
| Plugins | expo-splash-screen, expo-notifications, expo-secure-store |
| React compiler experiment | enabled |
5.2 Environment
core/config/env.ts uses:
| Variable | Purpose | Default |
|---|---|---|
EXPO_PUBLIC_API_URL |
Backend API base URL | http://localhost:3001/api |
EXPO_PUBLIC_APP_ENV |
App environment label | development |
Android emulator behavior:
- When running on Android,
localhostis rewritten to10.0.2.2.
5.3 Module Resolution
babel.config.js and tsconfig.json define:
@/* -> ./src/*
TypeScript is strict and uses bundler module resolution.
6. Navigation Architecture
6.1 Root Navigation
RootNavigator owns auth restoration and top-level routing.
Flow:
isRestoring
-> show ActivityIndicator
else accessToken exists
-> MainNavigator
else
-> AuthNavigator
Session restoration:
- Calls
authAPI.restoreSession(). restoreSession()calls refresh flow using SecureStore refresh token.- On success, Zustand
setSession()stores access token/user and refresh token. - On failure, Zustand
clearSession()clears session.
Deep link reset password:
| Prefix | Route |
|---|---|
mobile:// |
reset-password |
https://app.aquax.app |
reset-password |
6.2 Auth Navigator
Auth stack screens:
| Screen | Purpose |
|---|---|
Login |
Login |
Register |
Registration screen |
ForgotPassword |
Password reset request |
CheckEmail |
Reset request confirmation |
ResetLinkExpired |
Expired reset link surface |
ResetPassword |
Reset password with token |
6.3 Main Navigator
MainNavigator selects a role navigator from the authenticated user's role:
| Role | Navigator |
|---|---|
ADMIN |
AdminNavigator |
MANAGER |
ManagerNavigator |
TECHNICIAN |
TechnicianNavigator |
OWNER |
OwnerNavigator |
| missing/unknown | defaults to OwnerNavigator |
MainNavigator also starts:
- push notification token registration;
- notification realtime stream.
6.4 Shared Role Tab Navigator
RoleTabNavigator provides:
- bottom tab navigation;
- shared app header;
- role-specific tabs;
- hidden screens for stack-like flows;
- safe-area-aware tab bar sizing;
- notification bell for OWNER and TECHNICIAN.
7. Role Navigation Map
7.1 ADMIN
Visible tabs:
| Tab | Screen |
|---|---|
Users |
AdminUserScreen |
Farms |
AdminFarmsNavigator |
Ponds |
AdminPondScreen |
Profile |
ProfileScreen |
Hidden screens:
AdminAddUserAdminDetailUserAdminEditUser
7.2 MANAGER
Visible tabs:
| Tab | Screen |
|---|---|
Home |
HomeScreen |
Profile |
ProfileScreen |
7.3 OWNER
Visible tabs:
| Tab | Screen |
|---|---|
Home |
ViewerHomeScreen |
Farms |
FarmsNavigator |
AISupport |
AiSupportScreen |
Ticket |
ActivityLogScreen |
Profile |
ProfileScreen |
Hidden screens:
FarmingLogNotificationsPondDetailCreateProblemProblemDetailTodoForm
7.4 TECHNICIAN
Visible tabs:
| Tab | Screen |
|---|---|
TechHome |
TechHomeScreen |
TechIncidentList |
TechIncidentListScreen |
Profile |
ProfileScreen |
Hidden screens:
TechIncidentDetailTechIncidentAcceptNotificationsPondDetail
8. State Management Architecture
8.1 Zustand Auth Store
core/store/auth.store.ts owns authenticated session state.
State:
| Field | Purpose |
|---|---|
accessToken |
In-memory access token for API requests |
expiresAt |
Access token expiry timestamp |
isRestoring |
Root restoration/loading state |
user |
Current authenticated user |
Actions:
| Action | Behavior |
|---|---|
setSession |
Stores refresh token in SecureStore if present and updates access token/user |
clearSession |
Removes refresh token and clears auth state |
finishRestoring |
Marks restore flow as complete |
8.2 Token Storage
Refresh token storage:
expo-secure-store key: auth.refreshToken
General storage abstraction:
AsyncStorage JSON get/set/remove
Security rule:
- Refresh token must stay in SecureStore.
- Access token is kept in Zustand state and attached by the API client.
8.3 TanStack Query
queryClient defaults:
| Setting | Value |
|---|---|
Query staleTime |
60 seconds |
Query gcTime |
30 minutes |
Query refetchOnReconnect |
true |
Query refetchOnWindowFocus |
false |
| Query retry | no retry for HTTP status < 500; otherwise up to 2 failures |
| Mutation retry | false |
Domain hooks use Query Client invalidation after mutations.
9. API Integration Architecture
9.1 API Client
core/config/api-client.ts creates the Axios client.
Current config:
| Concern | Current code |
|---|---|
| Base URL | env.apiUrl |
| Timeout | 15 seconds |
| Content type | application/json |
| Mobile client header | x-client-type: mobile |
| Credentials | withCredentials: false |
| Request auth | Adds Authorization: Bearer <accessToken> from Zustand |
| FormData | Removes Content-Type to let native boundary be set |
9.2 Refresh Flow
When a request receives 401:
- Login and refresh requests are excluded from retry.
- If refresh token is missing, session is cleared.
- If a refresh is already in progress, requests wait in a queue.
- Refresh uses SecureStore refresh token.
- Refresh request sends:
- body
{ refreshToken }; x-client-type: mobile;x-refresh-token: <refreshToken>.
- body
- On success,
setSession()updates auth store and refresh token. - Queued requests replay with the new access token.
- On failure, queue rejects and session is cleared.
Mobile refresh flow is intentionally different from web because native clients do not rely on browser httpOnly cookie behavior.
9.3 API Services
Current domain API services:
| Service | Area |
|---|---|
authAPI |
Login, restore/refresh, logout, profile, password flows |
userAPI |
Admin user list/detail/create/update/status |
farmAPI |
Admin farm list, owner farm/home/detail, technician home, create pond |
pondAPI |
Pond detail, dashboard, tabs, timeseries, update/delete |
cropAPI |
Crop lifecycle and crop catalog |
deviceAPI |
IoT output control, output mode, auto-rules |
feedingAPI |
Feed types and feeding records |
farmingLogAPI |
Manual water, mineral, siphon, combined logs and attachments |
ticketAPI |
Ticket list/detail/lifecycle/comment/attachments |
notificationAPI |
Notifications, stats, read state and device token registration |
activityLogAPI |
Activity log infinite list |
todoAPI |
Owner todo home and task CRUD |
10. Domain Hooks Architecture
Hooks live in core/hooks and are the preferred screen-facing API.
Current hook areas:
| Hook | Responsibility |
|---|---|
useAuth |
Login, forgot/reset password, profile, change password |
useUser |
Admin user queries and mutations |
useFarm |
Admin farms, owner farms/home/detail, technician home, create pond |
usePond |
Pond detail tabs, device control, crop, feeding, auto-rules |
useFarmingLog |
Farming logs and attachment mutations |
useTicket |
Ticket list/detail/lifecycle/comment/attachments |
useTodoTask |
Todo home and task CRUD |
useNotification |
Notification list/stats/read/delete/push/SSE |
useActivityLog |
Infinite activity logs |
useDebouncedValue |
Debounced UI inputs |
useKeyboardHeight |
Keyboard-aware form layout |
Architecture rule:
- Screens should call hooks, not raw API services, when a hook exists.
- Hooks should own cache keys, invalidation and toast side effects.
11. Feature Modules
Current feature grouping:
| Feature root | Current scope |
|---|---|
features/auth |
Login, registration and password recovery |
features/admin/users |
Admin user list/detail/add/edit |
features/admin/farms |
Admin farm list and farm navigator |
features/admin/ponds |
Admin pond screen |
features/manager/home |
Manager home |
features/owner/home |
Owner home and todo tasks |
features/owner/farms |
Owner farm list/detail, pond detail, crop and feeding flows |
features/owner/farming-logs |
Farming log navigator/list/form/type selector |
features/owner/activity-log |
Owner activity log |
features/owner/ai-support |
AI support screen |
features/owner/problems |
Problem/ticket creation and detail |
features/technician/home |
Technician home |
features/technician/incidents |
Technician incident list/detail/accept |
features/technician/notifications |
Technician notifications screen |
features/technician/profile |
Technician profile screen |
features/shared |
Alerts, notifications, profile, ticket and shared user surfaces |
12. Notifications Architecture
Mobile notifications have three layers:
- REST list/stats/read/delete APIs.
- SSE realtime stream.
- Expo push token registration.
Realtime stream:
- Starts when authenticated.
- Uses
fetchwithAccept: text/event-stream. - Sends
Authorization: Bearer <accessToken>. - Sends
x-client-type: mobile. - Listens for event type
notification.created. - Invalidates notification queries on matching event.
- Reconnect delay is 5 seconds.
- Stops when
AppStateis not active and reconnects when active.
Push registration:
- Ensures Android notification channel.
- Requests notification permission.
- Uses Expo project ID from app config.
- Registers Expo push token with backend.
- Failures are swallowed because REST/SSE remains source of truth.
13. Theme And Design System Integration
Theme provider:
ThemeProvider -> useTheme()
Theme tokens:
| Token group | File |
|---|---|
| Colors | core/theme/colors.ts |
| Spacing | core/theme/spacing.ts |
| Typography | core/theme/typography.ts |
| Layout | core/theme/layout.ts |
Current reusable components:
| Component | Purpose |
|---|---|
Screen |
Safe-area-aware scroll/static screen wrapper |
AppButton |
Themed Ant Design button wrapper |
AppToast |
Custom toast renderer |
BrandLogo |
Brand logo wrapper |
AppProviders |
App provider composition and launch loading |
Current design-system gap:
- Core theme primary color is blue, while many production screens use AquaX teal hardcoded values such as
#00796B,#006B5F,#005D4E. - Future cleanup should align core theme tokens with the AquaX teal palette before expanding token usage.
14. File Upload And Native Capabilities
Confirmed native capabilities:
| Capability | Usage |
|---|---|
| Image/file picking | expo-image-picker dependency; ticket/farming-log attachment APIs accept file URI/mime/name |
| Secure token storage | expo-secure-store |
| Push notifications | expo-notifications |
| Safe areas | react-native-safe-area-context |
| Gesture handling | react-native-gesture-handler |
| Reanimated | react-native-reanimated plugin configured |
FormData behavior:
- API client removes
Content-Typewhen request body isFormData. - Ticket and farming-log attachment services append native file objects.
15. Build, CI And Release Considerations
Package scripts:
| Script | Purpose |
|---|---|
npm start |
Start Expo dev server |
npm run android |
Run Android native app |
npm run ios |
Run iOS native app |
npm run web |
Run Expo web |
npm run lint |
Expo lint |
npm run typecheck |
tsc --noEmit |
npm run format |
Prettier write |
npm run format:check |
Prettier check |
Android release script:
mobile/scripts/deploy_android_playstore.sh
Confirmed release flow:
- Load
mobile/secrets/build_config.envif present. - Detect Android keystore from
mobile/secrets. - Require Google Play service account JSON.
- Run
npm install. - Run
npx expo prebuild --platform android --clean. - Set Android versionCode.
- Run Gradle
bundleRelease. - Upload AAB with Fastlane
supply.
Supported tracks:
internalalphabetaproduction
Release gaps:
- iOS TestFlight/App Store release process is not documented in repo.
- Mobile CI/CD is not wired into GitHub Actions.
- Android release script mutates
app.jsonversionCode during release; this should be handled carefully in release branches.
16. Performance And UX Considerations
Current confirmed patterns:
- TanStack Query caches server state.
- Infinite query is used for activity logs.
- Pond detail loads multiple tab queries in parallel.
- Device tab and water timeseries poll while viewing pond detail.
- Notification stream suspends when app is not active.
- Keyboard-aware helper exists for long forms.
- Safe-area-aware layout is used in
Screenand tab/header UI.
Current gaps:
- No mobile performance budget is documented.
- No crash/error monitoring provider is confirmed.
- Large screens contain substantial local styles and may need decomposition as features grow.
17. Security And Privacy Considerations
Confirmed controls:
- Refresh token stored in Expo SecureStore.
- API client sends
x-client-type: mobile. - Access token is attached only through Axios interceptor.
- Refresh queue prevents parallel refresh storms.
- Session restore clears local state when refresh fails.
Risks:
| ID | Risk | Impact | Required follow-up |
|---|---|---|---|
| MOB-SEC-01 | Access token is held in memory and refreshed by native client | Expected native behavior, but requires backend refresh hardening | Keep refresh token rotation and revoke behavior tested |
| MOB-SEC-02 | Push token registration swallows failures | Push delivery issues may be silent | Add observability or user-visible diagnostics if push becomes critical |
| MOB-SEC-03 | Mobile release secrets live under mobile/secrets convention |
Risk if secrets are committed accidentally | Ensure secrets remain gitignored and rotate exposed keys |
| MOB-SEC-04 | AI support screen exists but production AI backend is not implemented | User expectations may exceed actual backend | Keep AI feature status aligned with PRD/SRS |
18. Architecture Risks And Action Items
| ID | Risk | Impact | Required follow-up |
|---|---|---|---|
| MOB-ARCH-01 | Older docs referenced VIEWER; current code uses OWNER |
Role docs can drift from app behavior | Keep permission matrix and mobile docs on OWNER |
| MOB-ARCH-02 | Mobile CI/CD is not configured in GitHub Actions | Release quality depends on manual checks | Add lint/typecheck/build workflow |
| MOB-ARCH-03 | iOS release process is TBD | App Store release cannot be repeated reliably | Document TestFlight/App Store runbook |
| MOB-ARCH-04 | Theme tokens are not fully aligned with production teal UI | UI inconsistency and harder maintenance | Align core/theme with AquaX design tokens |
| MOB-ARCH-05 | Some API services normalize inconsistent backend response shapes | Contract drift can leak into screens | Align backend API responses and update 08_API Specification.md |
| MOB-ARCH-06 | Role navigator defaults unknown role to OWNER | Misconfigured user role could show owner UI | Consider explicit unsupported-role screen |
| MOB-ARCH-07 | No crash/error monitoring provider confirmed | Production issues may be hard to diagnose | Add crash reporting and API error monitoring |
19. Change Rules
When changing mobile architecture:
- Add route types to
core/types/navigation.types.ts. - Register screens in the correct role navigator.
- Add endpoint constants in
core/constants/apiURL.constants.ts. - Add or update API service wrapper in
core/services/api. - Add or update domain hook in
core/hooks. - Keep refresh token handling inside SecureStore-backed auth store.
- Keep server-state cache/invalidation in hooks.
- Update
docs/08_API Specification.mdfor contract changes. - Update this file when navigation, auth, storage, notification or release architecture changes.
- Run
npm run lintandnpm run typecheckfor mobile code changes.
20. Traceability
| Source | Architecture evidence |
|---|---|
mobile/package.json |
Stack, dependencies and scripts |
mobile/app.json |
Expo app identity, plugins and release metadata |
mobile/babel.config.js |
Alias and Reanimated plugin |
mobile/tsconfig.json |
Strict TypeScript and path alias |
mobile/src/main.tsx |
Expo root registration |
mobile/src/App.tsx |
Provider and root navigator composition |
mobile/src/core/components/AppProviders.tsx |
Provider tree and launch loading |
mobile/src/core/navigation/** |
Auth, root, role and tab navigation |
mobile/src/core/config/api-client.ts |
Axios client and refresh queue |
mobile/src/core/config/env.ts |
API URL and app env behavior |
mobile/src/core/config/query-client.ts |
TanStack Query defaults |
mobile/src/core/store/auth.store.ts |
Zustand auth state and SecureStore refresh token flow |
mobile/src/core/services/api/** |
API service wrappers |
mobile/src/core/hooks/** |
Domain hooks and cache invalidation |
mobile/src/core/theme/** |
Theme provider and tokens |
mobile/src/core/types/** |
Auth, navigation, API and domain types |
mobile/src/features/** |
Role/feature screens and components |
mobile/scripts/deploy_android_playstore.sh |
Android Play Store release flow |