Get the FREE Ultimate OpenClaw Setup Guide →

nylo-routing

npx machina-cli add skill nylo-core/claude-code/nylo-routing --openclaw
Files (1)
SKILL.md
14.3 KB

Nylo Routing

Overview

Nylo v7 routing is defined in lib/routes/router.dart using nyRoutes(). Routes map URL paths to page widgets, support guards for access control, and integrate with NavigationHub for tab-based and journey navigation layouts.

When to Use

  • Defining or modifying app routes
  • Navigating between pages (push, replace, pop)
  • Passing data, query parameters, or path parameters between pages
  • Protecting routes with guards (auth, roles, subscriptions)
  • Setting up bottom navigation, top tabs, or onboarding journeys
  • Configuring page transition animations
  • Implementing deep linking
  • When NOT to use: For state management within a single page, use NyState/NyPage instead

Quick Reference

ActionCode
Define routerouter.add(MyPage.path)
Set initial routerouter.add(MyPage.path).initialRoute()
Navigate to pagerouteTo(MyPage.path)
Navigate with datarouteTo(MyPage.path, data: {"key": "val"})
Navigate with query paramsrouteTo(MyPage.path, queryParameters: {"tab": "1"})
Navigate with path paramsrouteTo(MyPage.path.withParams({"id": 7}))
Go backpop()
Go back with resultpop(result: {"status": "done"})
Replace current routerouteTo(MyPage.path, navigationType: NavigationType.pushReplace)
Clear stack and navigaterouteTo(MyPage.path, navigationType: NavigationType.pushAndForgetAll)
Navigate to initial routerouteToInitial()
Navigate to auth routerouteToAuthenticatedRoute()
Add route guardrouter.add(MyPage.path, routeGuards: [MyGuard()])
Create page (CLI)metro make:page profile_page
Create guard (CLI)metro make:route_guard auth
Create nav hub (CLI)metro make:navigation_hub base

Route Definitions

Routes live in lib/routes/router.dart:

appRouter() => nyRoutes((router) {
  router.add(HomePage.path).initialRoute();
  router.add(ProfilePage.path);
  router.add(SettingsPage.path);
});

Special Route Types

// Initial route - first page on app launch
router.add(HomePage.path).initialRoute();

// Conditional initial route
router.add(OnboardingPage.path).initialRoute(
  when: () => !hasCompletedOnboarding()
);
router.add(HomePage.path).initialRoute(
  when: () => hasCompletedOnboarding()
);

// Authenticated route - shown after Auth.authenticate()
router.add(DashboardPage.path).authenticatedRoute();

// Unknown/404 route
router.add(NotFoundPage.path).unknownRoute();

// Preview route - dev only, remove before release
router.add(ProfilePage.path).previewRoute();

Page with Path Parameters

class ProfilePage extends NyStatefulWidget<HomeController> {
  static RouteView path = ("/profile/{userId}", (_) => ProfilePage());
  ProfilePage() : super(child: () => _ProfilePageState());
}

Navigation

Basic Navigation

routeTo(SettingsPage.path);

Navigation Types

// Push onto stack (default)
routeTo(MyPage.path, navigationType: NavigationType.push);

// Replace current route
routeTo(MyPage.path, navigationType: NavigationType.pushReplace);

// Pop current, then push
routeTo(MyPage.path, navigationType: NavigationType.popAndPushNamed);

// Push and clear entire stack
routeTo(MyPage.path, navigationType: NavigationType.pushAndForgetAll);

Passing Data

// Send data
routeTo(ProfilePage.path, data: {"firstName": "Anthony"});

// Receive data in target page
class _ProfilePageState extends NyPage<ProfilePage> {
  @override
  get init => () {
    final data = widget.data();
    print(data["firstName"]); // Anthony
  };
}

Path Parameters

// Navigate with path params (e.g., /profile/7)
routeTo(ProfilePage.path.withParams({"userId": 7}));

// Access in target page
class _ProfilePageState extends NyPage<ProfilePage> {
  @override
  get init => () {
    print(widget.queryParameters()); // {"userId": "7"}
  };
}

Query Parameters

// Navigate with query params
routeTo(ProfilePage.path, queryParameters: {"user": "20", "tab": "posts"});

// Or use withQueryParams
routeTo(ProfilePage.path.withQueryParams({"user": "20", "tab": "posts"}));

// Access in target page
class _ProfilePageState extends NyPage<ProfilePage> {
  @override
  get init => () {
    final params = queryParameters();
    final userId = params["user"];   // "20"
    final tab = params["tab"];       // "posts"
  };
}

Pop with Result

// Navigate and listen for result
routeTo(SettingsPage.path, onPop: (value) {
  print(value); // {"status": "COMPLETE"}
});

// In SettingsPage, return result
pop(result: {"status": "COMPLETE"});

Conditional Navigation

routeIf(isLoggedIn, DashboardPage.path);

routeIf(
  hasPermission('view_reports'),
  ReportsPage.path,
  data: {'filters': defaultFilters},
  navigationType: NavigationType.push,
);

Route Guards

Guards run before navigation to control access. Generate with CLI:

metro make:route_guard auth

Basic Guard

class AuthRouteGuard extends NyRouteGuard {
  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    bool isLoggedIn = await Auth.isAuthenticated();
    if (!isLoggedIn) {
      return redirect(LoginPage.path);
    }
    return next();
  }

  @override
  Future<void> onAfter(RouteContext context) async {
    Analytics.trackPageView(context.routeName);
  }

  @override
  Future<bool> onLeave(RouteContext context) async {
    if (hasUnsavedChanges) {
      return await showConfirmDialog();
    }
    return true; // allow leaving
  }
}

Guard Actions

ActionPurpose
return next()Continue to route
return redirect(LoginPage.path)Redirect to another route
return abort()Cancel navigation, stay on current page
setData({...})Enrich data for subsequent guards/target

Redirect with Options

return redirect(
  LoginPage.path,
  data: {"returnTo": context.routeName},
  navigationType: NavigationType.pushReplace,
  queryParameters: {"source": "guard"},
);

Apply Guards to Routes

// Single guard
router.add(DashboardPage.path, routeGuards: [AuthRouteGuard()]);

// Multiple guards (run in sequence)
router.add(AdminPage.path, routeGuards: [AuthRouteGuard(), AdminRoleGuard()]);

// Fluent API
router.add(DashboardPage.path).addRouteGuard(MyGuard());
router.add(DashboardPage.path).addRouteGuards([Guard1(), Guard2()]);

Parameterized Guards

class RoleGuard extends ParameterizedGuard<List<String>> {
  RoleGuard(super.params);

  @override
  Future<GuardResult> onBefore(RouteContext context) async {
    User? user = await Auth.user<User>();
    if (user == null || !params.contains(user.role)) {
      return redirect(UnauthorizedPage.path);
    }
    return next();
  }
}

// Usage
router.add(AdminPage.path, routeGuards: [
  RoleGuard(["admin", "super_admin"])
]);

Guard Stacks (Reusable Combinations)

final protectedRoute = GuardStack([
  AuthRouteGuard(),
  VerifyEmailGuard(),
  TwoFactorGuard(),
]);

router.add(SecurePage.path, routeGuards: [protectedRoute]);

Conditional Guards

router.add(BetaPage.path, routeGuards: [
  ConditionalGuard(
    condition: (context) => context.queryParameters.containsKey("beta"),
    guard: BetaAccessGuard(),
  ),
]);

Route Groups

Share settings across multiple routes:

router.group(() => {
  "route_guards": [AuthRouteGuard()],
  "prefix": "/dashboard",
  "transition_type": TransitionType.fade(),
}, (router) {
  router.add(ChatPage.path);
  router.add(FollowersPage.path);
});
SettingPurpose
route_guardsGuards applied to all routes in group
prefixURL prefix for all routes
transition_typeShared transition animation

Page Transitions

router.add(SettingsPage.path,
  transitionType: TransitionType.bottomToTop()
);

// Or when navigating
routeTo(ProfilePage.path, transitionType: TransitionType.fade());

Available Transitions

CategoryTypes
Basicfade(), theme()
DirectionalrightToLeft(), leftToRight(), topToBottom(), bottomToTop()
With faderightToLeftWithFade(), leftToRightWithFade()
Transformscale(alignment:), rotate(alignment:), size(alignment:)
MaterialsharedAxisHorizontal(), sharedAxisVertical(), sharedAxisScale()

Each transition accepts optional curve, duration, reverseDuration, fullscreenDialog, and opaque parameters.

NavigationHub

NavigationHub manages multi-tab and journey-based navigation layouts. Generate with CLI:

metro make:navigation_hub base
# Choose: navigation_tabs or journey_states

Bottom Navigation Hub

class BaseNavigationHub extends NyStatefulWidget with BottomNavPageControls {
  static RouteView path = ("/base", (_) => BaseNavigationHub());
  BaseNavigationHub()
      : super(child: () => _BaseNavigationHubState(),
              stateName: path.stateName());

  static NavigationHubStateActions stateActions =
    NavigationHubStateActions(path.stateName());
}

class _BaseNavigationHubState extends NavigationHub<BaseNavigationHub> {
  @override
  NavigationHubLayout? layout(BuildContext context) =>
    NavigationHubLayout.bottomNav();

  @override
  bool get maintainState => true;

  @override
  int get initialIndex => 0;

  _BaseNavigationHubState() : super(() => {
    0: NavigationTab.tab(title: "Home", page: HomeTab(), icon: Icon(Icons.home)),
    1: NavigationTab.tab(title: "Settings", page: SettingsTab(), icon: Icon(Icons.settings)),
  });

  @override
  onTap(int index) {
    super.onTap(index);
  }
}

Layout Types

// Bottom navigation
NavigationHubLayout.bottomNav(
  backgroundColor: Colors.white,
  selectedItemColor: Colors.blue,
  unselectedItemColor: Colors.grey,
);

// Top navigation (tabs)
NavigationHubLayout.topNav(
  labelColor: Colors.blue,
  unselectedLabelColor: Colors.grey,
  indicatorColor: Colors.blue,
);

// Journey (onboarding/multi-step)
NavigationHubLayout.journey(
  progressStyle: JourneyProgressStyle(
    indicator: JourneyProgressIndicator.linear(),
  ),
);

Tab Types

// Standard tab
NavigationTab.tab(title: "Home", page: HomeTab(), icon: Icon(Icons.home));

// Badge tab (shows count)
NavigationTab.badge(title: "Chats", page: ChatTab(),
  icon: Icon(Icons.message), initialCount: 10);

// Alert tab (shows dot indicator)
NavigationTab.alert(title: "Alerts", page: AlertTab(),
  icon: Icon(Icons.notification_important), alertEnabled: true);

// Journey tab
NavigationTab.journey(page: WelcomeStep());

Controlling Hub from Anywhere

// Switch tab programmatically
MyHub.stateActions.currentTabIndex(2);

// Badge management
MyHub.stateActions.updateBadgeCount(tab: 0, count: 5);
MyHub.stateActions.incrementBadgeCount(tab: 0);
MyHub.stateActions.clearBadgeCount(tab: 0);

// Alert management
MyHub.stateActions.alertEnableTab(tab: 0);
MyHub.stateActions.alertDisableTab(tab: 0);

Journey Navigation (Onboarding Flows)

Journey states extend JourneyState and provide lifecycle hooks:

class _WelcomeState extends JourneyState<Welcome> {
  _WelcomeState() : super(
    navigationHubState: OnboardingNavigationHub.path.stateName()
  );

  @override
  Future<bool> canContinue() async {
    if (nameController.text.isEmpty) {
      showToastSorry(description: "Please enter your name");
      return false;
    }
    return true;
  }

  @override
  Future<void> onBeforeNext() async {
    session('onboarding', {'name': nameController.text});
  }

  @override
  Widget view(BuildContext context) {
    return buildJourneyContent(
      content: Text("Welcome!"),
      nextButton: Button.primary(text: "Continue", onPressed: nextStep),
      backButton: isFirstStep ? null : Button.textOnly(
        text: "Back", onPressed: onBackPressed),
    );
  }
}

Key JourneyState properties: isFirstStep, isLastStep, currentStep, totalSteps, completionPercentage

Key JourneyState methods: nextStep(), previousStep(), goToStep(index), exitJourney()

Route History

Nylo.getRouteHistory();            // All visited routes
Nylo.getCurrentRouteName();        // Current route path
Nylo.getPreviousRouteName();       // Previous route path
Nylo.getCurrentRouteArguments();   // Current route data

// Programmatically update the navigation stack
NyNavigator.updateStack([
  HomePage.path,
  ProfilePage.path,
], replace: true, dataForRoute: {
  ProfilePage.path: {"userId": 42},
});

Deep Linking

Routes automatically support deep links. Configure platform-specific settings (universal links for iOS, app links for Android), then handle events:

nylo.onDeepLink((String route, Map<String, String>? data) {
  if (route == ProfilePage.path) {
    NyNavigator.updateStack([
      HomePage.path,
      ProfilePage.path,
    ], replace: true, dataForRoute: {
      ProfilePage.path: data,
    });
  }
});

Test with CLI:

# Android
adb shell am start -a android.intent.action.VIEW \
  -d "https://yourdomain.com/profile?user=20" com.yourcompany.yourapp

# iOS Simulator
xcrun simctl openurl booted "https://yourdomain.com/profile?user=20"

Common Mistakes

MistakeFix
Forgetting super.onTap(index) in NavigationHub onTap overrideAlways call super.onTap(index) to update the tab index
Using path params but accessing via widget.data()Path params are accessed via widget.queryParameters() or queryParameters()
Not converting URL params from stringsAll URL/query params arrive as strings; parse with int.parse(), DateTime.parse(), etc.
Leaving previewRoute() in productionRemove .previewRoute() before release builds
Calling routeTo without registering the route in router.dartEvery page must be registered with router.add()
Guards returning nothingAlways return next(), redirect(), or abort() from onBefore
Using NavigationType.pushAndForgetAll for regular navigationOnly use when you intentionally want to clear the entire nav stack (e.g., after login)

Source

git clone https://github.com/nylo-core/claude-code/blob/main/skills/nylo-routing/SKILL.mdView on GitHub

Overview

Nylo routing defines app routes in lib/routes/router.dart using nyRoutes(). Routes map URL paths to pages, add guards for access control, and integrate with NavigationHub for tab-based and journey navigation layouts. This keeps navigation, deep links, and transitions organized in a single, testable layer.

How This Skill Works

Routing is configured with nyRoutes and router.add calls to declare each page path. It supports initialRoute, authenticatedRoute, unknownRoute, and routeGuards, and uses routeTo to navigate with data, query parameters, or path parameters, enabling consistent transitions and deep links.

When to Use It

  • Defining or modifying app routes
  • Navigating between pages (push, replace, pop)
  • Passing data, query parameters, or path parameters between pages
  • Protecting routes with guards (auth, roles, subscriptions)
  • Setting up bottom navigation, top tabs, or onboarding journeys with NavigationHub

Quick Start

  1. Step 1: Open lib/routes/router.dart and define routes with nyRoutes and router.add
  2. Step 2: Use routeTo for navigation, including data or params as needed
  3. Step 3: Add guards and NavigationHub setups (auth, onboarding) and test deep links

Best Practices

  • Centralize routing in lib/routes/router.dart using nyRoutes
  • Use initialRoute(), authenticatedRoute(), and unknownRoute() to control entry and errors
  • Pass data via routeTo(data: ...) and retrieve with widget.data() in the destination
  • Utilize withParams and queryParameters to handle dynamic paths and queries
  • Integrate with NavigationHub for tab layouts and onboarding flows; define clear page transitions

Example Use Cases

  • Define a route and mark it as initial: router.add(HomePage.path).initialRoute();
  • Navigate to a page: routeTo(SettingsPage.path);
  • Navigate with data: routeTo(ProfilePage.path, data: {"firstName": "Anthony"});
  • Navigate with path params: routeTo(MyPage.path.withParams({"id": 7}));
  • Protect a route with a guard: router.add(MyPage.path, routeGuards: [MyGuard()]);

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers