Skip to content

Navigation

DWallet uses AutoRoute for declarative, type-safe routing in Flutter.

  • Type-safe: No more string-based route names
  • Code generation: Routes are generated automatically
  • Deep linking: Built-in support for URL-based navigation
  • Guards: Protect routes with authentication checks
  • Nested navigation: Support for complex routing scenarios

Routes are defined in lib/app/router/_app_router.dart:

@AutoRouterConfig()
class DAppRouter extends RootStackRouter {
@override
List<AutoRoute> get routes => [
// Onboarding flow (no guard)
AutoRoute(
page: OnboardRoute.page,
path: '/onboard',
children: [
AutoRoute(page: WelcomeRoute.page, path: ''),
AutoRoute(page: CurrencySelectorRoute.page, path: 'currency-selector'),
AutoRoute(page: AddInitialBalanceRoute.page, path: 'add-initial-balance'),
],
),
// Main app (guarded)
AutoRoute(
page: ClientRoute.page,
path: '/client',
guards: [OnboardRouteGuard],
children: [
AutoRoute(
page: BottomNavRoute.page,
path: '',
children: [
AutoRoute(page: HomeRoute.page, path: 'home'),
AutoRoute(page: WalletListRoute.page, path: 'wallet-list'),
AutoRoute(page: AnalyticsRoute.page, path: 'analytics'),
AutoRoute(page: SettingsRoute.page, path: 'settings'),
],
),
AutoRoute(page: AppearanceRoute.page, path: 'appearance'),
AutoRoute(page: AppLockSecurityRoute.page, path: 'app-lock-security'),
AutoRoute(page: AllTransactionListRoute.page, path: 'all-transaction-list'),
AutoRoute(page: WalletDetailsRoute.page, path: 'wallet-details'),
],
),
];
}

Protect routes with guards:

class OnboardRouteGuard extends AutoRouteGuard {
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
final container = ProviderScope.containerOf(resolver.context);
final authService = container.read(authUserServiceProvider);
// Check if user has completed onboarding
final hasCompletedOnboarding = authService.value?.hasCompletedOnboarding ?? false;
if (hasCompletedOnboarding) {
resolver.next(true); // Allow navigation
} else {
resolver.redirect(const OnboardRoute()); // Redirect to onboarding
}
}
}
// Push a new route
context.router.push(const HomeRoute());
// Replace current route
context.router.replace(const HomeRoute());
// Pop current route
context.router.pop();
// Pop until specific route
context.router.popUntil((route) => route.settings.name == HomeRoute.name);
// Define route with parameters
@RoutePage()
class WalletDetailsPage extends StatelessWidget {
final String walletId;
const WalletDetailsPage({
required this.walletId,
super.key,
});
}
// Navigate with parameters
context.router.push(WalletDetailsRoute(walletId: '123'));
// Access child router
final tabsRouter = AutoTabsRouter.of(context);
// Switch tabs
tabsRouter.setActiveIndex(2); // Switch to 3rd tab
class SomePage extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(dAppRouterProvider);
return ElevatedButton(
onPressed: () => router.push(const HomeRoute()),
child: Text('Go Home'),
);
}
}

Use wrapper pages for shared UI:

// Auth wrapper with custom transitions
@RoutePage()
class AuthWrapperPage extends StatelessWidget {
const AuthWrapperPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: AutoRouter(), // Child routes render here
);
}
}

AutoRoute supports deep linking out of the box:

// In AndroidManifest.xml
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="dwallet" android:host="wallet" />
</intent-filter>
  1. Use const constructors for routes when possible
  2. Keep route definitions centralized in one file
  3. Use guards for authentication/protected routes
  4. Use nested routes for tab-based navigation
  5. Run build_runner after adding new routes:
    Terminal window
    flutter pub run build_runner build --delete-conflicting-outputs
AutoTabsRouter(
routes: const [
HomeRoute(),
WalletListRoute(),
AnalyticsRoute(),
SettingsRoute(),
],
builder: (context, child) {
final tabsRouter = AutoTabsRouter.of(context);
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabsRouter.activeIndex,
onTap: tabsRouter.setActiveIndex,
items: [...],
),
);
},
)
// Bottom sheet
showModalBottomSheet(
context: context,
builder: (_) => const ManageTransactionModal(),
);
// Full screen modal
context.router.pushModal(const SomeRoute());