DEV Community

GeorgeGcs
GeorgeGcs

Posted on • Edited on

Experience with Official Recommended Component-Level Routing Navigation

Experience with Official Recommended Component-Level Routing Navigation

HarmonyOS Development Capabilities ##HarmonyOS SDK Application Services ##HarmonyOS Financial Applications (Financial Management#

In recent weeks, during a major update to our application’s architecture, I’ve delved deeply into the nuances of routing in HarmonyOS. Over the past two years of developing for OpenHarmony, my team and I relied heavily on router routing and window controls for module navigation. While functional, this approach led to tightly coupled page logic—each navigation action required extensive code to manage page transitions, and adjusting even small parts of the interface often triggered cascading changes across multiple files. The workflow felt cumbersome, especially in complex financial applications with dozens of interconnected modules (e.g., asset overview, transaction history, and fund details).

The Shift to Navigation: A Paradigm Change

As HarmonyOS APIs have evolved, Huawei now officially recommends replacing router with Navigation for routing needs. The core distinction lies in their positioning: whereas router operates at the page level (treating entire pages as navigation units), Navigation is component-level, enabling granular control over specific UI components within a page. This shift has revolutionized how we structure navigation logic, particularly in financial apps where precision and flexibility are critical.

Key Advantages of Navigation

  1. Unmatched Flexibility

    Unlike router, which requires full page reloads for navigation, Navigation allows routing control over partial page components. For example, in a financial dashboard displaying a user’s investment portfolio, we can navigate between "Stock Details" and "Bond Analysis" components without refreshing the entire dashboard. This reduces unnecessary data reloading, cuts down on battery usage, and creates a smoother user experience—essential for apps where users frequently switch between related financial data.

  2. Enhanced Navigation Management

    Traditional router only supports page replacement, with limited control over transition animations. Navigation, however, offers robust stack management, including support for removing specific entries from the navigation stack. This is invaluable in multi-step financial workflows (e.g., loan application forms with 5+ steps). If a user corrects a mistake in step 3, Navigation lets us remove steps 4 and 5 from the stack, ensuring the back button behaves intuitively—something router struggles to handle cleanly. Additionally, it natively supports customizable transition animations (e.g., sliding, fading), which we’ve used to differentiate between "forward" (e.g., proceeding to payment) and "backward" (e.g., revisiting personal info) actions in financial flows.

  3. Adaptive Design for Diverse Devices

    Navigation includes an auto-scaling container that seamlessly adapts to split-screen layouts and foldable devices—a game-changer for modern financial apps. For instance, on a foldable phone unfolded, we can display the "Asset Summary" on the left and "Recent Transactions" on the right, linked via Navigation; when folded, it automatically condenses into a single-screen stack. This adaptability ensures users get optimal information density regardless of device form factor, a critical feature for power users managing investments on the go.

How to Implement Navigation

Navigation operates as a three-part system: the main container (Navigation), subpage containers (NavDestination), and a navigation stack manager (NavPathStack). Here’s a step-by-step guide tailored to financial application scenarios:

(1) Create the Main Page

The main page initializes the navigation stack and serves as the root container for all navigation actions.

@Entry
@Component
struct MainPage {
  @State message: string = 'Financial Dashboard';
  // Initialize the navigation stack to manage subpage history
  pageStack: NavPathStack = new NavPathStack()

  build() {
    // Bind the stack to Navigation to enable state synchronization
    Navigation(this.pageStack) {
      // Main page layout (e.g., financial app homepage)
      Row() {
        Column({ space: 20 }) {
          Text(this.message)
            .fontSize(30)
            .fontWeight(FontWeight.Bold)
            .textColor('#1A1A1A')

          // Button to navigate to "Investment Details" subpage
          Button('View Investment Details')
            .fontSize(16)
            .padding({ left: 20, right: 20, top: 10, bottom: 10 })
            .backgroundColor('#0066FF')
            .borderRadius(8)
            .onClick(() => {
              // Push the subpage to the stack
              this.pageStack.pushDestination({
                name: "InvestmentPage", // Matches the name in the routing table
                // Optional: Pass initial data (e.g., user ID for financial data fetching)
                data: { userId: '12345' }
              }, false); // Disable animation for sensitive financial pages
            })
        }
        .width('100%')
        .padding(20)
      }
      .height('100%')
    }
    // Set navigation mode: Stack (for linear workflows like forms)
    .mode(NavigationMode.Stack)
    .title('Personal Finance Hub') // Optional: Add a persistent title
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Details:

  • NavPathStack acts as the "memory" of navigation, tracking the order of subpages. This ensures the back button (physical or on-screen) correctly navigates backward through the stack.
  • pushDestination accepts a data parameter, which we use to pass context (e.g., user IDs, transaction IDs) to subpages—critical for fetching user-specific financial data without redundant API calls.

(2) Create Subpages with NavDestination

Subpages are wrapped in NavDestination, which integrates with the main Navigation container and inherits the stack context.

// Define a builder function to register the subpage in the routing table
@Builder
export function InvestmentPageBuilder() {
  InvestmentPage()
}

@Entry
@Component
struct InvestmentPage {
  private TAG: string = "InvestmentPage";
  @State details: string = 'Loading investment data...';
  // Holds the navigation stack context to enable further navigation
  pathStack: NavPathStack = new NavPathStack();

  build() {
    // Subpage container, bound to the main navigation stack
    NavDestination() {
      Row() {
        Column({ space: 15 }) {
          Text('Investment Details')
            .fontSize(24)
            .fontWeight(FontWeight.Medium)

          Text(this.details)
            .fontSize(16)
            .textColor('#666666')

          // Button to navigate deeper (e.g., to "Transaction History")
          Button('View Transaction History')
            .fontSize(14)
            .padding({ left: 16, right: 16, top: 8, bottom: 8 })
            .backgroundColor('#F0F7FF')
            .textColor('#0066FF')
            .borderRadius(6)
            .onClick(() => {
              this.pathStack.pushDestination({ name: "TransactionPage" });
            })
        }
        .width('100%')
        .padding(20)
      }
      .height('100%')
    }
    // Triggered when the subpage becomes visible
    .onShown(() => {
      console.log(this.TAG, "Investment page displayed");
      // Fetch real-time investment data (e.g., from a financial API)
      this.loadInvestmentData();
    })
    // Triggered when the subpage is ready, with stack context
    .onReady((context: NavDestinationContext) => {
      this.pathStack = context.pathStack; // Get the stack to enable further navigation
    })
  }

  // Simulate fetching user-specific investment data
  private loadInvestmentData() {
    // In practice, this would call a secure financial API with the user ID from data
    setTimeout(() => {
      this.details = "Stocks: $15,200 | Bonds: $8,700 | Cash: $3,500";
    }, 800);
  }
}
Enter fullscreen mode Exit fullscreen mode

Key Details:

  • @Builder registers the subpage, allowing the routing table to locate and instantiate it.
  • onShown is ideal for refreshing data when the subpage becomes active (e.g., reloading real-time stock prices when the user returns to the page).
  • onReady retrieves the NavPathStack context, enabling the subpage to initiate further navigation (e.g., drilling down from investment details to transaction history).

(3) Configure the Routing Table

To link subpages to the main navigation system, configure a routing table (router_map.json) and reference it in module.json5.

router_map.json (stored in src/main/ets/profile):

{
  "routerMap": [
    {
      "name": "InvestmentPage", // Must match the name used in pushDestination
      "pageSourceFile": "src/main/ets/pages/InvestmentPage.ets", // Path to the subpage
      "buildFunction": "InvestmentPageBuilder", // Builder function name
      "data": { "description": "Displays user investment portfolio" } // Optional initial data
    },
    {
      "name": "TransactionPage",
      "pageSourceFile": "src/main/ets/pages/TransactionPage.ets",
      "buildFunction": "TransactionPageBuilder",
      "data": { "description": "Shows recent financial transactions" }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

module.json5 (project configuration):

{
  "module": {
    "routerMap": "$profile:route_map" // Reference the routing table
    // Other module configurations...
  }
}
Enter fullscreen mode Exit fullscreen mode

Critical Note: The routing table is mandatory. It acts as a "directory" for the system to locate subpages, ensuring Navigation can correctly resolve and load components. Without it, navigation will fail silently—we learned this the hard way during testing!

(4) Dynamic Routing for Modular Development (API 12+)

Starting from API version 12, Navigation supports dynamic routing via the system-level routing table. This means each business module (e.g., a "Loans" HSP or "Investments" HAR) can define its own router_map.json. During navigation, the system dynamically loads the required module, builds the component, and completes the routing—eliminating hard dependencies between modules.

In financial apps, this is transformative: teams can develop the "Account Management" and "Payment" modules independently, with Navigation handling cross-module navigation seamlessly. It also reduces app startup time, as modules are loaded on demand rather than upfront.

Practical Experience in Financial Apps

Since adopting Navigation, we’ve seen tangible improvements:

  • Reduced Coupling: Components no longer need to import or reference each other directly; routing is managed via names and data passing, making the codebase easier to maintain.
  • Intuitive Navigation: Users report fewer confusion in multi-step workflows (e.g., loan applications), thanks to Navigation’s predictable back-button behavior.
  • Device Adaptability: On foldable devices, our app now provides a "desktop-like" experience with split-screen financial data, boosting user engagement by 23% in beta testing.

In conclusion, Navigation represents a significant leap forward in HarmonyOS routing, especially for complex applications like financial tools. Its component-level design, robust stack management, and adaptive capabilities make it far more suitable than router for modern, user-centric apps. If you’re working on a HarmonyOS project—particularly one with multi-step workflows or diverse device targets—adopting Navigation is well worth the transition effort.

Top comments (0)