Skip to main content

Navigation

Aptean Mesh supports multi-page navigation within a mobile app bundle. This tutorial shows how to navigate from a list page to a detail page, pass context data between pages, and handle back navigation.

Prerequisites

Complete the Using a Repeater tutorial first.

Overview

Navigation in Aptean Mesh works in two ways:

  1. JSON-driven navigation -- Define a navigate action in the page contract that pushes a new page onto the navigation stack.
  2. AL-driven navigation -- Use MobileCommandBuilder220FDW to navigate programmatically from server-side logic, passing data along with the navigation command.

Both approaches push a destination page onto a stack. The mobile app automatically provides a back button to return to the previous page.

User taps list item
NavigateAction fires (contract.actions)
|v
Navigation Stack
[Previous Page]
|v
[Detail Page]
initialAction fires
|v
Service.RPC(State)
State.GetInput(key) reads params
User taps back button
|v
Stack pops → previous page restored (no RPC call)

Step 1: Navigate from a List Page (JSON)

In your page contract, define a navigate action with a destination matching the target page's ID. Use data to pass values from the current page context:

List Page -- ItemList.json

{
"header": {
"title": "Items",
"refresh": true
},
"body": [
{
"type": "repeater",
"dataKey": "items",
"key": "No",
"template": "CARD",
"onTapAction": "onItemTap",
"emptyMessage": "No items found",
"fields": [
{ "key": "No", "label": "Item No." },
{ "key": "Description", "label": "Description" }
]
}
],
"contract": {
"initialAction": "onLoadItems",
"actions": [
{
"id": "onLoadItems",
"type": "api",
"method": "GetItems",
"params": []
},
{
"id": "onItemTap",
"type": "navigate",
"destination": "ITEM-DETAIL",
"data": { "ItemNo": "{No}" }
}
]
}
}

The {No} syntax interpolates the No field from the tapped repeater row. When the user taps an item with No = "1000", the navigation passes ItemNo = "1000" to the detail page.

Detail Page -- ItemDetail.json

{
"header": {
"title": "Item Detail",
"titleKey": "item.Description",
"refresh": true
},
"body": [
{
"type": "card",
"props": {
"title": "Item Details",
"fields": [
{ "label": "No.", "valueKey": "item.No" },
{ "label": "Description", "valueKey": "item.Description" },
{ "label": "Category", "valueKey": "item.Category" },
{ "label": "Unit Price", "valueKey": "item.UnitPrice" }
]
}
}
],
"contract": {
"initialAction": "onLoadItem",
"actions": [
{
"id": "onLoadItem",
"type": "api",
"method": "GetItem",
"params": ["ItemNo"]
}
]
}
}

The params: ["ItemNo"] sends the navigation parameter to your AL service as an input value.

Step 2: Read Context on the Target Page

On the detail page, the navigation data is available through State.GetInput(). The parameter name (ItemNo) matches the key you used in the data object:

AL Service

codeunit 50100 MyItemService implements IBCRPC220FDW
{
procedure RPC(var State: Codeunit MobileRPCState220FDW) ResponseJson: JsonObject
begin
case State.Method() of
'GetItems':
exit(GetItems(State));
'GetItem':
exit(GetItem(State));
end;

ResponseJson := State.Serialize();
end;

local procedure GetItems(var State: Codeunit MobileRPCState220FDW) Result: JsonObject
var
Item: Record Item;
Items: JsonArray;
ItemObj: JsonObject;
begin
if Item.FindSet() then
repeat
Clear(ItemObj);
ItemObj.Add('No', Item."No.");
ItemObj.Add('Description', Item.Description);
Items.Add(ItemObj);
until Item.Next() = 0;

Result.Add('items', Items);
end;

local procedure GetItem(var State: Codeunit MobileRPCState220FDW) Result: JsonObject
var
Item: Record Item;
ItemNo: Code[20];
Document: JsonObject;
begin
// Read the parameter passed via navigation data
Evaluate(ItemNo, State.GetInput('ItemNo', true));
Item.Get(ItemNo);

Document.Add('No', Item."No.");
Document.Add('Description', Item.Description);
Document.Add('Category', Item."Item Category Code");
Document.Add('UnitPrice', Format(Item."Unit Price"));

Result.Add('item', Document);
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::MobileRPCRegistry220FDW,
OnResolveService, '', false, false)]
local procedure HandleMyService(
CodeunitId: Integer;
var Service: Interface IBCRPC220FDW;
var IsServiceResolved: Boolean)
begin
if IsServiceResolved then
exit;
if CodeunitId = Codeunit::MyItemService then begin
Service := this;
IsServiceResolved := true;
end;
end;
}

The end-to-end flow when a user taps item 1000:

  1. onItemTap action fires with data { "ItemNo": "1000" }
  2. App navigates to the ITEM-DETAIL page
  3. ITEM-DETAIL runs initialAction "onLoadItem" with params ["ItemNo"]
  4. RPC call to GetItem -- State.GetInput('ItemNo') returns "1000"
  5. Service returns item data; page renders the card

Step 3: Navigate Programmatically from AL

When navigation depends on server-side logic (such as access checks or data lookups), use the MobileCommandBuilder220FDW from within your RPC handler. This is the pattern used throughout the base app:

local procedure SelectDocument(var State: Codeunit MobileRPCState220FDW) Result: JsonObject
var
NavigateData: JsonObject;
DocumentNo: Code[20];
begin
Evaluate(DocumentNo, State.GetInput('documentNo', true));

// Build the navigation data payload
NavigateData.Add('documentNo', DocumentNo);
NavigateData.Add('documentType', 'Receipt');

// Issue a navigate command -- the app pushes the target page
State.Command().Navigate('DETAIL', NavigateData);
Result := State.Serialize();
end;

The State.Command() accessor returns the embedded MobileCommandBuilder220FDW. Calling Navigate() buffers a navigation command that is included in the serialized response. The mobile app processes the command and pushes the destination page.

You can combine navigation with an alert that shows before the page transition:

State.Command().Navigate('DETAIL', NavigateData, 'Document Loaded', 'Opening receipt R-001');

Step 4: Custom Routing via MobileNavigator220FDW

The MobileNavigator220FDW codeunit provides higher-level navigation helpers and a customization point through the OnResolveTarget event. Subscribe to this event to override the default page code or title for a given navigation target:

[EventSubscriber(ObjectType::Codeunit, Codeunit::MobileNavigator220FDW,
OnResolveTarget, '', false, false)]
local procedure CustomizeNavTarget(
Target: Enum MobilePage220FDW;
DocumentNo: Code[20];
var PageCode: Code[20];
var Title: Text;
var TypeText: Text;
var Handled: Boolean)
begin
// Route transfer orders to a custom page
if Target = MobilePage220FDW::Detail then
if IsTransferOrder(DocumentNo) then begin
PageCode := 'TRANSFER-DETAIL';
Title := StrSubstNo('Transfer %1', DocumentNo);
TypeText := 'Transfer';
Handled := true;
end;
end;

The navigator also provides convenience methods for common navigation patterns:

var
Navigator: Codeunit MobileNavigator220FDW;
begin
// Navigate to a detail page with standard title resolution
exit(Navigator.NavigateToDetail(State, MobilePage220FDW::Detail, DocumentNo));

// Navigate to a workflow page with document and line context
exit(Navigator.NavigateToWorkflow(State, MobilePage220FDW::Receipt, DocumentNo, LineNo));

// Navigate back to the home page
exit(Navigator.NavigateHome(State));

// Navigate back to home with a success alert
exit(Navigator.NavBackToHomeWithAlert(State, 'Posted', 'Receipt posted successfully.'));
end;
tip

See MobileNavigator220FDW API reference for all navigation methods and the MobilePage220FDW enum values.

Step 5: Back Navigation

Automatic Back Navigation

The mobile app automatically adds a back button in the header of every page except the root. Users can tap it or use the Android back gesture to return to the previous page.

Programmatic Back Navigation

From AL, issue a NavBack command to pop the current page from the navigation stack:

local procedure DeleteAndGoBack(var State: Codeunit MobileRPCState220FDW) Result: JsonObject
begin
// ... perform delete logic ...

// Navigate back to the previous page
State.Command().NavBack();
Result := State.Serialize();
end;

Show an alert before navigating back:

State.Command().NavBack('Deleted', 'The item has been removed.');

Navigate back to a specific page deeper in the stack (skipping intermediate pages):

// Pop back to the HOME page, skipping any intermediate screens
State.Command().NavBackTo('HOME');

// Pop back with an alert
State.Command().NavBackTo('HOME', 'Receipt Posted', 'Receipt R-001 posted successfully.');

The base app uses NavBackTo after posting a document -- it skips the detail page and returns directly to the home screen with a confirmation message.

Summary

ApproachWhen to use
JSON navigate actionSimple page transitions where no server logic is needed
State.Command().Navigate()Navigation that depends on server-side checks or data
MobileNavigator220FDW helpersStandard patterns like detail pages and workflows
OnResolveTarget eventCustom routing based on document type or other criteria
NavBack() / NavBackTo()Returning to a previous page after an operation

What's Next

  • Workflows -- Build guided step-by-step flows for warehouse operations