GgRouter
is a simple and powerful routing package for Flutter. Just define
your nested routes. Add query parameters. Define the route transitions. GgRouter
will do the rest for you:
Additionally, GgRouter allows you to create index routes, default routes,
wildcard routes, assign semantic labels to routes. With GgNavigationPage
a page
based hierarchical navigation system is provided. And finally, it can backup
and restore the complete route tree as JSON.
Klick here to watch a YouTube demo of GgRouter.
To initialize GgRouter
, create a MaterialApp.router(...)
instance
and provide it with an instance of GgRouterDelegate
and
GgRouterInformationParser
.
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: GgRouterDelegate(
child: Scaffold(
body: GgRouterExample(),
),
),
routeInformationParser: GgRouteInformationParser(),
);
}
}
Use the GgRouter
widget to add routes to your application structure:
@override
Widget build(BuildContext context){
return GgRouter(
{
'sports': _sports,
'transportation': _transportation,
'places': _places
}
);
}
Each of these routes will replace its siblings when being selected.
To show a route in front of existing content, create a popover route:
GgPopoverRoute(
// ...
name: 'popover', // The route which will open the popover
base: _myWidget, // The regular content
popover: _myDialog, // The popover
inAnimation: _rotateIn, // The appearing animation
outAnimation: _rotateOut, // The disappearing animation
),
You can arbitrarily nest these routes. Just place another GgRouter
widget
within one of the routes. Child GgRouter
widgets do not need to be direct
children.
To define a default route which is shown when none of the routes is selected,
add a route with name '_INDEX_'
:
GgRouter(
{
'_INDEX_': _index,
'sports': _sports,
// ...
}
);
Chose a default route when no _INDEX
route is defined by using the
defaultRoute
parameter.
GgRouter(
{
'sports': _sports,
// ...
},
defaultRoute: 'sports'
);
If you want to handle arbitrary route names, e.g., parsing an ID from the URI,
you can setup a wild card route using *
as route name:
return GgRouter(
{
// ...
'*': _wildCardPage,
},
/// ...
);
To get the name of the actual route, use GgRouter.of(context).routeName
:
Widget _wildCardPage(BuildContext context) {
final routeName = GgRouter.of(context).routeName;
// ... do something with the routeName
}
Use GgRouter.of(context).navigateTo('/sports/football')
to absolutely navigate
to the football page, no matter where you currently are in your application.
GgRouter.of(context).navigateTo('./dialog/')
to navigate to the direct child.GgRouter.of(context).navigateTo('..')
to navigate to the parent.GgRouter.of(context).navigateTo('../../')
to navigate to the grand parent.GgRouter.of(context).navigateTo('../transportation/')
to navigate to a sibling.When you switch to a route, you might want to open the child route that was
opened when you left the route the last time. Use the _LAST_
keyword to
activate this route:
GgRouter.of(context).navigateTo('/sports/_LAST_');
Navigation buttons and GgRouter
widgets can be used side by side. Navigation
elements can use GgRouter.of(context)
to perform various routing operations:
GgRouter.of(context).navigateTo('...')
to navigate to a route.GgRouter.of(context).routeNameOfActiveChild
to find out which child
route is currently visible.GgRouter.of(context).indexOfActiveChild
to find out which of the items
in a BottomNavigationBar
need to be styled as visible elements.GgRouter.of(context).onActiveChildChange
to rebuild the navigation bar,
when the visible child changes.Use GgRouteParams
to define a list of query params that are shown in the URI.
GgRouteParams(
params: {
'a': GgRouteParam<bool>(seed: false),
'b': GgRouteParam<int>(seed: 5),
'c': GgRouteParam<String>(seed: 'hello'),
},
child: // ...
}
The param names a
, b
, and c
must only be used one time in a route path.
Different route paths can define the same parameter names. When switching a
route, also the route parameters will change.
To use the value of a query param in a widget, use these method:
GgRouter.of(context).param('a')?.value
to get or set the value of the
query param a
.GgRouter.of(context).param('a')?.stream
to observe value changes of
query param a
.GgRouter
offers a simple way to animate route transitions. Use inAnimation
and outAnimation
to define animations that are applied to the appearing and
the disappearing route:
builder: (context) {
return GgRouter(
// ...
inAnimation: (context, animation, child, size)
=> Transform.scale(scale: animation.value, child: child),
outAnimation: (context, animation, child, size)
=> Transform.scale(scale: 1.0 - animation.value, child: child),
);
},
With the possibility to define separate in and out animations, you can create
advanced transitions. E.g., move an appearing widget in from the left side and
out from the right side. Use the provided size
parameter to define how far
animated widgets should be moved.
To find out which route is currently fading in or fading out, use the following methods within your animation callback:
GgRouter.of(context).indexOfChildAnimatingOut
GgRouter.of(context).nameOfChildAnimatingOut
GgRouter.of(context).indexOfChildAnimatingIn
GgRouter.of(context).nameOfChildAnimatingIn
Widget _moveOut(
BuildContext context,
Animation animation,
Widget child,
) {
final w = MediaQuery.of(context).size.width;
final h = MediaQuery.of(context).size.height;
final index = GgRouter.of(context).indexOfChildAnimatingOut;
final toRight = Offset(w * (animation.value), 0);
final toBottom = Offset(0, h * (animation.value));
final toLeft = Offset(w * (-animation.value), 0);
Offset offset = index == 0
? toLeft
: index == 1
? toBottom
: toRight;
return Transform.translate(
offset: offset,
child: child,
);
}
With GgNavigationPage
we provide a simple way to create nested navigation
pages:
To create pages as shown in the animation, just nest instances of
GgNavigationPage
inside each other:
Widget build(context){
return GgNavigationPageRoot(
pageContent: (context) => _parentPage;
children: {
'child': (context) => GgNavigationPage(
pageContent: (_) => Container(
color: Color(0xFF555555),
child: _childPage,
children: {
'grand-child': (_) => GgNavigationPage(
pageContent: _grandChildPage
},
)
},
);
}
The code above will create the following page hierarchy:
|-root
|-child
|-grand-child
These things are done by the package for you:
GgRouter
to match the page hierarchy.To see an example, launch example/lib/main.dart
, click on "Sports" at the
top, click on "Basketball" at the bottom, and then click on the "basketball" in
the center of the screen.
GgRouter
constructor offers a saveState
and restorState
callback:
saveState
will be called with a JSON string when the route state changes.restoreState
will be called at the very first beginning and allows you
to restore a previously defined state.If you want to rebuild a widget each time the active route is changing,
use GgRouteChangeBuilder
.
int buildNumber;
final widget = GgRouter.root(
child: GgRouteChangeBuilder(
key: key, builder: (_) => Text('${buildNumber++}')),
node: rootNode,
);
The semanticLabels
constructor parameter of GgRouter
allows you to specify a
semantic label for each route:
@override
GgRouter(
// ...
semanticLabels: {
'sports': 'Navigate to sports page',
'transportation': 'Navigate to transportations page',
'places': 'Navigate to places page',
}
);
To retrieve the semantic label for a given route, use GgRouter
's the
semanticLabelForPath(...)
property:
final semanticLabel = GgRouter.of(context).semanticLabelForPath(route);
By doing so, you can now assign semantic labels to buttons that perform route operations.
If you need to specify a semantic label for a child router in advance, you
can use GgRouter.of(context).setSemanticLabelForPath(path, label)
.
If you open a URI in the browser that is not defined using GgRouter(...)
, an
error is thrown. To handle that error, assign an error handler to
GgRouter.of(context).node.errorHandler = (error){/* Handle Error**/}
.
An example demonstrating all of the features above can be found in example/main.dart
.
Please file feature requests and bugs at GitHub.