GetX navigation patterns including routing, parameters, guards, deep linking, and custom transitions for Flutter applications
Implements GetX navigation patterns including routing, parameters, guards, deep linking, and custom transitions for Flutter apps.
/plugin marketplace add Kaakati/rails-enterprise-dev/plugin install reactree-flutter-dev@manifest-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Complete guide to implementing navigation in Flutter applications using GetX's powerful routing system.
Replace MaterialApp with GetMaterialApp to enable GetX navigation:
// lib/main.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'presentation/routes/app_pages.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'My App',
initialRoute: AppRoutes.home,
getPages: AppPages.pages,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
);
}
}
Create a centralized file for all route names:
// lib/presentation/routes/app_routes.dart
class AppRoutes {
// Authentication routes
static const login = '/login';
static const register = '/register';
static const forgotPassword = '/forgot-password';
// Main app routes
static const home = '/';
static const profile = '/profile';
static const settings = '/settings';
// Feature routes with parameters
static const productDetails = '/product/:id';
static const userProfile = '/user/:userId';
static const editPost = '/post/:postId/edit';
// Nested routes
static const dashboard = '/dashboard';
static const dashboardHome = '/dashboard/home';
static const dashboardStats = '/dashboard/stats';
}
Naming Convention:
/product-details not /productDetails):parameter syntax for route parametersConfigure all routes with bindings and middleware:
// lib/presentation/routes/app_pages.dart
import 'package:get/get.dart';
import '../pages/home/home_page.dart';
import '../pages/home/home_binding.dart';
import '../pages/login/login_page.dart';
import '../pages/login/login_binding.dart';
import '../pages/profile/profile_page.dart';
import '../pages/profile/profile_binding.dart';
import 'app_routes.dart';
import 'middlewares/auth_middleware.dart';
class AppPages {
static final pages = [
// Public routes (no authentication required)
GetPage(
name: AppRoutes.login,
page: () => LoginPage(),
binding: LoginBinding(),
transition: Transition.fadeIn,
transitionDuration: const Duration(milliseconds: 300),
),
GetPage(
name: AppRoutes.register,
page: () => RegisterPage(),
binding: RegisterBinding(),
),
// Protected routes (authentication required)
GetPage(
name: AppRoutes.home,
page: () => HomePage(),
binding: HomeBinding(),
middlewares: [AuthMiddleware()],
transition: Transition.fade,
),
GetPage(
name: AppRoutes.profile,
page: () => ProfilePage(),
binding: ProfileBinding(),
middlewares: [AuthMiddleware()],
),
// Routes with parameters
GetPage(
name: AppRoutes.productDetails,
page: () => ProductDetailsPage(),
binding: ProductDetailsBinding(),
middlewares: [AuthMiddleware()],
),
];
}
// Navigate to named route
Get.toNamed(AppRoutes.profile);
// Navigate to route instance
Get.to(() => ProfilePage());
// Navigate and remove previous route
Get.off(() => HomePage());
Get.offNamed(AppRoutes.home);
// Navigate and remove all previous routes
Get.offAll(() => HomePage());
Get.offAllNamed(AppRoutes.home);
// Go back
Get.back();
// Go back with result
Get.back(result: {'success': true});
Route Parameters (in URL path):
// Define route with parameter
static const productDetails = '/product/:id';
// Navigate with parameter
Get.toNamed('/product/123');
// Access parameter in controller
class ProductDetailsController extends GetxController {
@override
void onInit() {
super.onInit();
final productId = Get.parameters['id']; // '123'
}
}
Arguments (passed separately):
// Navigate with arguments
Get.toNamed(
AppRoutes.profile,
arguments: {
'userId': 123,
'userName': 'John Doe',
'isFollowing': true,
},
);
// Access arguments in controller
class ProfileController extends GetxController {
late final int userId;
late final String userName;
late final bool isFollowing;
@override
void onInit() {
super.onInit();
final args = Get.arguments as Map<String, dynamic>;
userId = args['userId'];
userName = args['userName'];
isFollowing = args['isFollowing'];
}
}
Query Parameters:
// Navigate with query parameters
Get.toNamed('/search?query=flutter&sort=relevance');
// Access query parameters
final query = Get.parameters['query']; // 'flutter'
final sort = Get.parameters['sort']; // 'relevance'
// Navigate and await result
final result = await Get.toNamed(AppRoutes.editProfile);
if (result != null && result['updated'] == true) {
// Profile was updated
refreshProfile();
}
// In the destination page, return result
ElevatedButton(
onPressed: () {
Get.back(result: {'updated': true});
},
child: const Text('Save'),
)
// lib/presentation/routes/middlewares/auth_middleware.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../app_routes.dart';
class AuthMiddleware extends GetMiddleware {
@override
int? get priority => 1; // Lower priority = higher execution order
@override
RouteSettings? redirect(String? route) {
// Check if user is authenticated
final authService = Get.find<AuthService>();
if (!authService.isAuthenticated) {
// Redirect to login if not authenticated
return const RouteSettings(name: AppRoutes.login);
}
// Allow navigation if authenticated
return null;
}
@override
GetPage? onPageCalled(GetPage? page) {
// Called before page is created
// Can modify page configuration
return page;
}
@override
List<Bindings>? onBindingsStart(List<Bindings>? bindings) {
// Called before bindings are initialized
return bindings;
}
@override
GetPageBuilder? onPageBuildStart(GetPageBuilder? page) {
// Called before page widget is built
return page;
}
@override
Widget onPageBuilt(Widget page) {
// Called after page widget is built
// Can wrap page with additional widgets
return page;
}
@override
void onPageDispose() {
// Called when page is disposed
}
}
class AdminMiddleware extends GetMiddleware {
@override
int? get priority => 2;
@override
RouteSettings? redirect(String? route) {
final authService = Get.find<AuthService>();
if (!authService.isAdmin) {
// Redirect to home if not admin
Get.snackbar(
'Access Denied',
'You do not have permission to access this page',
snackPosition: SnackPosition.BOTTOM,
);
return const RouteSettings(name: AppRoutes.home);
}
return null;
}
}
Usage:
GetPage(
name: AppRoutes.adminPanel,
page: () => AdminPanelPage(),
binding: AdminBinding(),
middlewares: [
AuthMiddleware(), // Check authentication first
AdminMiddleware(), // Then check admin role
],
)
<!-- android/app/src/main/AndroidManifest.xml -->
<manifest>
<application>
<activity android:name=".MainActivity">
<!-- App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="myapp.example.com"
android:pathPrefix="/product" />
</intent-filter>
<!-- Custom URL Scheme -->
<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="myapp" />
</intent-filter>
</activity>
</application>
</manifest>
<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
<!-- Universal Links -->
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.example.com</string>
</array>
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'My App',
initialRoute: AppRoutes.home,
getPages: AppPages.pages,
// Deep link will automatically match routes
// For example: myapp://product/123 → /product/123
);
}
}
GetPage(
name: AppRoutes.profile,
page: () => ProfilePage(),
transition: Transition.fadeIn,
transitionDuration: const Duration(milliseconds: 300),
)
Available transitions:
Transition.fadeTransition.fadeInTransition.rightToLeftTransition.leftToRightTransition.topToBottomTransition.bottomToTopTransition.rightToLeftWithFadeTransition.leftToRightWithFadeTransition.zoomTransition.sizeTransition.circularRevealGetPage(
name: AppRoutes.productDetails,
page: () => ProductDetailsPage(),
customTransition: CustomSlideTransition(),
transitionDuration: const Duration(milliseconds: 400),
)
class CustomSlideTransition extends CustomTransition {
@override
Widget buildTransition(
BuildContext context,
Curve? curve,
Alignment? alignment,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(
CurvedAnimation(
parent: animation,
curve: curve ?? Curves.easeInOut,
),
),
child: child,
);
}
}
class MainPage extends StatelessWidget {
final controller = Get.put(MainController());
@override
Widget build(BuildContext context) {
return Scaffold(
body: Obx(() => IndexedStack(
index: controller.currentIndex.value,
children: const [
HomePage(),
SearchPage(),
ProfilePage(),
],
)),
bottomNavigationBar: Obx(() => BottomNavigationBar(
currentIndex: controller.currentIndex.value,
onTap: controller.changeTab,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'),
],
)),
);
}
}
class MainController extends GetxController {
final currentIndex = 0.obs;
void changeTab(int index) {
currentIndex.value = index;
}
}
class DashboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text('Dashboard'),
bottom: const TabBar(
tabs: [
Tab(text: 'Overview'),
Tab(text: 'Stats'),
Tab(text: 'Settings'),
],
),
),
body: TabBarView(
children: [
// Each tab can have its own Navigator
Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => OverviewTab(),
);
},
),
Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => StatsTab(),
);
},
),
Navigator(
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => SettingsTab(),
);
},
),
],
),
),
);
}
}
Route Naming:
/user/profile/edit)Parameter Passing:
/product/:id)Navigation Guards:
Deep Linking:
Transitions:
Performance:
lazyPut for route bindings to avoid loading all controllers at startupThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.