Cleanup to navigation_and_routing sample (#881)

- make more things private and final, where possible
- remove unused members
- used expression bodies, where possible
pull/886/head
Kevin Moore 4 years ago committed by GitHub
parent 410e43fbc1
commit fa7dec1475
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -18,12 +18,11 @@ class Bookstore extends StatefulWidget {
}
class _BookstoreState extends State<Bookstore> {
final auth = BookstoreAuth();
late final BookstoreRouteGuard guard;
late final RouteState routeState;
late final SimpleRouterDelegate routerDelegate;
late final TemplateRouteParser routeParser;
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final _auth = BookstoreAuth();
final _navigatorKey = GlobalKey<NavigatorState>();
late final RouteState _routeState;
late final SimpleRouterDelegate _routerDelegate;
late final TemplateRouteParser _routeParser;
final library = Library()
..addBook(
@ -49,10 +48,10 @@ class _BookstoreState extends State<Bookstore> {
@override
void initState() {
guard = BookstoreRouteGuard(auth: auth);
final guard = BookstoreRouteGuard(auth: _auth);
/// Configure the parser with all of the app's allowed path templates.
routeParser = TemplateRouteParser(
_routeParser = TemplateRouteParser(
allowedPaths: [
'/signin',
'/authors',
@ -67,50 +66,48 @@ class _BookstoreState extends State<Bookstore> {
initialRoute: '/signin',
);
routeState = RouteState(routeParser);
_routeState = RouteState(_routeParser);
routerDelegate = SimpleRouterDelegate(
routeState: routeState,
navigatorKey: navigatorKey,
_routerDelegate = SimpleRouterDelegate(
routeState: _routeState,
navigatorKey: _navigatorKey,
builder: (context) => BookstoreNavigator(
navigatorKey: navigatorKey,
navigatorKey: _navigatorKey,
),
);
// Listen for when the user logs out and display the signin screen.
auth.addListener(_handleAuthStateChanged);
_auth.addListener(_handleAuthStateChanged);
super.initState();
}
@override
Widget build(BuildContext context) {
return RouteStateScope(
notifier: routeState,
child: BookstoreAuthScope(
notifier: auth,
child: LibraryScope(
library: library,
child: MaterialApp.router(
routerDelegate: routerDelegate,
routeInformationParser: routeParser,
Widget build(BuildContext context) => RouteStateScope(
notifier: _routeState,
child: BookstoreAuthScope(
notifier: _auth,
child: LibraryScope(
library: library,
child: MaterialApp.router(
routerDelegate: _routerDelegate,
routeInformationParser: _routeParser,
),
),
),
),
);
}
);
void _handleAuthStateChanged() {
if (!auth.signedIn) {
routeState.go('/signin');
if (!_auth.signedIn) {
_routeState.go('/signin');
}
}
@override
void dispose() {
auth.removeListener(_handleAuthStateChanged);
routeState.dispose();
routerDelegate.dispose();
_auth.removeListener(_handleAuthStateChanged);
_routeState.dispose();
_routerDelegate.dispose();
super.dispose();
}
}

@ -9,9 +9,7 @@ import 'package:flutter/widgets.dart';
class BookstoreAuth extends ChangeNotifier {
bool _signedIn = false;
bool get signedIn {
return _signedIn;
}
bool get signedIn => _signedIn;
Future<void> signOut() async {
await Future<void>.delayed(const Duration(milliseconds: 200));
@ -30,9 +28,8 @@ class BookstoreAuth extends ChangeNotifier {
}
@override
bool operator ==(Object other) {
return other is BookstoreAuth && other._signedIn == _signedIn;
}
bool operator ==(Object other) =>
other is BookstoreAuth && other._signedIn == _signedIn;
@override
int get hashCode => _signedIn.hashCode;
@ -45,9 +42,7 @@ class BookstoreAuthScope extends InheritedNotifier<BookstoreAuth> {
Key? key,
}) : super(key: key, notifier: notifier, child: child);
static BookstoreAuth? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()
?.notifier;
}
static BookstoreAuth? of(BuildContext context) => context
.dependOnInheritedWidgetOfExactType<BookstoreAuthScope>()
?.notifier;
}

@ -7,7 +7,7 @@ import 'auth.dart';
/// An implementation of [RouteGuard] that redirects to /signIn
class BookstoreRouteGuard implements RouteGuard<ParsedRoute> {
BookstoreAuth auth;
final BookstoreAuth auth;
BookstoreRouteGuard({
required this.auth,

@ -32,15 +32,11 @@ class Library {
allBooks.add(book);
}
List<Book> get popularBooks {
return [
...allBooks.where((book) => book.isPopular),
];
}
List<Book> get popularBooks => [
...allBooks.where((book) => book.isPopular),
];
List<Book> get newBooks {
return [
...allBooks.where((book) => book.isNew),
];
}
List<Book> get newBooks => [
...allBooks.where((book) => book.isNew),
];
}

@ -28,9 +28,7 @@ class SimpleRouterDelegate extends RouterDelegate<ParsedRoute>
}
@override
Widget build(BuildContext context) {
return builder(context);
}
Widget build(BuildContext context) => builder(context);
@override
Future<void> setNewRoutePath(ParsedRoute configuration) async {
@ -39,9 +37,7 @@ class SimpleRouterDelegate extends RouterDelegate<ParsedRoute>
}
@override
ParsedRoute get currentConfiguration {
return routeState.route;
}
ParsedRoute get currentConfiguration => routeState.route;
@override
void dispose() {

@ -27,13 +27,12 @@ class ParsedRoute {
this.path, this.pathTemplate, this.parameters, this.queryParameters);
@override
bool operator ==(Object other) {
return other is ParsedRoute &&
other.pathTemplate == pathTemplate &&
other.path == path &&
_mapEquality.equals(parameters, other.parameters) &&
_mapEquality.equals(queryParameters, other.queryParameters);
}
bool operator ==(Object other) =>
other is ParsedRoute &&
other.pathTemplate == pathTemplate &&
other.path == path &&
_mapEquality.equals(parameters, other.parameters) &&
_mapEquality.equals(queryParameters, other.queryParameters);
@override
int get hashCode => hash4(

@ -44,9 +44,8 @@ class TemplateRouteParser extends RouteInformationParser<ParsedRoute> {
@override
Future<ParsedRoute> parseRouteInformation(
RouteInformation routeInformation) async {
return await _parse(routeInformation);
}
RouteInformation routeInformation) async =>
await _parse(routeInformation);
Future<ParsedRoute> _parse(RouteInformation routeInformation) async {
final path = routeInformation.location!;
@ -74,7 +73,6 @@ class TemplateRouteParser extends RouteInformationParser<ParsedRoute> {
}
@override
RouteInformation restoreRouteInformation(ParsedRoute configuration) {
return RouteInformation(location: configuration.path);
}
RouteInformation restoreRouteInformation(ParsedRoute configuration) =>
RouteInformation(location: configuration.path);
}

@ -44,9 +44,6 @@ class RouteStateScope extends InheritedNotifier<RouteState> {
Key? key,
}) : super(key: key, notifier: notifier, child: child);
static RouteState? of(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<RouteStateScope>()
?.notifier;
}
static RouteState? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<RouteStateScope>()?.notifier;
}

@ -17,25 +17,23 @@ class AuthorDetailsScreen extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(author.name),
),
body: Center(
child: Column(
children: [
Expanded(
child: BookList(
books: author.books,
onTap: (book) {
RouteStateScope.of(context)!.go('/book/${book.id}');
},
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(author.name),
),
body: Center(
child: Column(
children: [
Expanded(
child: BookList(
books: author.books,
onTap: (book) {
RouteStateScope.of(context)!.go('/book/${book.id}');
},
),
),
),
],
],
),
),
),
);
}
);
}

@ -14,17 +14,15 @@ class AuthorsScreen extends StatelessWidget {
const AuthorsScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: AuthorList(
authors: LibraryScope.of(context).allAuthors,
onTap: (author) {
RouteStateScope.of(context)!.go('/author/${author.id}');
},
),
);
}
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(title),
),
body: AuthorList(
authors: LibraryScope.of(context).allAuthors,
onTap: (author) {
RouteStateScope.of(context)!.go('/author/${author.id}');
},
),
);
}

@ -45,21 +45,18 @@ class BookDetailsScreen extends StatelessWidget {
onPressed: () {
Navigator.of(context).push<void>(
MaterialPageRoute<void>(
builder: (context) {
return AuthorDetailsScreen(author: book!.author);
},
builder: (context) =>
AuthorDetailsScreen(author: book!.author),
),
);
},
),
Link(
uri: Uri.parse('/author/${book!.author.id}'),
builder: (context, followLink) {
return TextButton(
onPressed: followLink,
child: const Text('View author (Link)'),
);
},
builder: (context, followLink) => TextButton(
onPressed: followLink,
child: const Text('View author (Link)'),
),
),
],
),

@ -10,11 +10,8 @@ import '../widgets/book_list.dart';
import '../widgets/library_scope.dart';
class BooksScreen extends StatefulWidget {
final ParsedRoute currentRoute;
const BooksScreen({
Key? key,
required this.currentRoute,
}) : super(key: key);
@override
@ -36,7 +33,7 @@ class _BooksScreenState extends State<BooksScreen>
void didChangeDependencies() {
super.didChangeDependencies();
final newPath = routeState.route.pathTemplate;
final newPath = _routeState.route.pathTemplate;
if (newPath.startsWith('/books/popular')) {
_tabController.index = 0;
} else if (newPath.startsWith('/books/new')) {
@ -96,35 +93,23 @@ class _BooksScreenState extends State<BooksScreen>
);
}
String get title {
switch (_tabController.index) {
case 1:
return 'New';
case 2:
return 'All';
case 0:
default:
return 'Popular';
}
}
RouteState get routeState => RouteStateScope.of(context)!;
RouteState get _routeState => RouteStateScope.of(context)!;
void _handleBookTapped(Book book) {
routeState.go('/book/${book.id}');
_routeState.go('/book/${book.id}');
}
void _handleTabIndexChanged() {
switch (_tabController.index) {
case 1:
routeState.go('/books/new');
_routeState.go('/books/new');
break;
case 2:
routeState.go('/books/all');
_routeState.go('/books/all');
break;
case 0:
default:
routeState.go('/books/popular');
_routeState.go('/books/popular');
break;
}
}

@ -30,10 +30,10 @@ class BookstoreNavigator extends StatefulWidget {
}
class _BookstoreNavigatorState extends State<BookstoreNavigator> {
final signInKey = const ValueKey('Sign in');
final scaffoldKey = const ValueKey<String>('App scaffold');
final bookDetailsKey = const ValueKey<String>('Book details screen');
final authorDetailsKey = const ValueKey<String>('Author details screen');
final _signInKey = const ValueKey('Sign in');
final _scaffoldKey = const ValueKey<String>('App scaffold');
final _bookDetailsKey = const ValueKey<String>('Book details screen');
final _authorDetailsKey = const ValueKey<String>('Author details screen');
@override
Widget build(BuildContext context) {
@ -60,12 +60,12 @@ class _BookstoreNavigatorState extends State<BookstoreNavigator> {
// When a page that is stacked on top of the scaffold is popped, display
// the /books or /authors tab in BookstoreScaffold.
if (route.settings is Page &&
(route.settings as Page).key == bookDetailsKey) {
(route.settings as Page).key == _bookDetailsKey) {
routeState.go('/books/popular');
}
if (route.settings is Page &&
(route.settings as Page).key == authorDetailsKey) {
(route.settings as Page).key == _authorDetailsKey) {
routeState.go('/authors');
}
@ -75,7 +75,7 @@ class _BookstoreNavigatorState extends State<BookstoreNavigator> {
if (routeState.route.pathTemplate == '/signin')
// Display the sign in screen.
FadeTransitionPage<void>(
key: signInKey,
key: _signInKey,
child: SignInScreen(
onSignIn: (credentials) async {
var signedIn = await authState.signIn(
@ -89,21 +89,21 @@ class _BookstoreNavigatorState extends State<BookstoreNavigator> {
else ...[
// Display the app
FadeTransitionPage<void>(
key: scaffoldKey,
key: _scaffoldKey,
child: const BookstoreScaffold(),
),
// Add an additional page to the stack if the user is viewing a book
// or an author
if (selectedBook != null)
MaterialPage<void>(
key: bookDetailsKey,
key: _bookDetailsKey,
child: BookDetailsScreen(
book: selectedBook,
),
)
else if (selectedAuthor != null)
MaterialPage<void>(
key: authorDetailsKey,
key: _authorDetailsKey,
child: AuthorDetailsScreen(
author: selectedAuthor,
),

@ -41,9 +41,9 @@ class BookstoreScaffoldBody extends StatelessWidget {
)
else if (currentRoute.pathTemplate.startsWith('/books') ||
currentRoute.pathTemplate == '/')
FadeTransitionPage<void>(
key: const ValueKey('books'),
child: BooksScreen(currentRoute: currentRoute),
const FadeTransitionPage<void>(
key: ValueKey('books'),
child: BooksScreen(),
)
// Avoid building a Navigator with an empty `pages` list when the

@ -17,26 +17,24 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: const Card(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 12),
child: SettingsContent(),
Widget build(BuildContext context) => Scaffold(
body: SafeArea(
child: SingleChildScrollView(
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 400),
child: const Card(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 18, horizontal: 12),
child: SettingsContent(),
),
),
),
),
),
),
),
);
}
);
}
class SettingsContent extends StatelessWidget {
@ -45,57 +43,53 @@ class SettingsContent extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
children: [
...[
Text(
'Settings',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: () {
BookstoreAuthScope.of(context)!.signOut();
},
child: const Text('Sign out'),
),
Link(
uri: Uri.parse('/book/0'),
builder: (context, followLink) {
return TextButton(
Widget build(BuildContext context) => Column(
children: [
...[
Text(
'Settings',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
onPressed: () {
BookstoreAuthScope.of(context)!.signOut();
},
child: const Text('Sign out'),
),
Link(
uri: Uri.parse('/book/0'),
builder: (context, followLink) => TextButton(
onPressed: followLink,
child: const Text('Go directly to /book/0 (Link)'),
);
},
),
),
),
TextButton(
child: const Text('Go directly to /book/0 (RouteState)'),
onPressed: () {
RouteStateScope.of(context)!.go('/book/0');
},
),
].map((w) => Padding(padding: const EdgeInsets.all(8), child: w)),
TextButton(
child: const Text('Go directly to /book/0 (RouteState)'),
onPressed: () {
RouteStateScope.of(context)!.go('/book/0');
},
),
].map((w) => Padding(padding: const EdgeInsets.all(8), child: w)),
TextButton(
onPressed: () => showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Alert!'),
content: const Text('The alert description goes here.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
onPressed: () => showDialog<String>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Alert!'),
content: const Text('The alert description goes here.'),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
),
),
child: const Text('Show Dialog'),
)
],
);
}
child: const Text('Show Dialog'),
)
],
);
}

@ -29,43 +29,41 @@ class _SignInScreenState extends State<SignInScreen> {
final _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Card(
child: Container(
constraints: BoxConstraints.loose(const Size(600, 600)),
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text('Sign in', style: Theme.of(context).textTheme.headline4),
TextField(
decoration: const InputDecoration(labelText: 'Username'),
controller: _usernameController,
),
TextField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
),
Padding(
padding: const EdgeInsets.all(16),
child: TextButton(
onPressed: () async {
widget.onSignIn(Credentials(
_usernameController.value.text,
_passwordController.value.text));
},
child: const Text('Sign in'),
Widget build(BuildContext context) => Scaffold(
body: Center(
child: Card(
child: Container(
constraints: BoxConstraints.loose(const Size(600, 600)),
padding: const EdgeInsets.all(8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text('Sign in', style: Theme.of(context).textTheme.headline4),
TextField(
decoration: const InputDecoration(labelText: 'Username'),
controller: _usernameController,
),
),
],
TextField(
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
),
Padding(
padding: const EdgeInsets.all(16),
child: TextButton(
onPressed: () async {
widget.onSignIn(Credentials(
_usernameController.value.text,
_passwordController.value.text));
},
child: const Text('Sign in'),
),
),
],
),
),
),
),
),
);
}
);
}

@ -17,11 +17,9 @@ class AuthorList extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: authors.length,
itemBuilder: (context, index) {
return ListTile(
Widget build(BuildContext context) => ListView.builder(
itemCount: authors.length,
itemBuilder: (context, index) => ListTile(
title: Text(
authors[index].name,
),
@ -29,8 +27,6 @@ class AuthorList extends StatelessWidget {
'${authors[index].books.length} books',
),
onTap: onTap != null ? () => onTap!(authors[index]) : null,
);
},
);
}
),
);
}

@ -17,11 +17,9 @@ class BookList extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
return ListTile(
Widget build(BuildContext context) => ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) => ListTile(
title: Text(
books[index].title,
),
@ -29,8 +27,6 @@ class BookList extends StatelessWidget {
books[index].author.name,
),
onTap: onTap != null ? () => onTap!(books[index]) : null,
);
},
);
}
),
);
}

@ -15,15 +15,14 @@ class FadeTransitionPage<T> extends Page<T> {
}) : super(key: key);
@override
Route<T> createRoute(BuildContext context) {
return PageBasedFadeTransitionRoute<T>(this);
}
Route<T> createRoute(BuildContext context) =>
PageBasedFadeTransitionRoute<T>(this);
}
class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
final FadeTransitionPage<T> page;
final FadeTransitionPage<T> _page;
PageBasedFadeTransitionRoute(this.page) : super(settings: page);
PageBasedFadeTransitionRoute(this._page) : super(settings: _page);
@override
Color? get barrierColor => null;
@ -32,7 +31,7 @@ class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
String? get barrierLabel => null;
@override
Duration get transitionDuration => page.duration;
Duration get transitionDuration => _page.duration;
@override
bool get maintainState => true;
@ -49,7 +48,6 @@ class PageBasedFadeTransitionRoute<T> extends PageRoute<T> {
@override
Widget buildTransitions(BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return child;
}
Animation<double> secondaryAnimation, Widget child) =>
child;
}

@ -19,7 +19,6 @@ class LibraryScope extends InheritedWidget {
bool updateShouldNotify(LibraryScope oldWidget) =>
library != oldWidget.library;
static Library of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<LibraryScope>()!.library;
}
static Library of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<LibraryScope>()!.library;
}

Loading…
Cancel
Save