Navigating between screens is a critical part of any mobile application. With the introduction of Navigator 2.0 in Flutter, developers gained more control over navigation, making it easier to handle complex app routing and state management. In this post, we will explore the core concepts of Navigator 2.0, provide code examples, and demonstrate best practices to get the most out of this powerful API.
What is Navigator 2.0 in Flutter?
Navigator 2.0 is a declarative navigation system introduced in Flutter to replace the imperative approach of Navigator 1.0. It allows developers to manage app navigation in a structured and predictable way by using:
- Page API: Defines individual pages with unique identifiers.
- Router API: Handles route configuration and state.
- RouterDelegate: Manages navigation logic and listens to route changes.
- RouteInformationParser: Parses and converts route information.
This system provides greater flexibility and is especially useful for apps that require deep linking, URL-based navigation, and nested navigation.
Key Components of Navigator 2.0
To fully understand Navigator 2.0, let’s break down its main components:
- Page
- Represents an entry in the navigation stack.
- Example:
MaterialPageorCupertinoPage.
- RouterDelegate
- Implements the navigation logic.
- Controls the stack of pages shown to the user.
- RouteInformationParser
- Converts incoming route information (like URLs) into a format the app understands.
- BackButtonDispatcher
- Handles system back button events.
Setting Up Navigator 2.0
Let’s dive into the code to create a simple Flutter app using Navigator 2.0. This example will demonstrate how to set up routing for a three-screen app (Home, Details, and Settings).
Step 1: Define Pages
First, define the pages as Dart classes:
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Navigator.of(context).pushNamed('/details'),
child: Text('Go to Details'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pushNamed('/settings'),
child: Text('Go to Settings'),
),
],
),
),
);
}
}
class DetailsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Details')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Back to Home'),
),
),
);
}
}
class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Settings')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Back to Home'),
),
),
);
}
}
Step 2: Create RouterDelegate
class MyRouterDelegate extends RouterDelegate<RouteSettings>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
final GlobalKey<NavigatorState> navigatorKey;
List<Page> _pages = [];
MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
_pages.add(MaterialPage(child: HomePage(), key: ValueKey('Home')));
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: List.of(_pages),
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
_pages.removeLast();
notifyListeners();
return true;
},
);
}
void pushPage(String path) {
if (path == '/details') {
_pages.add(MaterialPage(child: DetailsPage(), key: ValueKey('Details')));
} else if (path == '/settings') {
_pages.add(MaterialPage(child: SettingsPage(), key: ValueKey('Settings')));
}
notifyListeners();
}
@override
Future<void> setNewRoutePath(RouteSettings configuration) async {
// Handle deep linking
}
}
Step 3: Create RouteInformationParser
class MyRouteInformationParser extends RouteInformationParser<RouteSettings> {
@override
Future<RouteSettings> parseRouteInformation(RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location!);
return RouteSettings(name: uri.path);
}
}
Step 4: Integrate in MaterialApp
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final MyRouterDelegate _routerDelegate = MyRouterDelegate();
final MyRouteInformationParser _routeInformationParser = MyRouteInformationParser();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
}
}
Benefits of Navigator 2.0
- Declarative Navigation: Better integration with state management tools like Provider and Riverpod.
- Deep Linking Support: Seamlessly handle URL-based navigation.
- Improved Flexibility: Customizable routing and stack management.
Tips for Working with Navigator 2.0
- Use meaningful keys for your pages to avoid rebuild issues.
- Leverage
RouteInformationParserfor deep linking to provide a better user experience. - Combine Navigator 2.0 with state management tools for scalability.
Conclusion
Navigator 2.0 in Flutter is a powerful tool for managing app navigation, offering more flexibility and control compared to its predecessor. While it has a steeper learning curve, its benefits make it an essential skill for Flutter developers building complex applications.
With the examples and tips shared in this post, you are now equipped to start integrating Navigator 2.0 into your Flutter projects. Have questions or insights? Drop them in the comments below!