Last active
August 7, 2024 21:34
-
-
Save justinmc/6bdccf18ec0329fcbe5629f104956113 to your computer and use it in GitHub Desktop.
Example of predictive back (root and transitions) working with Navigator 2.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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