Formulus Component Reference
Complete technical reference for the Formulus mobile application component.
Overview
Formulus is a React Native mobile application that serves as the client-side component of ODE. It provides offline-first data collection capabilities, custom application hosting, and bidirectional synchronization with the Synkronus server.
Architecture
Technology Stack
- Framework: React Native
- Language: TypeScript
- Database: WatermelonDB (SQLite-based)
- State Management: React Context API
- Navigation: React Navigation
- WebView: React Native WebView (for custom apps)
Component Structure
formulus/
├── src/
│ ├── api/ # Synkronus API client (auto-generated)
│ ├── components/ # React Native UI components
│ ├── contexts/ # React Context providers
│ ├── database/ # WatermelonDB schema and models
│ ├── hooks/ # Custom React hooks
│ ├── navigation/ # Navigation configuration
│ ├── screens/ # Screen components
│ ├── services/ # Business logic services
│ ├── webview/ # WebView integration and bridge
│ └── utils/ # Utility functions
├── android/ # Android native code
├── ios/ # iOS native code
└── assets/ # Static assets
Core Features
Offline-First Data Storage
Formulus uses WatermelonDB for local data storage:
- Observations: All form submissions stored locally
- Attachments: Binary files stored in local filesystem
- App Bundles: Custom application files cached locally
- Sync State: Tracks synchronization status
Custom Application Hosting
Formulus hosts custom web applications in WebViews:
- WebView Integration: React Native WebView component
- JavaScript Bridge: Communication between native and web
- Formulus API: Injected JavaScript interface for custom apps
- Asset Loading: Serves app bundle files from local storage
Synchronization Engine
Two-phase synchronization protocol:
- Observation Sync: JSON metadata synchronization
- Attachment Sync: Binary file synchronization
Form Rendering
Integration with Formplayer for form rendering:
- Formplayer WebView: Renders forms using JSON Forms
- Question Types: Supports various input types
- Validation: Client-side and schema-based validation
- Data Binding: Connects form data to observations
JavaScript Interface
Formulus exposes a JavaScript API to custom applications running in WebViews.
API Access
The API is automatically injected into WebViews. Use the helper function to ensure it's ready:
// Wait for API to be ready
const api = await getFormulus();
// Now use the API
const version = await api.getVersion();
Core Methods
getVersion()
Get the Formulus host version.
const version = await api.getVersion();
// Returns: "1.0.0"
addObservation(formType, initializationData)
Create a new observation by opening a form.
await api.addObservation('survey', {
participantId: '123',
location: 'Field Site A'
});
Parameters:
formType(string): The form type identifierinitializationData(object): Optional data to pre-populate the form
Returns: Promise that resolves when form is opened
editObservation(formType, observationId)
Edit an existing observation.
await api.editObservation('survey', 'obs-123');
Parameters:
formType(string): The form type identifierobservationId(string): The observation ID to edit
Returns: Promise that resolves when form is opened
deleteObservation(formType, observationId)
Delete an observation.
await api.deleteObservation('survey', 'obs-123');
Parameters:
formType(string): The form type identifierobservationId(string): The observation ID to delete
Returns: Promise that resolves when deletion is complete
getObservations(formType, isDraft?, includeDeleted?)
List observations for a form type (no structured filter).
const observations = await api.getObservations('survey', false, false);
getObservationsByQuery(options)
Query observations with a structured filter AST (preferred for custom apps). Declared data.* paths use a local observation index; other paths use json_extract. See Observation queries.
const observations = await api.getObservationsByQuery({
formType: 'hh_person',
includeDeleted: false,
filter: {
op: 'and',
conditions: [
{ field: 'data.village', op: 'eq', value: 'kopria' },
],
},
});
Parameters:
formType(string): Form type identifierincludeDeleted(boolean, optional): Include soft-deleted rowsfilter(ObservationFilter, optional): Structured filter AST
Returns: Promise resolving to an array of observations
sync(options?)
Trigger manual synchronization.
const { version } = await api.sync();
// Optional: include attachments (slower)
await api.sync({ includeAttachments: true });
Returns: Promise<{ version: number }> — the server's data revision after sync completes.
getConnectivityStatus()
Probe whether the configured Synkronus server answers GET /health. Never rejects for offline devices — returns { online: false }.
const status = await api.getConnectivityStatus();
// { online: boolean, serverUrl: string | null, checkedAt: number }
Use for "verify when online, fall back when offline" workflows in custom apps.
getCurrentDataRevisionCount()
Read the device's last-known Synkronus data revision (current_version from the most recent successful sync).
const revision = await api.getCurrentDataRevisionCount(); // number, 0 if never synced
Reflects server-stream alignment only — not unsynced local edits. Poll after sync() or on an interval to detect remote changes from other devices.
persistObservation(input)
Persist an observation without opening Formplayer (headless write). Uses the same path as a Formplayer submit.
const result = await api.persistObservation({
formType: 'survey',
finalData: { name: 'Ada', age: 30 },
observationId: null, // omit or null to create; provide id to update
});
// { observationId, formData }
openFormplayer options
When opening forms programmatically, openFormplayer accepts:
| Option | Description |
|---|---|
subObservationMode | Nested child form for embedded sub-observations |
skipFinalize | Omit Finalize page; Done on last content page submits after child-schema validation; returns formData to parent |
skipDraftSelection | Skip draft picker on new root sessions (custom-app orchestration) |
Form init params reserved keys (not persisted as observation data): defaultData, theme, darkMode, themeColors, context (read-only session context exposed in Formplayer as window.formulusSessionContext), validationMode.
Database Schema
Observations Table
| Column | Type | Description |
|---|---|---|
id | string | Unique observation identifier |
form_type | string | Form type identifier |
data | JSON | Observation data (form responses) |
created_at | timestamp | Creation timestamp |
updated_at | timestamp | Last update timestamp |
deleted | boolean | Soft delete flag |
_status | string | Sync status (created, updated, deleted) |
_changed | string | Changed fields tracking |
Attachments Table
| Column | Type | Description |
|---|---|---|
id | string | Unique attachment identifier |
observation_id | string | Reference to observation |
file_path | string | Local file path |
mime_type | string | File MIME type |
size | number | File size in bytes |
synced | boolean | Sync status |
Synchronization Protocol
Two-Phase Sync
Phase 1: Observation Sync
- Pull: Request changes from server since last sync
- Apply: Apply server changes to local database
- Push: Send local changes to server
- Resolve Conflicts: Handle conflicts if any
Phase 2: Attachment Sync
- Download Manifest: Get list of attachments to download
- Download Files: Download missing attachments
- Upload Files: Upload pending attachments
- Update Status: Mark attachments as synced
Sync State Management
The app maintains sync state:
- Last Sync Timestamp: When last sync completed
- Pending Observations: Count of unsynced observations
- Sync Status: Current sync state (idle, syncing, error)
Configuration
Server Configuration
Configured through Settings screen:
- Server URL: Synkronus server address
- Username: User credentials
- Password: User password
- Auto-login: Enable automatic login
Sync Configuration
- Auto-sync: Enable automatic synchronization
- Sync Interval: How often to check for sync
- WiFi Only: Sync only on WiFi connection
Development
Building from Source
See Formulus Development Guide for complete development setup.
Key Development Commands
# Install dependencies (build @ode/tokens first; see Development Setup)
cd ../packages/tokens && pnpm install && pnpm run build && cd ../formulus
pnpm install
# Start Metro bundler
pnpm start
# Run on Android (vendors Notifee via preandroid)
pnpm run android
# Run on iOS
pnpm run ios
# Generate API client from OpenAPI spec
pnpm run generate:api
# Generate WebView injection script
pnpm run generate
Project Structure
- Android: Native Android code in
android/directory - iOS: Native iOS code in
ios/directory - Source: TypeScript source in
src/directory - Assets: Static assets in
assets/directory
Platform-Specific Features
Android
- File System Access: Direct access to Android storage
- Camera Integration: Native camera API
- GPS: Android location services
- Permissions: Android permission system
iOS
- File System Access: iOS file system access
- Camera Integration: Native camera API
- GPS: iOS Core Location
- Permissions: iOS permission system
Security
Authentication
- JWT Tokens: Stored securely in device keychain/keystore
- Token Refresh: Automatic token refresh before expiration
- Secure Storage: Credentials stored using platform secure storage
Data Protection
- Local Encryption: Sensitive data encrypted at rest
- Secure Communication: HTTPS for all server communication
- Permission Management: Granular permission requests
Performance
Optimization Strategies
- Lazy Loading: Load forms and data on demand
- Caching: Aggressive caching of app bundles and assets
- Background Sync: Sync in background to avoid blocking UI
- Database Indexing: Optimized database queries
Memory Management
- Image Compression: Compress images before storage
- Attachment Cleanup: Remove old attachments after sync
- Cache Limits: Limit cache size to prevent memory issues
Troubleshooting
Common Issues
Sync Failures
- Check network connectivity
- Verify server URL and credentials
- Review sync logs for errors
- Check server status
App Crashes
- Review crash logs
- Check memory usage
- Verify database integrity
- Update to latest version
Performance Issues
- Clear app cache
- Check database size
- Review attachment storage
- Optimize app bundle size
API Reference
For complete API documentation, see:
- Synkronus API Reference - Server API endpoints
- Custom Applications Guide - Building custom apps
- Form Design Guide - Creating forms
Related Documentation
- Formulus Features - User-facing features
- Installing Formulus - Installation guide
- Formulus Development - Development setup
- Synchronization - Sync protocol details