Skip to content

Instantly share code, notes, and snippets.

@justinmc
Last active August 7, 2024 21:34
Show Gist options
  • Save justinmc/6bdccf18ec0329fcbe5629f104956113 to your computer and use it in GitHub Desktop.
Save justinmc/6bdccf18ec0329fcbe5629f104956113 to your computer and use it in GitHub Desktop.
Example of predictive back (root and transitions) working with Navigator 2.0
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() async {
runApp(_MyApp());
}
class _MyApp extends StatelessWidget {
_MyApp();
final _MyRouteInformationParser _routeInformationParser = _MyRouteInformationParser();
final _MyRouterDelegate _routerDelegate = _MyRouterDelegate();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
theme: ThemeData(
useMaterial3: true,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
},
),
),
title: 'MaterialApp.router Example',
routeInformationParser: _routeInformationParser,
routerDelegate: _routerDelegate,
);
}
}
class _MyRouteInformationParser extends RouteInformationParser<_MyPageConfiguration> {
@override
SynchronousFuture<_MyPageConfiguration> parseRouteInformation(RouteInformation routeInformation) {
return SynchronousFuture(_MyPageConfiguration.values.firstWhere((_MyPageConfiguration pageConfiguration) {
return pageConfiguration.uriString == routeInformation.uri.toString();
},
orElse: () => _MyPageConfiguration.unknown,
));
}
@override
RouteInformation? restoreRouteInformation(_MyPageConfiguration configuration) {
return RouteInformation(uri: configuration.uri);
}
}
class _MyRouterDelegate extends RouterDelegate<_MyPageConfiguration> {
final Set<VoidCallback> _listeners = <VoidCallback>{};
final List<_MyPageConfiguration> _pages = <_MyPageConfiguration>[];
void _notifyListeners() {
for (VoidCallback listener in _listeners) {
listener();
}
}
void _onNavigateToLeaf() {
assert(!_pages.contains(_MyPageConfiguration.leaf), 'Should not ever be two leaf pages on the navigation stack.');
_pages.add(_MyPageConfiguration.leaf);
_notifyListeners();
}
void _onNavigateToHome() {
_pages.clear();
_pages.add(_MyPageConfiguration.home);
_notifyListeners();
}
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
@override
Future<bool> popRoute() {
if (_pages.isEmpty) {
return SynchronousFuture(false);
}
_pages.removeLast();
_notifyListeners();
return SynchronousFuture(true);
}
@override
Future<void> setNewRoutePath(_MyPageConfiguration configuration) {
_pages.add(configuration);
_notifyListeners();
return SynchronousFuture(null);
}
@override
Widget build(BuildContext context) {
return Navigator(
restorationScopeId: 'root',
onDidRemovePage: (Page page) {
_pages.remove(_MyPageConfiguration.fromName(page.name!));
},
pages: _pages.map((_MyPageConfiguration page) => switch (page) {
_MyPageConfiguration.unknown => _MyUnknownPage(),
_MyPageConfiguration.home => _MyHomePage(
onNavigateToLeaf: _onNavigateToLeaf,
),
_MyPageConfiguration.leaf => _MyLeafPage(
onNavigateToHome: _onNavigateToHome,
),
}).toList(),
);
}
}
class _MyUnknownPage extends MaterialPage {
_MyUnknownPage() : super(
key: const ValueKey('_MyUnknownPage'),
restorationId: 'unknown-page',
child: Scaffold(
appBar: AppBar(title: const Text('404')),
body: const Center(
child: Text('404'),
),
),
);
@override
String get name => _MyPageConfiguration.unknown.name;
}
class _MyHomePage extends MaterialPage {
_MyHomePage({
required VoidCallback onNavigateToLeaf,
}) : super(
key: const ValueKey('_MyHomePage'),
restorationId: 'home-page',
child: Scaffold(
appBar: AppBar(title: const Text('Home Page')),
body: Center(
child: ElevatedButton(
onPressed: onNavigateToLeaf,
child: const Text('Go to leaf page'),
),
),
),
);
@override
String get name => _MyPageConfiguration.home.name;
}
class _MyLeafPage extends MaterialPage {
_MyLeafPage({
required VoidCallback onNavigateToHome,
}) : super(
key: const ValueKey('_MyLeafPage'),
restorationId: 'leaf-page',
child: Scaffold(
appBar: AppBar(title: const Text('Leaf Page')),
body: Center(
child: ElevatedButton(
// TODO(justinmc): As far as I can tell, it's not possible/practical
// to do Navigator.pushNamed etc. here. That requires me to pass
// Navigator.onGenerateRoute, which seems to create routes in parallel
// to Navigator.pages, leading to confusing behavior.
onPressed: onNavigateToHome,
child: const Text('Go home'),
),
),
),
);
@override
String get name => _MyPageConfiguration.leaf.name;
}
enum _MyPageConfiguration {
home(uriString: '/'),
leaf(uriString: '/leaf'),
unknown(uriString: '/404');
const _MyPageConfiguration({
required this.uriString,
});
final String uriString;
static _MyPageConfiguration fromName(String testName) {
return values.firstWhere((_MyPageConfiguration page) => page.name == testName);
}
Uri get uri => Uri.parse(uriString);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment