felangel / felangel/flow_builder
[Proposal] Add Routing Support
- Dominant language
- Dart
- Stars
- 416
- Forks
- 68
- PR merge metrics
- No merged PRs in 30d
Description
# [Proposal] Add Routing Support
Currently `FlowBuilder` does not have custom routing support for deep linking, dynamic linking, custom paths, query parameters, browser url synchronization, etc (#20).
This proposal outlines a potential FlowBuilder API which can accomodate for the above use-cases by leveraging the Navigator 2.0 router APIs. Ideally, the proposed enhancements should be backward compatible with the existing API.
## Routing API
```dart
FlowBuilder(
// The state of the flow.
state: FlowState(),
// `VoidCallback` invoked whenever the route location changes.
// Responsible for reacting to changes in the location.
//
// * Will be invoked immediately when the app is launched
// to determine what the initial state of the flow should be.
// * Will be invoked when the current route changes via `pushNamed`, `pushReplacementNamed`, etc.
onLocationChanged: (BuildContext context, FlowLocation location) {
// Equivalent to `window.location.pathname`.
final String path = location.path;
// Map of query parameters which are analagous to `window.location.search`.
final Map params = location.params;
// Determine the flow state based on the current location.
final FlowState state = _determineFlowState(path, params);
/// Update the flow state.
context.flow().update((_) => state);
},
// Called whenever the flow state changes. Will remaing unchanged.
// Responsible for determining the correct navigation stack
// based on the current flow state.
onGeneratePages: (FlowState state, List currentPages) {
final List pages = _determinePages(state, currentPages);
return pages;
}
)
```
### Execution Flow
1. `FlowBuilder` is initialized with a `state`
2. `onLocationChanged` is invoked when a location change occurs.
3. Flow state can be updated based on the location change.
4. `onGeneratePages` is triggered when the flow state changes & updates the nav stack.
The developer can optionally react to changes in `FlowLocation` (abstraction on top of `RouteInformation`) and trigger updates in flow state.
### Named Routes
Named routes can be achieved by defining a `FlowPage` which extends `Page`.
```dart
const profilePath = 'profile';
...
FlowBuilder(
state: Profile(),
onLocationChanged: (BuildContext context, FlowLocation location) {
if (location.path != profilePath) return;
// Alternatively we can potentially use `fromJson` with `package:json_serializable`.
// `final profile = Profile.fromJson(location.params);`
final profile = Profile(
name: location.params['name'] as String?,
age: int.tryParse(location.params['age'] as String?)
);
context.flow().update((_) => profile);
},
onGeneratePages: (Profile profile, List pages) {
return [
FlowPage(
child: ProfileNameForm(),
location: FlowLocation(path: profilePath),
),
if (profile.name != null)
FlowPage(
child: ProfileAgeForm(),
location: FlowLocation(path: profilePath, params: {'name': profile.name})
),
]
}
)
```
The above code will result in the following state to route mapping:
- `Profile()` (default): `/profile`
- `Profile(name: 'Felix')`: `/profile?name=Felix`
- `Profile(name: 'Felix', age: 26)`: `/profile?name=Felix&age=26`
### Navigation
Navigation will largely remain unchanged. Using `context.flow` or a `FlowController`, developers can `update` the flow state or `complete` the flow. The main difference would be updates to the flow state can potentially be accompanied by location changes if the associated pages are of type `FlowPage` with a custom `location`. When a named route is pushed via `Navigator.of(context).pushNamed`, all available `FlowBuilder` instances will be notified via `onLocationChanged`.
### Nested Routes
`FlowBuilders` can be nested to support nested routing. For example:
```dart
enum AuthState { uninitialized, unauthenticated, authenticated }
class AuthBloc extends Bloc {...}
FlowBuilder(
state: context.watch().state,
onGeneratePages: (AuthState state, List pages) {
switch (state) {
case AuthState.uninitialized:
return [Splash.page()];
case AuthState.unauthenticated:
return [Login.page()];
case AuthState.authenticated:
return [Home.page()];
}
}
)
```
We can push a nested flow from within `Login` to initiate a Sign Up flow.
```dart
class Login extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
...
ElevatedButton(
onPressed: () => Navigator.of(context).push(SignUp.page());
)
}
}
```
```dart
class SignUp extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return FlowBuilder(
...
)
}
}
```
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.