Milad-Akarie / auto_route_library

Flutter route generator
MIT License
1.57k stars 395 forks source link

FEATURE: Enable push/navigate without changing the url #1373

Closed wer-mathurin closed 1 year ago

wer-mathurin commented 1 year ago

@Milad-Akarie How would be possible to achieve :

In my usecases, would love being able to have a "stepper" done with a pageview and having the browser back button to get to the previous page of the pageview instead of getting back to the previous route. From my understanding, this can be achieved with the Router API.(see bellow). So having a kind of "pushHistory" would do the job.

One can force the Router to report new route information as navigation event to the routeInformationProvider (and thus the browser) even if the RouteInformation.location has not changed by calling the Router.navigate method with a callback that performs the state change. This causes Router to call the RouteInformationProvider.routerReportsNewRouteInformation with RouteInformationReportingType.navigate, and thus causes PlatformRouteInformationProvider to push a new history entry regardlessly. This allows one to support the browser's back and forward buttons without changing the URL. For example, the scroll position of a scroll view may be saved in the RouteInformation.state. Using Router.navigate to update the scroll position causes the browser to create a new history entry with the RouteInformation.state that stores this new scroll position. When the user clicks the back button, the app will go back to the previous scroll position without changing the URL in the location bar.

Milad-Akarie commented 1 year ago

@wer-mathurin I think that's a good feature to have. I'll see what I can do. I'll properly only added to v6 thu.

wer-mathurin commented 1 year ago

Awesome 😎

I was also thinking of a another way to achieving the use case that may be faster to implement for you, but a little bit different in terms of API.

Having a new widget.

The AutoWillPopScope, basically this is the same API of WillPopScope, but capture the browser back navigation and allow us to decide to allows it or not. This allow you to start the implementation of the previous feature but without affecting the history of navigation.(easier in my mind)

I'm losing the "feature" of the forward browser arrow, but for an MVP this is enough. Probably this is more a PR that need to be push to Flutter itself, but having it in your library as a playground can be cool and faster available to users!

Milad-Akarie commented 1 year ago

@wer-mathurin I've already gave that ago a while back with not success, preventing the browser's backbutton action is not that easy if not impossible. and this's why https://www.irt.org/script/311.htm

What instantly came in mind is to provide a way to push 'states' to the existing urls which gonna add an entry to the web browser history without any visual changes, popping those states should give you a step-back kinda behaviour, e.g you're at /stepper url you push new state (pushUrlState(context,1) 1 is the current step now your history will look something like this

/stepper -> with state : 1 /stepper

now hitting browser backbutton should take you to /stepper and still no visual changes

and there should be a way for users to read the entry state to reflect that state in their UI

wer-mathurin commented 1 year ago

@Milad-Akarie Understand! Since you might target v6 for this feature. Do you have an estimate or a timeline?

I'm on a project that would benefit from it and I would like to use this feature. Between you, me...and the internet...I'll propose to buy you a tons of coffee ;-)

Milad-Akarie commented 1 year ago

I appreciate the support, I already have an idea on how to implement this but im not sure what challenges I might face, I can tell you that's the the current feature on my todo list. Just migrate to v6

Milad-Akarie commented 1 year ago

Hello @wer-mathurin auto_route version 6.0.0-rc-1 should have a router.pushPathState method and a router.pathState getter, I believe these methods are what you need in your use case.

Feb-27-2023 3-44-03 PM

Button code from above demo

  ElevatedButton(
              onPressed: () {
                final currentState = ((context.router.pathState as int?) ?? 0);
                context.router.pushPathState(currentState + 1);
              },
              child: AnimatedBuilder(
                  animation: context.router.navigationHistory,
                  builder: (context, _) {
                  return Text('Update State: ${context.router.pathState}');
                }
              ),
            )
wer-mathurin commented 1 year ago

@Milad-Akarie : no way...🤩.

That's just incredible! I was hoping to have this feature implements.in weeks....not a day ;-)

I will test this next week. We are in a middle of a sprint and changing the routing Library will be on my plate next week. I didn't look at the API. But from the example, I can imagine we can push any state we want(dynamic).

For long term, do you think there would be a solution to define a AutoRoute with a "typed state" for history. This will prevent casting. See bellow.

AutoRouterConfig(replaceInRouteName: 'Screen,Route')
class AppRouter extends $AppRouter {

@override    
  final List<AutoRoute> routes = [    
    //HomeScreen is generated as HomeRoute because   
    //of the replaceInRouteName property  
    AutoRoute<MyState>(HomeRoute.page),  
   ]  
 }

So the API would be context.router.pushPathState(...)

This way the state pass in parameter is type safe. I don't what is the implications for the navigationHistory....

Milad-Akarie commented 1 year ago

@wer-mathurin by now you properly know how much I prefer strong types, but in this case I don't think it's possible to force types because 'pathState' is not route/segment exclusive, meaning the whole url (location) will have the same state, a url can be constructed by multiple AutoRoutes/segments so I don't think it's possible to device the state inside of AutoRoute.

it's possible to define a global path state in RootRouter scope that is, but I don't think that makes sense as you might need to push different states for different locations

I guess we can add helper method to get typed states e.g router.getPathState<T>()

wer-mathurin commented 1 year ago

@Milad-Akarie In my case this is not a problem. But playing with dynamic smells 🤪.

Just an idea(this is definitely not mandatory from my perspective)

Maybe you can offer the option of providing a Type when specifying a Router state.

@AutoRouterConfig(pathState: MyState)

So when pushing a state you can assert the state is the right type.

Seems counter intuitive, but it will allow to have a guard/prevent pushing anything and possibly break other part of the app.

For now I could use "freezed" states and use "when" function for pattern matching. In can leverage the pattern matching when retrieving the state, by using Dart 3 and providing a sealed Type.

Or just a note in the documentation for recommendations of strong type for pushingPathState.

.

wer-mathurin commented 1 year ago

@Milad-Akarie I found a usecase where this feature is not working properly.

If you have a declarative routing at the root, seems to break the pathstate propagation.

Milad-Akarie commented 1 year ago

Hey @wer-mathurin in declarative routing auto_route does not report the state until routes builder is evacuated because we don't know what routes the user might chose to show regardless of the path coming from the browser. any ways, you can do the following for now until I figure a seamless way to implement this

appRouter.declarativeDelegate(
  onNavigate: (urlState) {
           // don't report update if url is changed
          if(urlState.url == _appRouter.currentUrl) {
            _appRouter.navigationHistory.onNewUrlState(urlState);
          }
        }, 
        routes:(){}
)
github-actions[bot] commented 1 year ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions