// 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/material.dart'; void main() { runApp(BooksApp()); } class Book { final String title; final String author; Book(this.title, this.author); } class BooksApp extends StatefulWidget { @override State createState() => _BooksAppState(); } class _BooksAppState extends State { BookRouterDelegate _routerDelegate = BookRouterDelegate(); 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) async { final uri = Uri.parse(routeInformation.location); // Handle '/' if (uri.pathSegments.length == 0) { return BookRoutePath.home(); } // Handle '/book/:id' if (uri.pathSegments.length == 2) { if (uri.pathSegments[0] != 'book') return BookRoutePath.unknown(); var remaining = uri.pathSegments[1]; var id = int.tryParse(remaining); if (id == null) return BookRoutePath.unknown(); return BookRoutePath.details(id); } // Handle unknown routes return BookRoutePath.unknown(); } @override RouteInformation restoreRouteInformation(BookRoutePath path) { if (path.isUnknown) { return RouteInformation(location: '/404'); } if (path.isHomePage) { return RouteInformation(location: '/'); } if (path.isDetailsPage) { return RouteInformation(location: '/book/${path.id}'); } return null; } } class BookRouterDelegate extends RouterDelegate with ChangeNotifier, PopNavigatorRouterDelegateMixin { final GlobalKey navigatorKey; Book _selectedBook; bool show404 = false; List books = [ Book('Stranger in a Strange Land', 'Robert A. Heinlein'), Book('Foundation', 'Isaac Asimov'), Book('Fahrenheit 451', 'Ray Bradbury'), ]; BookRouterDelegate() : navigatorKey = GlobalKey(); 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: ValueKey('BooksListPage'), child: BooksListScreen( books: books, onTapped: _handleBookTapped, ), ), if (show404) MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen()) else if (_selectedBook != null) BookDetailsPage(book: _selectedBook) ], onPopPage: (route, 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 path) async { if (path.isUnknown) { _selectedBook = null; show404 = true; return; } if (path.isDetailsPage) { if (path.id < 0 || path.id > books.length - 1) { show404 = true; return; } _selectedBook = books[path.id]; } else { _selectedBook = null; } show404 = false; } void _handleBookTapped(Book book) { _selectedBook = book; notifyListeners(); } } class BookDetailsPage extends Page { final Book book; BookDetailsPage({ this.book, }) : super(key: ValueKey(book)); Route createRoute(BuildContext context) { return MaterialPageRoute( settings: this, builder: (BuildContext 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; BooksListScreen({ @required this.books, @required this.onTapped, }); @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; BookDetailsScreen({ @required this.book, }); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Padding( padding: const EdgeInsets.all(8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (book != null) ...[ Text(book.title, style: Theme.of(context).textTheme.headline6), Text(book.author, style: Theme.of(context).textTheme.subtitle1), ], ], ), ), ); } } class UnknownScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Center( child: Text('404!'), ), ); } }