-
-
Save johnpryan/5ce79aee5b5f83cfababa97c9cf0a204 to your computer and use it in GitHub Desktop.
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<StatefulWidget> createState() => _BooksAppState(); | |
} | |
class _BooksAppState extends State<BooksApp> { | |
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<BookRoutePath> { | |
@override | |
Future<BookRoutePath> parseRouteInformation( | |
RouteInformation routeInformation) async { | |
final uri = Uri.parse(routeInformation.location); | |
if (uri.pathSegments.length >= 2) { | |
var remaining = uri.pathSegments[1]; | |
return BookRoutePath.details(int.tryParse(remaining)); | |
} else { | |
return BookRoutePath.home(); | |
} | |
} | |
@override | |
RouteInformation restoreRouteInformation(BookRoutePath path) { | |
if (path.isHomePage) { | |
return RouteInformation(location: '/'); | |
} | |
if (path.isDetailsPage) { | |
return RouteInformation(location: '/book/${path.id}'); | |
} | |
return null; | |
} | |
} | |
class BookRouterDelegate extends RouterDelegate<BookRoutePath> | |
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> { | |
final GlobalKey<NavigatorState> navigatorKey; | |
Book _selectedBook; | |
List<Book> books = [ | |
Book('Stranger in a Strange Land', 'Robert A. Heinlein'), | |
Book('Foundation', 'Isaac Asimov'), | |
Book('Fahrenheit 451', 'Ray Bradbury'), | |
]; | |
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>(); | |
BookRoutePath get currentConfiguration => _selectedBook == null | |
? BookRoutePath.home() | |
: BookRoutePath.details(books.indexOf(_selectedBook)); | |
@override | |
Widget build(BuildContext context) { | |
return Navigator( | |
key: navigatorKey, | |
transitionDelegate: NoAnimationTransitionDelegate(), | |
pages: [ | |
MaterialPage( | |
key: ValueKey('BooksListPage'), | |
child: BooksListScreen( | |
books: books, | |
onTapped: _handleBookTapped, | |
), | |
), | |
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; | |
notifyListeners(); | |
return true; | |
}, | |
); | |
} | |
@override | |
Future<void> setNewRoutePath(BookRoutePath path) async { | |
if (path.isDetailsPage) { | |
_selectedBook = books[path.id]; | |
} | |
} | |
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; | |
BookRoutePath.home() : id = null; | |
BookRoutePath.details(this.id); | |
bool get isHomePage => id == null; | |
bool get isDetailsPage => id != null; | |
} | |
class BooksListScreen extends StatelessWidget { | |
final List<Book> books; | |
final ValueChanged<Book> 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 NoAnimationTransitionDelegate extends TransitionDelegate<void> { | |
@override | |
Iterable<RouteTransitionRecord> resolve({ | |
List<RouteTransitionRecord> newPageRouteHistory, | |
Map<RouteTransitionRecord, RouteTransitionRecord> | |
locationToExitingPageRoute, | |
Map<RouteTransitionRecord, List<RouteTransitionRecord>> | |
pageRouteToPagelessRoutes, | |
}) { | |
final results = <RouteTransitionRecord>[]; | |
for (final pageRoute in newPageRouteHistory) { | |
if (pageRoute.isWaitingForEnteringDecision) { | |
pageRoute.markForAdd(); | |
} | |
results.add(pageRoute); | |
} | |
for (final exitingPageRoute in locationToExitingPageRoute.values) { | |
if (exitingPageRoute.isWaitingForExitingDecision) { | |
exitingPageRoute.markForRemove(); | |
final pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute]; | |
if (pagelessRoutes != null) { | |
for (final pagelessRoute in pagelessRoutes) { | |
pagelessRoute.markForRemove(); | |
} | |
} | |
} | |
results.add(exitingPageRoute); | |
} | |
return results; | |
} | |
} |
Yeah you have to make it compliant with null safety:
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<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
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<BookRoutePath> {
@override
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location!);
if (uri.pathSegments.length >= 2) {
var remaining = uri.pathSegments[1];
return BookRoutePath.details(int.tryParse(remaining));
} else {
return BookRoutePath.home();
}
}
@override
RouteInformation? restoreRouteInformation(BookRoutePath configuration) {
if (configuration.isHomePage) {
return const RouteInformation(location: '/');
}
if (configuration.isDetailsPage) {
return RouteInformation(location: '/book/${configuration.id}');
}
return null;
}
}
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
final GlobalKey<NavigatorState> navigatorKey;
Book? _selectedBook;
List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
@override
BookRoutePath get currentConfiguration => _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 (_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;
notifyListeners();
return true;
},
);
}
@override
Future<void> setNewRoutePath(BookRoutePath configuration) async {
if (configuration.isDetailsPage) {
_selectedBook = books[configuration.id!];
}
}
void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}
}
class BookDetailsPage extends Page {
final Book? book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
@override
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) {
return BookDetailsScreen(book: book);
},
);
}
}
class BookRoutePath {
final int? id;
BookRoutePath.home() : id = null;
BookRoutePath.details(this.id);
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
}
class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
const BooksListScreen({Key? key,
required this.books,
required this.onTapped,
}) : 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({Key? key,
required this.book,
}) : 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: [
if (book != null) ...[
Text(book!.title, style: Theme.of(context).textTheme.headline6),
Text(book!.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
Hello, even though this thread is quite old, I hope that someone will help me with some questions / issues.
I am very happy that example is simple, however I see some issues with it mostly related to WEB implementation. I am trying to polish it to the level it behaves as consistent with native browser navigation as possible.
Example's main issue is with browser history: every time user presses back arrow it returns it to home screen, but instead of moving back in history, it just pushes new entry, so history is constantly growing (I belive that "back arrow" button executes Navigator.of(context).pop()
under the hood). Is that possible to make navigator's pop behavior the same as of browser's back button? I have solution for that:
html.window.history.go(-1);
but I am not sure it is the best one.
Second question is about page title (what we can see in tab and what we can see in browser history, like Application: Home for home or Application: Book Title for book details page).
I tried using Title widget, but I am not sure where I should put it in our example to make it work.
thanks a lot and looking forward some response from you.
Using this main.dart in a fresh flutter 2.2.3 project to kick the tyres and it doesn't run which isn't filling me with confidence.