I am currently trying to build a web app with the new Flutter web beta. The thing is to be able to have a history, handle the forward and back buttons in the browser and be able to handle user inputs into the URL the new Navigator 2.0 API is required (at least from what I understood).
There are only a few resources available currently, based on which I am trying to build my Navigator. The resources I used:
- (My Main Source)'Learning Flutter’s new navigation and routing system' by John Ryan
- A similar project (by orestesgaolin) but with a page manager and more complex
- The publicly shared document by flutter about the new Navigator
I managed to get the Back and forward button, as well as the history working. However I am struggling to be able to handle the page switches (in the Navigator()). In the example from John he manages the different sites in the 'page: ' array of the Navigator Widget (in the routeDelegater). It seemed strange to me but I tried it like this and it did not really work (Code further down).
In the 'page: []' I first tried to have booleans that trigger (like the show404) but that wasn't very clean so my next try was to get the current page as follows (in the array):
if(_currentPage.name == 'pages[0]') pageBuilds[0]
This kinda worked, as I was then able to type in the segment /matchit/0 and the correct page loaded, however for some reason the '/' route didn't work anymore and I got the error
Navigator.onGenerateRoute was null, but the route named "/" was referenced
I then tried to use the 'ongenerateRoute' but that just threw a bunch of errors. I am kinda new so maybe I did something wrong there. But it seemed to me like this was not the correct approach. And this is where I am currently stuck at. I don't know what to try next and am hoping that some of you guys can help me out.
My Route-Delegater looks as follows (I will include my comments in the code aswell, maybe it helps some of you, who are also looking to understand the Navigator 2.0, understand what is going on):
/**
* The RouteDelegate defines application specific behavious of how the router
* learns about changes in the application state and how it responds to them.
* It listens to the RouteInformation Parser and the app state and builds the Navigator with
* the current list of pages (immutable object used to set navigator's history stack).
*/
//ChangeNotifier for the listeners as they are handled there
//The popRoute is handled by 'PopNavigatorRouterDelegateMixin'
class RoutesDelegater extends RouterDelegate<RoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<RoutePath> {
//This is the state of the navigator widget (in build method)
GlobalKey<NavigatorState> get navigatorKey => GlobalKey<NavigatorState>();
//RoutesDelegater() : navigatorKey = GlobalKey<NavigatorState>();
MyPage _currentPage;
bool show404 = false; //checks if we show the 404 page
List<MyPage> pages = [
MyPage('ProjektListe'),
MyPage('StudiListe'),
MyPage('PRView'),
];
List<Page> pageBuilds = [
MaterialPage(key: ValueKey('Unknown'), child: UnknownScreen()),
MaterialPage(key: ValueKey('Homepage'), child: MyFirstHomepage()),
MaterialPage(key: ValueKey('OtherScreen'), child: OtherScreen()),
];
//currentConfiguration detects changes in the route information
//It helps complete the browser history and (IMPORTANT) makes the browser back and forward buttons work
RoutePath get currentConfiguration {
if (show404) {
return RoutePath.unknown();
}
if (_currentPage == null) return RoutePath.home();
//if not 404 or homepage it is some other page
return RoutePath.details(pages.indexOf(_currentPage));
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: //List.of(pageBuilds),
[
//pageBuilds[1],
if (show404)
pageBuilds[0]
else if (_currentPage != null)
pageBuilds[1]
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
_currentPage = null;
show404 = false;
//we are using Changenotifier
notifyListeners();
return true;
});
}
void _handleTapped(MyPage page) {
_currentPage = page;
notifyListeners();
}
@override
Future<void> setNewRoutePath(RoutePath path) async {
//Handle the unknown path
if (path.isUnknown) {
show404 = true;
_currentPage = null;
return;
}
if (path.isDetailPage) {
//Check if Path id is valid
if (path.id.isNegative || path.id > pages.length - 1) {
show404 = true;
return;
}
_currentPage = pages[path.id];
} else {
//homepage will be shown
_currentPage = null;
}
show404 = false;
}
}
My RoutingInformationParser looks like this:
/*
* The RouteInformationParser takes the RouteInformation from RouteInformationProvider and
* parses it into a user-defined data type.
*/
class MyRoutesInformationParser extends RouteInformationParser<RoutePath> {
@override
Future<RoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
//routeInformation is an object we get from the uri
final uri = Uri.parse(routeInformation.location);
// Handle '/' (Home Path)
//Path segments are the segments seperated by /, if we don't have any we are on Home
if (uri.pathSegments.length == 0) {
return RoutePath.home();
}
//We have 2, as we have matchit/...
if (uri.pathSegments.length == 2) {
//If there is no 'matchit' in the first path segment the path is unknown
if (uri.pathSegments.first != 'matchit') return RoutePath.unknown();
//If we now have the correct first segment we can now handle the rest of the segment
final remaining = uri.pathSegments.elementAt(1);
final id = int.tryParse(remaining);
//if it fails we return the unknown path
if (id == null) return RoutePath.unknown();
return RoutePath.details(id);
}
//Handling the unknown Path, e.g. user just typed anything in uri
return RoutePath.unknown();
}
//THIS IS IMPORTANT: Here we restore the web history
@override
RouteInformation restoreRouteInformation(RoutePath path) {
//Here we set the routeInformation which is used above, e.g. /404 for unknown page
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
//Any other page is handled here via /matchit/... <= the id of the path
if (path.isDetailPage) {
return RouteInformation(location: '/matchit/${path.id}');
}
//If none of the paths are hit
return null;
}
}
Then we also have my Data-type for the routing information:
class RoutePath{
final int id;
final bool isUnknown;
RoutePath.home()
: id = null,
isUnknown = false;
//Details means here that it is any other side than Home or unknown
RoutePath.details(this.id) : isUnknown = false;
RoutePath.unknown()
: id = null,
isUnknown = true;
//check if is on HomePage or other page, then either == null or != null
//not needed for isInknown, as when unknown then = true as set above
bool get isHomePage => id == null;
bool get isDetailPage => id != null;
}
An lastly my Homepage() where the InformationParser and Delegater are initialized:
class Homepage extends StatefulWidget {
@override
State<StatefulWidget> createState() => _HomepageState();
}
class _HomepageState extends State<Homepage> {
//initialize the RouteDelegater and Information Parser to be unsed further down
RoutesDelegater _routesDelegater = RoutesDelegater();
MyRoutesInformationParser _myRoutesInformationParser =
MyRoutesInformationParser();
/*
* Relevant routing information for this build method:
* We need to use the MaterialApp.router else we can't use routerDelegate and routeInformationParser.
* Then we define the delegate and Information Parser (they are initiated above)
*/
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'MatchIT',
routerDelegate: _routesDelegater,
routeInformationParser: _myRoutesInformationParser,
theme: ThemeData(primarySwatch: Colors.blue),
debugShowCheckedModeBanner: false);
}
}
Thank you in advance!
I actually managed to solve it by adding another function that is called in my Navigator() in the RouteDelegator under pages[]:
I also changed the names in my Lists above (just so you guys can understand the code):