-
-
Notifications
You must be signed in to change notification settings - Fork 69
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
FlowBuilder<FlowState>(
// 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<String, String> params = location.params;
// Determine the flow state based on the current location.
final FlowState state = _determineFlowState(path, params);
/// Update the flow state.
context.flow<FlowState>().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<Page> currentPages) {
final List<Page> pages = _determinePages(state, currentPages);
return pages;
}
)Execution Flow
FlowBuilderis initialized with astateonLocationChangedis invoked when a location change occurs.- Flow state can be updated based on the location change.
onGeneratePagesis 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.
const profilePath = 'profile';
...
FlowBuilder<Profile>(
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<Profile>().update((_) => profile);
},
onGeneratePages: (Profile profile, List<Page> pages) {
return [
FlowPage<void>(
child: ProfileNameForm(),
location: FlowLocation(path: profilePath),
),
if (profile.name != null)
FlowPage<void>(
child: ProfileAgeForm(),
location: FlowLocation(path: profilePath, params: {'name': profile.name})
),
]
}
)The above code will result in the following state to route mapping:
Profile()(default):/profileProfile(name: 'Felix'):/profile?name=FelixProfile(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:
enum AuthState { uninitialized, unauthenticated, authenticated }
class AuthBloc extends Bloc<AuthEvent, AuthState> {...}
FlowBuilder<AuthState>(
state: context.watch<AuthBloc>().state,
onGeneratePages: (AuthState state, List<Page> 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.
class Login extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
...
ElevatedButton(
onPressed: () => Navigator.of(context).push(SignUp.page());
)
}
}class SignUp extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return FlowBuilder<SignUpState>(
...
)
}
}