// Copyright 2020, the Flutter project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. /// Full sample that shows a custom RouteInformationParser and RouterDelegate /// parsing named routes and declaratively building the stack of pages for the /// [Navigator]. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; void main() { runApp(const BooksApp()); } class Book { final String title; final String author; Book(this.title, this.author); } class BooksApp extends StatefulWidget { const BooksApp({Key? key}) : super(key: key); @override State createState() => _BooksAppState(); } class _BooksAppState extends State { final BookRouterDelegate _routerDelegate = BookRouterDelegate(); final BookRouteInformationParser _routeInformationParser = BookRouteInformationParser(); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Books App', routerDelegate: _routerDelegate, routeInformationParser: _routeInformationParser, ); } } class BookRouteInformationParser extends RouteInformationParser { @override Future parseRouteInformation( RouteInformation routeInformation, ) { final uri = Uri.parse(routeInformation.location ?? ''); // Handle '/' if (uri.pathSegments.isEmpty) { return SynchronousFuture(BookRoutePath.home()); } // Handle '/book/:id' if (uri.pathSegments.length == 2 && uri.pathSegments[0] == 'book') { var remaining = uri.pathSegments[1]; var id = int.tryParse(remaining); if (id != null) { return SynchronousFuture(BookRoutePath.details(id)); } } // Handle unknown routes return SynchronousFuture(BookRoutePath.unknown()); } @override RouteInformation? restoreRouteInformation(BookRoutePath configuration) { if (configuration.isUnknown) { return const RouteInformation(location: '/404'); } if (configuration.isHomePage) { return const RouteInformation(location: '/'); } if (configuration.isDetailsPage) { return RouteInformation(location: '/book/${configuration.id}'); } return null; } } class BookRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { @override final GlobalKey navigatorKey; Book? _selectedBook; bool show404 = false; final List books = [ Book('Left Hand of Darkness', 'Ursula K. Le Guin'), Book('Too Like the Lightning', 'Ada Palmer'), Book('Kindred', 'Octavia E. Butler'), ]; BookRouterDelegate() : navigatorKey = GlobalKey(); @override BookRoutePath get currentConfiguration { if (show404) { return BookRoutePath.unknown(); } return _selectedBook == null ? BookRoutePath.home() : BookRoutePath.details(books.indexOf(_selectedBook!)); } @override Widget build(BuildContext context) { return Navigator( key: navigatorKey, pages: [ MaterialPage( key: const ValueKey('BooksListPage'), child: BooksListScreen( books: books, onTapped: _handleBookTapped, ), ), if (show404) const MaterialPage( key: ValueKey('UnknownPage'), child: UnknownScreen(), ) else if (_selectedBook != null) BookDetailsPage(book: _selectedBook!) ], onPopPage: (route, dynamic result) { if (!route.didPop(result)) { return false; } // Update the list of pages by setting _selectedBook to null _selectedBook = null; show404 = false; notifyListeners(); return true; }, ); } @override Future setNewRoutePath(BookRoutePath configuration) { if (configuration.isUnknown) { _selectedBook = null; show404 = true; return SynchronousFuture(null); } if (configuration.isDetailsPage) { if (configuration.id! < 0 || configuration.id! > books.length - 1) { show404 = true; return SynchronousFuture(null); } _selectedBook = books[configuration.id!]; } else { _selectedBook = null; } show404 = false; return SynchronousFuture(null); } void _handleBookTapped(Book book) { _selectedBook = book; notifyListeners(); } } class BookDetailsPage extends Page { final Book book; BookDetailsPage({ required this.book, }) : super(key: ValueKey(book)); @override Route createRoute(BuildContext context) { return MaterialPageRoute( settings: this, builder: (context) { return BookDetailsScreen(book: book); }, ); } } class BookRoutePath { final int? id; final bool isUnknown; BookRoutePath.home() : id = null, isUnknown = false; BookRoutePath.details(this.id) : isUnknown = false; BookRoutePath.unknown() : id = null, isUnknown = true; bool get isHomePage => id == null; bool get isDetailsPage => id != null; } class BooksListScreen extends StatelessWidget { final List books; final ValueChanged onTapped; const BooksListScreen({ required this.books, required this.onTapped, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: ListView( children: [ for (var book in books) ListTile( title: Text(book.title), subtitle: Text(book.author), onTap: () => onTapped(book), ) ], ), ); } } class BookDetailsScreen extends StatelessWidget { final Book book; const BookDetailsScreen({ required this.book, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(book.title, style: Theme.of(context).textTheme.headline6), Text(book.author, style: Theme.of(context).textTheme.subtitle1), ], ), ), ); } } class UnknownScreen extends StatelessWidget { const UnknownScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: const Center( child: Text('404!'), ), ); } }