Overview
The Softbee app uses GoRouter for declarative routing with Flutter. The router is integrated with Riverpod for state management and includes authentication-aware navigation.
Source: lib/core/router/app_router.dart
AppRouter Provider
appRouterProvider
The main router provider that creates and configures the GoRouter instance.
final appRouterProvider = Provider<GoRouter>((ref) {
final notifier = ref.watch(routerNotifierProvider);
return GoRouter(
refreshListenable: notifier,
initialLocation: kIsWeb ? AppRoutes.landingRoute : AppRoutes.loginRoute,
routes: [...],
redirect: (context, state) { ... },
errorBuilder: (context, state) => const NotFoundPage(),
);
});
Notifier that triggers router refresh when auth state changes
Initial route based on platform detection (landing page for web, login for mobile)
Global redirect logic for authentication-based navigation guards
Builder for 404/error pages (returns NotFoundPage)
RouterNotifier
A ChangeNotifier that listens to authentication state changes and triggers router rebuilds.
class RouterNotifier extends ChangeNotifier {
final Ref _ref;
RouterNotifier(this._ref) {
_ref.listen<AuthState>(
authControllerProvider,
(_, __) => notifyListeners(),
);
}
}
Purpose: Automatically refreshes routes when user logs in/out.
Route Definitions
All route paths are defined in lib/core/router/app_routes.dart:
Main Routes
Landing page for web users
registerRoute
String
default:"/register"
User registration page
dashboardRoute
String
default:"/dashboard"
Main dashboard showing list of apiaries
User profile management page
resetPasswordRoute
String
default:"/reset-password/:token"
Password reset page with token parameter
Apiary-Specific Routes
apiaryDashboardRoute
String
default:"/apiary-dashboard/:apiaryId"
Dashboard for a specific apiary
monitoringOverviewRoute
String
default:"/apiary-dashboard/:apiaryId/monitoring"
Monitoring overview for an apiary
beehiveManagementRoute
String
default:"/apiary-dashboard/:apiaryId/hives"
Beehive management page
Questions management for monitoring
inventoryRoute
String
default:"/apiary-dashboard/:apiaryId/inventory"
Inventory management page
reportsRoute
String
default:"/apiary-dashboard/:apiaryId/reports"
Reports page for an apiary
historyRoute
String
default:"/apiary-dashboard/:apiaryId/history"
History page showing apiary activity
apiarySettingsRoute
String
default:"/apiary-dashboard/:apiaryId/settings"
Settings page for an apiary
Navigation Guards
The router implements authentication-based navigation guards through the redirect callback:
redirect: (context, state) {
final authState = ref.read(authControllerProvider);
final isLoggedIn = authState.isAuthenticated;
final isAuthRoute = state.matchedLocation == AppRoutes.loginRoute ||
state.matchedLocation == AppRoutes.registerRoute ||
state.matchedLocation == '/forgot-password' ||
state.matchedLocation.startsWith(
AppRoutes.resetPasswordRoute.split(':')[0],
);
final isLandingRoute = state.matchedLocation == AppRoutes.landingRoute;
// Don't redirect while checking auth status
if (authState.isAuthenticating) {
return null;
}
// Redirect to login if not authenticated and not on auth/landing route
if (!isLoggedIn && !isAuthRoute && !isLandingRoute) {
return AppRoutes.loginRoute;
}
// Redirect to dashboard if authenticated and on auth/landing route
if (isLoggedIn && (isAuthRoute || isLandingRoute)) {
return AppRoutes.dashboardRoute;
}
return null;
}
Redirect Logic
- While authenticating: No redirect (allows loading states)
- Not logged in + protected route: Redirect to login
- Logged in + auth route: Redirect to dashboard
- Otherwise: Allow navigation
Usage Examples
Navigate to a Route
// Navigate to dashboard
context.go(AppRoutes.dashboardRoute);
// Navigate to apiary dashboard with parameters
context.go(
'/apiary-dashboard/abc123?apiaryName=My Apiary&apiaryLocation=California'
);
Using Named Routes
// Navigate to monitoring overview
context.goNamed(
AppRoutes.monitoringOverviewRoute,
pathParameters: {'apiaryId': 'abc123'},
queryParameters: {
'apiaryName': 'My Apiary',
'apiaryLocation': 'California',
},
);
Accessing Route Parameters
builder: (context, state) {
// Path parameters
final apiaryId = state.pathParameters['apiaryId'] as String;
// Query parameters
final apiaryName = state.uri.queryParameters['apiaryName'];
final apiaryLocation = state.uri.queryParameters['apiaryLocation'];
return ApiaryDashboardMenu(
apiaryId: apiaryId,
apiaryName: apiaryName ?? 'Unknown Apiary',
apiaryLocation: apiaryLocation,
);
}
Nested Routes
The apiary dashboard uses nested routes for sub-pages:
GoRoute(
path: AppRoutes.apiaryDashboardRoute,
builder: (context, state) => ApiaryDashboardMenu(...),
routes: [
GoRoute(
path: 'monitoring',
name: AppRoutes.monitoringOverviewRoute,
builder: (context, state) => MonitoringOverviewPage(...),
routes: [
GoRoute(
path: 'beehives',
name: AppRoutes.beehiveManagementRoute,
builder: (context, state) => ColmenasManagementScreen(...),
),
GoRoute(
path: 'questions',
name: AppRoutes.questionsManagementRoute,
builder: (context, state) => QuestionsManagementScreen(...),
),
],
),
],
),
The router uses platform detection for initial routing:
import 'package:flutter/foundation.dart' show kIsWeb;
initialLocation: kIsWeb
? AppRoutes.landingRoute // Web: Show landing page
: AppRoutes.loginRoute // Mobile: Go to login
Integration with Riverpod
The router is consumed in main.dart through the provider:
class MyApp extends ConsumerWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final router = ref.watch(appRouterProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: router,
);
}
}
See Also