Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mobile-starter.amisi.ai/llms.txt

Use this file to discover all available pages before exploring further.

@amisi/navigation

@amisi/navigation provides router helpers, navigation guards, and deep linking utilities for Expo Router applications.

Features

  • Deep link configuration - Parse and build deep links with type safety
  • Navigation guards - Protect routes based on auth, onboarding, subscription status
  • Transition presets - Reusable stack navigation transitions
  • Typed routes - Type-safe route helpers and navigation

Deep Linking

Create linking configuration

import { createLinkingConfig } from '@amisi/navigation';

const linking = createLinkingConfig({
  scheme: 'myapp',
  prefixes: ['https://myapp.com', 'myapp://'],
});
import { parseDeepLink } from '@amisi/navigation';

const parsed = parseDeepLink('myapp://profile/123');
// { screen: 'Profile', params: { id: '123' } }
import { buildDeepLink } from '@amisi/navigation';

const url = buildDeepLink('myapp', 'Profile', { userId: '123' });
// 'myapp://profile?userId=123'

Check route types

import { isAuthenticatedRoute, isOnboardingRoute } from '@amisi/navigation';

if (isAuthenticatedRoute('Profile')) {
  // Redirect to sign in if not authenticated
}

if (isOnboardingRoute('Welcome')) {
  // Show onboarding flow
}
Guards prevent screen flashing and protect routes based on app state.

AuthGate

Protect authenticated routes and redirect unauthenticated users:
import { AuthGate } from '@amisi/navigation';
import { useAuthContext } from '@amisi/auth';

export default function RootLayout() {
  const { session, loading } = useAuthContext();

  return (
    <AuthGate
      isAuthenticated={!!session}
      isLoading={loading}
      fallback={<LoadingScreen />}
      redirectTo="/sign-in"
    >
      <Stack />
    </AuthGate>
  );
}

OnboardingGate

Ensure users complete onboarding before accessing the app:
import { OnboardingGate } from '@amisi/navigation';

<OnboardingGate
  hasCompletedOnboarding={user?.onboardingComplete}
  isLoading={loading}
  redirectTo="/onboarding"
>
  <MainApp />
</OnboardingGate>;

SubscriptionGate

Require active subscription for premium features:
import { SubscriptionGate } from '@amisi/navigation';
import { useSubscriptions } from '@amisi/subscriptions';

export function PremiumFeature() {
  const { status, loading } = useSubscriptions();

  return (
    <SubscriptionGate
      hasActiveSubscription={status?.isActive ?? false}
      isLoading={loading}
      redirectTo="/subscription"
    >
      <PremiumContent />
    </SubscriptionGate>
  );
}

AppLockGate

Handle app lock/unlock state:
import { AppLockGate } from '@amisi/navigation';
import { useAppLock } from '@amisi/app-lock';

export function App() {
  const { isLocked, loading, unlock } = useAppLock();

  return (
    <AppLockGate
      isLocked={isLocked}
      isLoading={loading}
      onUnlock={unlock}
      fallback={<SplashScreen />}
    >
      <MainApp />
    </AppLockGate>
  );
}

ComposedGate

Combine multiple guards:
import { ComposedGate } from '@amisi/navigation';

<ComposedGate
  guards={[
    { condition: isAuthenticated, redirectTo: '/sign-in' },
    { condition: hasCompletedOnboarding, redirectTo: '/onboarding' },
    { condition: !isLocked, redirectTo: '/lock' },
  ]}
  fallback={<LoadingScreen />}
>
  <MainApp />
</ComposedGate>;

Transition Presets

Reusable navigation transitions for consistent animations.

Using presets

import { transitionPresets } from '@amisi/navigation';

<Stack.Screen
  name="modal"
  options={transitionPresets.modal}
/>

<Stack.Screen
  name="auth"
  options={transitionPresets.fade}
/>

Available presets

  • modal - Standard modal presentation
  • transparentModal - Modal with transparent background
  • slideFromRight - Horizontal slide (default stack)
  • slideFromBottom - Vertical slide
  • fade - Fade in/out
  • noAnimation - No animation
  • auth - Fade transition for auth screens
  • onboarding - Horizontal slide for onboarding
  • tabBar - Fade for tab navigation
  • platformModal - Platform-specific modal (iOS modal, Android slide)
  • platformAuth - Platform-specific auth (iOS fade, Android slide)

Platform-specific transitions

import {
  getTransitionForPlatform,
  fadeTransition,
  slideFromRightTransition,
} from '@amisi/navigation';

const customTransition = getTransitionForPlatform(
  fadeTransition,
  slideFromRightTransition,
);

Custom transitions

import { createCustomTransition } from '@amisi/navigation';

const myTransition = createCustomTransition({
  gestureEnabled: true,
  gestureDirection: 'horizontal',
  animationEnabled: true,
});

Typed Routes

Type-safe route helpers and navigation.

Route constants

import { routes } from '@amisi/navigation';

// Navigate to routes
router.push(routes.home());
// '/'

router.push(routes.profile('user123'));
// '/profile/user123'

router.push(routes.details('item456'));
// '/details/item456'

Build routes with params

import { buildRoute } from '@amisi/navigation';

const path = buildRoute('/profile', { tab: 'settings', edit: true });
// '/profile?tab=settings&edit=true'

Parse routes

import { parseRoute } from '@amisi/navigation';

const { pathname, params } = parseRoute('/profile?tab=settings');
// { pathname: '/profile', params: { tab: 'settings' } }

Match routes with patterns

import { matchRoute } from '@amisi/navigation';

const { match, params } = matchRoute('/details/123', '/details/:id');
// { match: true, params: { id: '123' } }

Route type checking

import { isModalRoute, isAuthRoute, isProtectedRoute } from '@amisi/navigation';

if (isModalRoute('details')) {
  // Use modal transition
}

if (isAuthRoute('signIn')) {
  // Don't require authentication
}

if (isProtectedRoute('home')) {
  // Require authentication
}

Get initial route

import { getInitialRoute } from '@amisi/navigation';

const initialRoute = getInitialRoute(isAuthenticated, hasCompletedOnboarding);
// Returns: 'SignIn' | 'Welcome' | 'Home'
import {
  getActiveRouteName,
  getPreviousRouteName,
  canGoBack,
} from '@amisi/navigation';

const currentRoute = getActiveRouteName(navigationState);
const previousRoute = getPreviousRouteName(navigationState);
const canNavigateBack = canGoBack(navigationState);

Architecture Benefits

This package solves common navigation pain points:
  1. Deep link → correct stack - Parse and route deep links to the right screen
  2. Modal routes - Consistent modal presentation across platforms
  3. No screen flashing - Guards prevent showing wrong screens during state changes
  4. Type safety - Typed route helpers prevent navigation errors
  5. Reusable transitions - Consistent animations across the app
  6. Composable guards - Combine multiple protection layers easily

Example: Complete Setup

import { Stack } from 'expo-router';
import { AuthGate, OnboardingGate, transitionPresets } from '@amisi/navigation';
import { useAuthContext } from '@amisi/auth';

export default function RootLayout() {
  const { session, loading } = useAuthContext();

  return (
    <AuthGate
      isAuthenticated={!!session}
      isLoading={loading}
      fallback={<LoadingScreen />}
    >
      <OnboardingGate
        hasCompletedOnboarding={session?.user?.onboardingComplete}
        isLoading={loading}
      >
        <Stack>
          <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
          <Stack.Screen name="modal" options={transitionPresets.modal} />
          <Stack.Screen
            name="details/[id]"
            options={transitionPresets.slideFromRight}
          />
        </Stack>
      </OnboardingGate>
    </AuthGate>
  );
}