Page JSON Overview
Aptean Mesh uses a Server-Driven UI (SDUI) architecture. Every screen in the Android client is defined as a JSON document authored in Business Central (BC). The client does not contain hard-coded layouts -- it receives JSON page definitions at runtime, parses them, and renders the corresponding UI components.
How It Works
- Author -- A developer creates a JSON page definition and stores it in the Mobile Page table (
MobilePage220FDW) in BC, linked to an App Bundle. - Serve -- When the client connects, it calls
GetPageFlowon theBCRPC220FDWAPI page. BC returns every page in the active bundle in a single payload. - Render -- The client deserializes each page into Kotlin data classes (
Page,Header,Footer,Component,Contract) and renders them using Jetpack Compose. - Interact -- User actions (button taps, scans, list selections) trigger actions defined in the page's
contract. These actions call back into BC via JSON-RPC, and the response updates the page data in place.
GetPageFlow ──▶ BC returns all page JSON for the bundleService.RPC(State)JsonObjectHow Pages Are Stored
Each page JSON document lives in the Mobile Page table:
| Field | Description |
|---|---|
App Bundle ID | The bundle this page belongs to |
Page ID | Unique identifier for the page within the bundle (e.g., "HOME") |
Page Json | The full JSON document defining layout and behavior |
Pages are managed through the Mobile Pages page in BC, accessible from the App Bundle card.
How the Client Fetches Pages
The client calls GetPageFlow once on startup. BC returns:
{
"result": {
"bundle": "00000000-0000-0000-0000-000000000001",
"rootPage": "HOME",
"pageFlow": [
{
"pageID": "HOME",
"pageJson": { ... }
},
{
"pageID": "PICK",
"pageJson": { ... }
}
]
}
}
The client caches all page definitions locally. Navigation between pages is instant -- no additional network call is needed unless an action triggers an API request.
Caching and Refresh
- Page definitions are cached on the client after the initial
GetPageFlowcall. They persist until the user switches bundles or the app is restarted. - Page data (the values bound to components) is refreshed whenever an action's API response returns new values. Pull-to-refresh re-executes the page's
initialActionwhenheader.refreshistrue. - Filters are applied client-side against cached data or by re-invoking an API action with filter parameters.
When to Use JSON vs AL Logic
| Scenario | Approach |
|---|---|
| Define which components appear on a screen | JSON page definition |
| Control layout, titles, field labels | JSON page definition |
| Validate input, compute values, query data | AL codeunit (called via actions) |
| Navigate between pages | JSON action with "type": "navigate" |
| Show/hide components dynamically | JSON visibleKey bound to data returned by AL |
| Complex business logic, transactions | AL codeunit behind an API action |
The JSON defines what the user sees. AL code defines what happens when they interact.
Page Structure at a Glance
{
"pageId": "PICK",
"service": "PICKSERVICE",
"header": {
"title": "Pick",
"refresh": true
},
"body": [
{ "type": "stepInput", "dataKey": "scanInput", "action": "onScan" },
{ "type": "repeater", "dataKey": "lines", "key": "lineNo", "template": "CARD" }
],
"contract": {
"initialAction": "onLoad",
"actions": [
{ "id": "onLoad", "type": "api", "method": "Initialize", "params": ["locationFilter"] }
]
}
}
What's Next
- Page Structure -- Top-level
Page,Header,Footer,DrawerItem,Contract, andEventContexttypes - Components -- All UI component types with complete property references
- Actions -- All action types (API, navigate, filter, lookup) with confirmation and input prompt dialogs
- Commands -- Server-sent commands (alert, navigate, refresh, vibrate) returned in RPC responses
- Filters -- Filter definitions, modes, static and dynamic options, segment mapping
- Data Contract -- Contract structure, action references, parameter passing, scan patterns