Open felangel opened 3 years ago
Regarding nested routes. Would such thing as below be possible? It feels more inline with routes being the representation of state, where you don't have to explicitly push any new routes onto the stack and all the routes in the Navigator have corresponding pages.
class AppRouting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowBuilder<AuthState>(
state: context.watch<AuthBloc>().state,
onGeneratePages: (AuthState state, List<Page> pages) {
switch (state) {
case AuthState.uninitialized:
return [Splash.page()];
case AuthState.unauthenticated:
case AuthState.signInInProgress:
return [
Login.page(),
if (state == AuthState.signInInProgress) SignInRouting.page(),
];
case AuthState.authenticated:
return [Home.page()];
}
});
}
}
enum AuthState {
uninitialized,
unauthenticated,
signInInProgress,
authenticated
}
class SignInRouting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowBuilder<SignInState>(
state: context.watch<SignInBloc>().state,
onGeneratePages: (SignInState state, List<Page> pages) {
switch (state) {
case SignInState.step1:
return [SignInStep1.page()];
case SignInState.step2:
return [SignInStep2.page()];
case SignInState.verify:
return [SignInVerify.page()];
}
});
}
}
enum SignInState {
step1,
step2,
verify,
}
Of course when using enums like AuthState
it's hard to represent more complex states where it can have 2 dimensions (e.g. authentication state and sign in process started), but with class it could be more readable.
class AppRouting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowBuilder<AppState>(
state: context.watch<AppState>().state,
onGeneratePages: (AppState state, List<Page> pages) {
switch (state.authState) {
case AuthState.uninitialized:
return [Splash.page()];
case AuthState.unauthenticated:
return [
Login.page(),
if (state.signState == SignState.signIn)
SignInRouting.page(),
if (state.signState == SignState.signUp)
SignUpRouting.page(),
];
case AuthState.authenticated:
return [Home.page()];
}
});
}
}
class AppState {
AppState(this.authState, this.signState);
final AuthState authState;
final SignState signState;
}
enum SignState {
signIn,
signUp,
}
enum AuthState { uninitialized, unauthenticated, authenticated }
class SignInRouting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FlowBuilder<SignInState>(
state: context.watch<SignInBloc>().state,
onGeneratePages: (SignInState state, List<Page> pages) {
switch (state) {
case SignInState.step1:
return [SignInStep1.page()];
case SignInState.step2:
return [SignInStep2.page()];
case SignInState.verify:
return [SignInVerify.page()];
}
});
}
}
enum SignInState {
step1,
step2,
verify,
}
The developer can optionally react to changes in FlowLocation (abstraction on top of RouteInformation) and trigger updates in flow state.
What would be structure of FlowLocation
? Currently RouteInformation
contains location
property being a string. I find having Uri
a bit more convenient, but it also imposes some constraints like validity of schema or path. Would that .path
property be a string as well?
For everyones benefit, here are the scenarios that this proposal would support: https://github.com/flutter/uxr/blob/master/nav2-usability/storyboards/%5BPublic%5D%20Flutter%20Navigator%20Scenarios%20Storyboards%20v2.pdf
I believe the onLocationChange and the FlowLocation really make sense for the deep linking path and query parameters and now I can clearly see how we can declaratively do this.
With the FlowLocation, we really just add a named parameter and that's pretty simple.
As always, it's not so much about what the API looks like as to how you use the API ! This means those little examples could go a long way and I look forward to helping apply the examples to these scenarios.
The real feedback will come when more people use it and we uncover desire paths. I personally cannot imagine any more scenarios and I have full faith in the communities ability to use this in ways we never imagined, for better or for worse !
In your examples, you have this function which I think really helps being up at the top of the file
List<Page> onGenerateProfilePages(Profile profile, List<Page> pages) {
return [
MaterialPage<void>(child: ProfileNameForm()),
if (profile.name != null) MaterialPage<void>(child: ProfileAgeForm()),
if (profile.age != null) MaterialPage<void>(child: ProfileWeightForm()),
];
}
Would you suggest similar for onLocationUpdate?
Sorry to derail a bit - is the intention of this to be a full drop in replacement for all routing within an app, or an enhancement for better flow support that should be used in conjunction with an existing routing solution?
For those who need the web working today you can try auto_route with authguard + authentication bloc
There are some limitations but it works somehow
Sorry to derail a bit - is the intention of this to be a full drop in replacement for all routing within an app, or an enhancement for better flow support that should be used in conjunction with an existing routing solution?
This can do both. Whether you would like to make it for a full replacement, or piecewise, that's the flexibility it offers
For those who need the web working today you can try auto_route with authguard + authentication bloc
There are some limitations but it works somehow
I think you will appreciate this ! https://github.com/felangel/bloc/tree/master/examples/flutter_firebase_login
@felangel any ETA of this proposal?
@felangel any ETA of this proposal?
Sorry for the delay! I'm planning to pick this up later this week or this weekend π
Are there any updates about this? This would be very helpful for some deep link cases me and my team are working on!
Thank you all for your efforts!
For anyone interested, there's a WIP branch for this proposal at https://github.com/felangel/flow_builder/tree/feat/url-routing-and-deep-linking π
@felangel How sync the browser url?
For anyone interested, there's a WIP branch for this proposal at https://github.com/felangel/flow_builder/tree/feat/url-routing-and-deep-linking π
I am interested, but it is not working as expected
Here is my code
FlowBuilder<NavigationStack>(
state: context.watch<NavigationCubit>().state,
onGeneratePages: (NavigationStack state, List<Page> pages) {
print(state.all);
return state.all.map((e){
return MaterialPage<dynamic>(child: MyScreen.getByRouteLocation(e.location).widget);
}).toList();
},
onDidPop: (dynamic result) {
print('hallo=$result');
context.watch<NavigationCubit>().popRoute();
},
onLocationChanged: (Uri uri, NavigationStack stack) {
print('uri=$uri');
print('stack=$stack');
return stack;
},
)
onDidPop is never called, onLocationChanged only on initial routing when starting the app. everything else works fine, I can see that the state is correctly updated and routing/navigation works also fine
the problem seems to be that FlowBuilder
expects the state
to be a single route, which in my case would be a NavigationRoute
, but my state
is a NavigationStack
with a List<NavigationRoute>
_history
has always 1 item in my case, the NavigationStack
, that's why the onDidPop
never is called
_pages
however has the correct amount of items (routes)
child: Navigator(
key: _navigatorKey,
pages: _pages,
observers: [_FlowNavigatorObserver(), ...widget.observers],
onPopPage: (route, dynamic result) {
if (_history.length > 1) { // <-- problem lies here
_history.removeLast();
_didPop = true;
widget.onDidPop?.call(result);
_controller.update((_) => _history.last);
}
if (_pages.length > 1) _pages.removeLast();
final onLocationChanged = widget.onLocationChanged;
final pageLocation = _pages.last.name;
if (onLocationChanged != null && pageLocation != null) {
_SystemNavigationObserver._updateLocation(pageLocation);
_controller.update(
(state) => onLocationChanged(Uri.parse(pageLocation), state),
);
}
setState(() {});
return route.didPop(result);
},
)
is this a Bug or do I need to rethink my state for Navigation?
UPDATE:
I was just playing around and made sure that _history
and _pages
have always the same amount of items, and everything worked as I was expecting it with the popping
Any update on this?
Looking forward for this feature to be added.
[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
Execution Flow
FlowBuilder
is initialized with astate
onLocationChanged
is invoked when a location change occurs.onGeneratePages
is triggered when the flow state changes & updates the nav stack.The developer can optionally react to changes in
FlowLocation
(abstraction on top ofRouteInformation
) and trigger updates in flow state.Named Routes
Named routes can be achieved by defining a
FlowPage
which extendsPage
.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 aFlowController
, developers canupdate
the flow state orcomplete
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 typeFlowPage
with a customlocation
. When a named route is pushed viaNavigator.of(context).pushNamed
, all availableFlowBuilder
instances will be notified viaonLocationChanged
.Nested Routes
FlowBuilders
can be nested to support nested routing. For example:We can push a nested flow from within
Login
to initiate a Sign Up flow.