Open TimLariviere opened 1 year ago
Tagging @twop @edgarfgp
@TimLariviere . Based in the current experience . I think this is a good improvement.
@TimLariviere We could separate one msg for Push and other for pop ? This way we bind onPush navigating forward and on Pop to navigate back ?
NavigationView(stack: NavigationStack, onPush: NavigationStack -> 'msg, onPop: NavigationStack -> 'msg) {
Route(key: string, viewFn: obj -> WidgetBuilder<'pageMsg, #IView>, mapMsgFn: int * 'pageMsg -> 'rootMsg)
Route(...)
Route(...)
}
// We can add some extension methods on Route i.e
Route(...)
.isRoot(true)
....
Edit : Found a similar approach here https://github.com/frzi/SwiftUIRouter
would be possible to pass the NavigationStackModel as part of the push
and pop
functions . So we can conditionally push and pop
let update navStack msg model =
match msg with
| GoBack ->
navStack.pop(fun model -> if model.myProperty then `pop the stack` else ` no `)
model
| GoToDetail id ->
navStack.push(
fun model -> if model.myProperty then Pages.list, DetailPage.init id else ...)
model
Context
While working on Fabulous.Maui fabulous-dev/Fabulous#919, I noticed the Maui team opened up the implementation of
NavigationPage
via the newIStackNavigationView
interface (https://github.com/dotnet/maui/blob/main/src/Core/src/Core/IStackNavigation.cs). This interface gives us way more flexibility in how we want to make the navigation work inside Fabulous.So it got me thinking: what would be a good navigation experience in Fabulous?
Today in Fabulous.XamarinForms, we are simply mapping 1-to-1 the
NavigationPage
. This NavigationPage uses the Push/Pop method to add or remove pages from the stack.In order to make it play nicely with MVU, we hid the Push/Pop calls by implicitly calling them as users add and remove child pages under NavigationPage.
Here, we will only push the second page if
model.UserHasNavigatedToSecondPage = true
. As soon asmodel.UserHasNavigatedToSecondPage
reverts back tofalse
, we will call pop - only showing the 1st screen.This model is nice but lacks flexibility. You need to explicitly define the whole navigation hierarchy of your app. If you need to navigate to any page in any order, it is not currently possible in Fabulous.
Prior arts
Challenges
Fabulous uses the MVU architecture.
This means ideally the complete state of the application MUST BE stored in the
Model
record so we can ensure consistency and repeatability.This also means the view function needs to explicitly list all the subviews, including all pages in the navigation stack.
SwiftUI, Compose and Flutter all choose to let an external party handle their navigation. This means it breaks the 2 rules above.
Proposition
I would like to introduce 3 new types:
NavigationStack
that will be in charge of keeping a list of all pages visited and their models; will be stored in the App.ModelRoute
, a widget taking a page key and the related page view function to be called when the navigation requires itNavigationView
, a new widget that will use one or moreRoute
to describe which pages are available for navigation andNavigationStack
to know which pages to actually show on screenRoute
is only here to describe a page and won't ever make it to the UI tree.The implementation will require a new
RouteBuilder
computation expression that only acceptsRoute
. When compiling this CE, it would insteadfor
-loop into the stack, call the correspondingRoute
view function and append the resulting view into theNavigationView.Pages
attribute.Usage
Child pages can directly interact with the NavigationStack by passing the stack to them when calling the update function. Since
NavigationStack
is not part of the UI tree, it can't dispatch messages for Fabulous. InsteadNavigationView
will subscribe to thePushed
/Popped
event of itsNavigationStack
and dispatch aNavStackUpdated
message to force Fabulous to trigger an update-view loop.Additional comments
The good thing about this proposition is that it's also compatible with Xamarin.Forms
NavigationPage
. This is thanks to the fact at runtime we still use thePages
collection attribute.