Last updated:
0 purchases
aps navigator
APS Navigator - App Pagination System #
This library is just a wrapper around Navigator 2.0 and Router/Pages API that tries to make their use easier:
🔧 Basic feature set #
🚣 What we've tried to achieve:
Simple API
Easy setup
Minimal amount of "new classes types" to learn:
No need to extend(or implement) anything
Web support (check the images in the following sections):
Back/Forward buttons
Dynamic URLs
Static URLs
Recover app state from web history
Control of Route Stack:
Add/remove Pages at a specific position
Add multiples Pages at once
Remove a range of pages at once
Handles Operational System events
Internal(Nested) Navigators
⚠️ What we didn't try to achieve:
To use code generation
Don't get me wrong. Code generation is a fantastic technique that makes code clear and coding faster - we have great libraries that are reference in the community and use it
The thing is: It doesn't seems natural to me have to use this kind of procedure for something "basic" as navigation
To use Strongly-typed arguments passing
👀 Overview #
1 - Create the Navigator and define the routes: #
final navigator = APSNavigator.from(
routes: {
'/dynamic_url_example{?tab}': DynamicURLPage.route,
'/': ...
},
);
copied to clipboard
2 - Configure MaterialApp to use it: #
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: navigator,
routeInformationParser: navigator.parser,
);
}
}
copied to clipboard
3 - Create the widget Page (route): #
class DynamicURLPage extends StatefulWidget {
final int tabIndex;
const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);
@override
_DynamicURLPageState createState() => _DynamicURLPageState();
// Builder function
static Page route(RouteData data) {
final tab = data.values['tab'] == 'books' ? 0 : 1;
return MaterialPage(
key: const ValueKey('DynamicURLPage'), // Important! Always include a key
child: DynamicURLPage(tabIndex: tab),
);
}
}
copied to clipboard
You don't need to use a static function as PageBuilder, but it seems to be a good way to organize things.
Important: AVOID using 'const' keyword at MaterialPage or DynamicURLPage levels, or Pop may not work correctly with Web History.
Important: Always include a Key.
4 - Navigate to it: #
APSNavigator.of(context).push(
path: '/dynamic_url_example',
params: {'tab': 'books'},
);
copied to clipboard
The browser's address bar will display: /dynamic_url_example?tab=books.
The Page will be created and put at the top of the Route Stack.
The following sections describe better the above steps.
💆 Usage #
1 - Creating the Navigator and defining the Routes: #
final navigator = APSNavigator.from(
// Defines the initial route - default is '/':
initialRoute: '/dynamic_url_example',
// Defines the initial route params - default is 'const {}':
initialParams: {'tab': '1'},
routes: {
// Defines the location: '/static_url_example'
'/static_url_example': PageBuilder..,
// Defines the location (and queries): '/dynamic_url_example?tab=(tab_value)&other=(other_value)'
// Important: Notice that the '?' is used only once
'/dynamic_url_example{?tab,other}': PageBuilder..,
// Defines the location (and path variables): '/posts' and '/posts/(post_id_value)'
'/posts': PageBuilder..,
'/posts/{post_id}': PageBuilder..,
// Defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)&q2=(q2_value)'.
'/path/{id}?{?q1,q2}': PageBuilder..,
// Defines app root - default
'/': PageBuilder..,
},
);
copied to clipboard
routes is just a map between Templates and Page Builders:
📮 Templates are simple strings with predefined markers to Path ({a}) and Query({?a,b,c..}) values.
🏠 Page Builders are plain functions that return a Page and receive a RouteData. Check the section 3 bellow.
Given the configuration above, the app will open at: /dynamic_url_example?tab=1.
2 - Configure MaterialApp: #
After creating a Navigator, we need to set it up to be used:
1️⃣ Set it as MaterialApp.router.routeDelegate.
2️⃣ Remember to also add the MaterialApp.router.routeInformationParser:
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerDelegate: navigator,
routeInformationParser: navigator.parser,
);
}
}
copied to clipboard
3 - Creating the widget Page(route): #
When building a Page:
1️⃣ The library tries to match the address templates with the current address. E.g.:
📮 Template: /dynamic_url_example/{id}{?tab,other}'
🏠 Address: /dynamic_url_example/10?tab=1&other=abc
2️⃣ All paths and queries values are extracted and included in a RouteData.data instance. E.g.:
{'id': '10', 'tab': '1', 'other': 'abc'}
3️⃣ This istance is passed as param to the PageBuilder function - static Page route(RouteData data)...
4️⃣ A new Page instance is created and included at the Route Stack - you check that easily using the dev tools.
class DynamicURLPage extends StatefulWidget {
final int tabIndex;
const DynamicURLPage({Key? key, required this.tabIndex}) : super(key: key);
@override
_DynamicURLPageState createState() => _DynamicURLPageState();
// You don't need to use a static function as Builder,
// but it seems to be a good way to organize things
static Page route(RouteData data) {
final tab = data.values['tab'] == 'books' ? 0 : 1;
return MaterialPage(
key: const ValueKey('DynamicURLPage'), // Important! Always include a key
child: DynamicURLPage(tabIndex: tab),
);
}
}
copied to clipboard
4 - Navigating to Pages: #
Example Link: All Navigating Examples
4.1 - To navigate to a route with query variables:
📮 Template: /dynamic_url_example{?tab,other}
🏠 Address: /dynamic_url_example?tab=books&other=abc
APSNavigator.of(context).push(
path: '/dynamic_url_example',
params: {'tab': 'books', 'other': 'abc'}, // Add query values in [params]
);
copied to clipboard
4.2 - To navigate to a route with path variables:
📮 Template: /posts/{post_id}
🏠 Address: /posts/10
APSNavigator.of(context).push(
path: '/post/10', // set path values in [path]
);
copied to clipboard
4.3 - You can also include params that aren't used as query variables:
📮 Template: /static_url_example
🏠 Address: /static_url_example
APSNavigator.of(context).push(
path: '/static_url_example',
params: {'tab': 'books'}, // It'll be added to [RouteData.values['tab']]
);
copied to clipboard
🍷 Details #
1. Dynamic URLs Example #
Example Link: Dynamic URLs Example
When using dynamic URLs, changing the app's state also changes the browser's URL. To do that:
Include queries in the templates. E.g: /dynamic_url_example{?tab}
Call updateParams method to update browser's URL:
final aps = APSNavigator.of(context);
aps.updateParams(
params: {'tab': index == 0 ? 'books' : 'authors'},
);
copied to clipboard
The method above will include a new entry on the browser's history.
Later, if the user selects such entry, we can recover the previous widget's State using:
@override
void didUpdateWidget(DynamicURLPage oldWidget) {
super.didUpdateWidget(oldWidget);
final values = APSNavigator.of(context).currentConfig.values;
tabIndex = (values['tab'] == 'books') ? 0 : 1;
}
copied to clipboard
😪 What is important to know:
Current limitation: Any value used at URL must be saved as string.
Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.
2. Static URLs Example #
Example Link: Static URLs Example
When using static URLs, changing the app's state doesn't change the browser's URL, but it'll generate a new entry on the history. To do that:
Don't include queries on route templates. E.g: /static_url_example
As we did with Dynamic's URL, call updateParams method again:
final aps = APSNavigator.of(context);
aps.updateParams(
params: {'tab': index == 0 ? 'books' : 'authors'},
);
copied to clipboard
Then, allow State restoring from browser's history:
@override
void didUpdateWidget(DynamicURLPage oldWidget) {
super.didUpdateWidget(oldWidget);
final values = APSNavigator.of(context).currentConfig.values;
tabIndex = (values['tab'] == 'books') ? 0 : 1;
}
copied to clipboard
😪 What is important to know:
Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.
3. Return Data Example #
Example Link: Return Data Example
Push a new route and wait the result:
final selectedOption = await APSNavigator.of(context).push(
path: '/return_data_example',
);
copied to clipboard
Pop returning the data:
APSNavigator.of(context).pop('Do!');
copied to clipboard
😪 What is important to know:
Data will only be returned once.
In case of user navigate your app and back again using the browser's history, the result will be returned at didUpdateWidget method as result, instead of await call.
@override
void didUpdateWidget(HomePage oldWidget) {
super.didUpdateWidget(oldWidget);
final params = APSNavigator.of(context).currentConfig.values;
result = params['result'] as String;
if (result != null) _showSnackBar(result!);
}
copied to clipboard
4. Multi Push #
Example Link: Multi Push Example
Push a list of the Pages at once:
APSNavigator.of(context).pushAll(
// position: (default is at top)
list: [
ApsPushParam(path: '/multi_push', params: {'number': 1}),
ApsPushParam(path: '/multi_push', params: {'number': 2}),
ApsPushParam(path: '/multi_push', params: {'number': 3}),
ApsPushParam(path: '/multi_push', params: {'number': 4}),
],
);
copied to clipboard
In the example above ApsPushParam(path: '/multi_push', params: {'number': 4}), will be the new top.
😪 What is important to know:
You don't necessarily have to add at the top; you can use the position param to add the routes at the middle of Route Stack.
Don't forget to include a Key on the Page created by the PageBuilder to everything works properly.
5. Multi Remove #
Example Link: Multi Remove Example
Remove all the Pages you want given a range:
APSNavigator.of(context).removeRange(start: 2, end: 5);
copied to clipboard
6. Internal (Nested) Navigators #
Example Link: Internal Navigator Example
class InternalNavigator extends StatefulWidget {
final String initialRoute;
const InternalNavigator({Key? key, required this.initialRoute})
: super(key: key);
@override
_InternalNavigatorState createState() => _InternalNavigatorState();
}
class _InternalNavigatorState extends State<InternalNavigator> {
late APSNavigator childNavigator = APSNavigator.from(
parentNavigator: navigator,
initialRoute: widget.initialRoute,
initialParams: {'number': 1},
routes: {
'/tab1': Tab1Page.route,
'/tab2': Tab2Page.route,
},
);
@override
void didChangeDependencies() {
super.didChangeDependencies();
childNavigator.interceptBackButton(context);
}
@override
Widget build(BuildContext context) {
return Router(
routerDelegate: childNavigator,
backButtonDispatcher: childNavigator.backButtonDispatcher,
);
}
}
copied to clipboard
😪 What is important to know:
Current limitation: Browser's URL won't update based on internal navigator state
Warning & Suggestions #
🚧 Although this package is already useful, it's still in the Dev stage.
😛 I'm not sure if creating yet another navigating library is something good - we already have a lot of confusion around it today.
💩 This lib is not back-compatible with the old official Navigation API - at least for now (Is it worth it?).
🐛 Do you have any ideas or found a bug? Fell free to open an issue! :)
💁 Do you want to know the current development stage? Check the Project's Roadmap.
Maintainers #
Gui Silva
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.