Skip to content

Router #45938

Closed
Closed

Description

@chunhtai

Design doc

The goal for this refactoring:

  • 1. Refactor the imperative api to continue working in the new navigation system

  • 2. Introduce new page api to Navigator widget for more flexible navigator history modification

  • 3. Implement Router widget for a more convenience api which wraps navigator widget and utilize the new page api.

Activity

  1. self-assigned this
    on Dec 2, 2019
  2. added
    f: routesNavigator, Router, and related APIs.
    frameworkflutter/packages/flutter repository. See also f: labels.
    on Dec 2, 2019
  3. spkersten commented on Dec 17, 2019

    @spkersten
    Contributor

    Since the design doc mentioned nested Navigators, I wonder whether routing could be something extensible, where Navigator aren't the only widgets representing part of the route. For example:

    In our apps, we have a Navigator at the root (the one in WidgetsApp) that we use to navigate between onboarding and daily UI. In Daily UI we have a (custom build) tab bar. Each tab has a Navigator for navigation within the tab (we want each tab to have an independent stack of pages). For one of the inner Navigators (the one showing settings), we'd like to show a different design on large devices, where the first page is shown on the left of the screen and used to select a page on the right side of the screen (a master-detail view, like the iOS Settings app).

    To handle deep links, it would be useful if routing could incorporate widgets like a tab bar or a master-detail view.

  4. added this to the February 2020 milestone on Dec 18, 2019
  5. amreniouinnovent commented on Jan 21, 2020

    @amreniouinnovent

    @chunhtai What about custom navigation to widgets similar to Fragments in Android?
    For example if I have a Form screen with several steps and I want to use navigation between Tabs is it possible I can use Navigator 2.0 but for Tabs?

    The same page but using a custom navigator to switch between fragments
    https://github.com/amorenew/FlutterBlocFragment

  6. chunhtai commented on Jan 21, 2020

    @chunhtai
    ContributorAuthor

    @amreniouinnovent I am not entirely sure what effect you would like to achieve. I imagine you should be able to achieve most by writing custom url parser with custom page, and on onPopPage.
    Can you explain more details on how would you expect to use the api, and what is the effect?

  7. amreniouinnovent commented on Jan 22, 2020

    @amreniouinnovent

    @chunhtai I expected same like navigation but for a part of a screen
    Like fragments in Android

                supportFragmentManager.beginTransaction()
                        .add(android.R.id.content, details)
                        .commit()
    
    

    In my Navigation Bloc Fragment, I have the same page but I switch between Sub widgets by my Bloc.
    Drawer and AppBar are fixed while widgets are changed.

    One more case in Dialogs I have the same dialog and I want a router for the same dialog.
    For example, instead of using Indexed Stack widget I want to use routes but for a Scaffold
    https://api.flutter.dev/flutter/widgets/IndexedStack-class.html

  8. MajedDH commented on Jan 25, 2020

    @MajedDH

    @amreniouinnovent Using a navigator as routing inside a scaffold can be achieved by using nested navigators.

    Take a look here Flutter Case Study: Multiple Navigators with BottomNavigationBar here's a Demo

  9. amreniouinnovent commented on Jan 26, 2020

    @amreniouinnovent

    @MajedDH I checked the example it is very good
    However that we can achieve that in different ways but not the same as fragments in Android.
    What if you want that in a Dialog you will have to add unnecessary complexity for just a simple task.
    Android fragments are an awesome and simple way of nested navigation.
    I think for now my bloc is good enough but it would be awesome if I could see fragments in Flutter

  10. chunhtai commented on Jan 29, 2020

    @chunhtai
    ContributorAuthor

    @amreniouinnovent That will likely to be a new widget that wraps the navigator to provide more convenient api. I think that should separate from this issue, feel free to file a new feature request

  11. royibernthal commented on Feb 7, 2020

    @royibernthal

    Hi @chunhtai

    How about being able to access the up-to-date context of the current route displayed from within a provider? Do you think it might be relevant?

    Here's an issue I opened in which I'm explaining why it's not possible today:
    #50231

  12. rrousselGit commented on Feb 7, 2020

    @rrousselGit
    Contributor

    Ah, I forgot to make an issue about that problem.

    I agree with @royibernthal that currently (and it'll get worse with Navigator 2.0), showing overlays in response to an InheritedWidget state change is not great.

    We had some sort of solution, that is didChangeDepencencies. But it recently broke on the master channel (see #49527).

    In my opinion, Navigator 2.0 should also include an Overlay 2.0 with the same reasoning than Navigator:
    OverlayEntry is currently imperative, and this is bad.
    So it'd be great to have a declarative Overlay manipulation instead.
    That's how React deals with overlays.

    This would completely solve the "showing overlays in answer to an InheritedWidget change" issue, as it'd just be our typical build method doing its work.
    I made an implementation of such declarative Overlay very recently actually: https://github.com/rrousselGit/flutter_portal. It's tested and most features are implemented – feel free to take a look.

    It's up to the team to think about it, but I am convinced that this flutter_portal thing should not be as a package since it's literally OverlayEntry.
    Also, all functions like showDialog, showSnackbar, ..., should be made declarative too, not just OverlayEntry.

    If needed, I'm open to discussing with the team, make an issue, or potentially make a design doc (although I'd like to avoid the latter – too much work for me).

  13. chunhtai commented on Feb 10, 2020

    @chunhtai
    ContributorAuthor

    @rrousselGit I am not sure if I understand, how would Navigator 2.0 makes the overlay issue worse?

    I would like to separate out the overlay entry issue from the navigator refactoring as it the navigator refactor is already pretty complicated. Would you open up a new feature request for merging the library and we can continue our discussion there?

  14. rrousselGit commented on Feb 18, 2020

    @rrousselGit
    Contributor

    Done in #50961
    For what it's worth:

    I think these two issues are related because if OverlayEntry is declarative, then Navigator is declarative too by default.\

    By that I mean Router used this way:

    Router(
      routes: [
        Route(path: '/', child: Home()),
      ], 
    )

    Is basically the equivalent a declarative OverlayEntry used this way:

    Overlay(
      overlays: [
        OverlayEntry(overlay: Home()),
      ], 
    )

    with a few extras to handle paths.


    It's also worth noting that if such Overlay 2.0 becomes a thing, then it would enable showing "detail" routes from within another route.

    This allows anonymous routes while still being declarative, and without having to make Router aware of too many things.

    For example consider our typical Todo app, with two views:

    • Home, which shows a list of todos
    • TodoDetails, which shows a page for a specific todo

    Then with an Overlay 2.0, then we could write:

    class Home extends StatefulWidget {
      @override
      _HomeState createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      List<Todo> todos;
      Todo focusedTodo;
    
      @override
      Widget build(BuildContext context) {
        return PageRoute(
          path: '/todos/${focusedTodo?.id}',
          visible: focusedTodo != null,
          onClose: () => setState(() => focusedTodo = null),
          page: TodoDetails(todo: focusedTodo),
          child: ListView.builder(
            itemBuilder: (context, index) {
              final todo = todos[index];
              return ListTile(
                onTap: () => setState(() => focusedTodo = todo),
                title: Text(todo.description),
              );
            },
          ),
        );
      }
    }
  15. 88 remaining items

  16. chunhtai commented on Nov 11, 2020

    @chunhtai
    ContributorAuthor

    @lukepighetti They meant different things, we separate out the browser history and navigator route stack. The browser back button means build the app with the previous url, and the back button on appbar means means pop the top most route off the route stack.

  17. lukepighetti commented on Nov 11, 2020

    @lukepighetti
    Contributor

    Shouldn't that be the same thing in a 100% declarative router implementation?

  18. chunhtai commented on Nov 11, 2020

    @chunhtai
    ContributorAuthor

    @lukepighetti
    Do you mean appbar back button should work the same as browser backward button, or the browser backward button should work the same as appbar back button?

    if you meant the former:
    The app bar is originally built with imperative API, so we automatically shows the backbutton when there are multiple routes underneath. This mechanism makes sense for mobile application because mobile has the concept of route stack. If you don't want this behavior you can write your own declarative back button

    if you meant the latter:
    The previous page can be an arbitrary page, it can be a certain page in the app or a page in a completely different website. The browser backward button is used for bring user back to the previous page. It does more than just popping top most route of the app.

  19. lukepighetti commented on Nov 11, 2020

    @lukepighetti
    Contributor

    In a fully declarative router where a single input (ie a url string) always results in the same navigator stack shown on screen, then it seems like there should be no difference between the app back button and the browser back button. The app back button cannot mutate the stack, as the router would no longer be declarative. All it should be able to do is go back one item in the router history. It would be up to us to modify the history if we need to layer things a certain way. This is not the same thing as modifying the navigator stack because it's still 100% declarative.

    TLDR the app back button should pop the last history item off the router history, just like the back button. Not mutate the underlying stack by popping a single route/page, because that breaks the promise of being fully declarative.

  20. orestesgaolin commented on Nov 11, 2020

    @orestesgaolin
    Contributor

    In a fully declarative router where a single input (ie a url string) always results in the same navigator stack shown on screen, then it seems like there should be no difference between the app back button and the browser back button.

    I think that's not desired. Imagine opening https://navigator_20_example.codemagic.app/#/details/5 and then pressing browser back button. It would be strange to be moved to details/4 or Home. Natural thing to observe would be to go back to this issue.

    The AppBar button, however, is very mobile-oriented so it's relatively normal to go back to the previous page within the app i.e. Home.

    Edit: one way I can think of would be to add web-oriented AppBar which would imitate the native back button behavior. Or just don't show AppBar's back button on web at all ;)

  21. lukepighetti commented on Nov 12, 2020

    @lukepighetti
    Contributor

    Nothing I've said is unique to browsers, especially since Android has a hardware back button.

    The situation you describe would be resolved with a method/delegate that builds the nav history on first load, much like how the current navigator does that with /onboarding/start building a page stack and route name like this:

    page stack

    • /
    • /onboarding
    • /onboarding/start

    route name

    • /onboarding/start

    Comparing to what I said, instead of doing pages like that, you'd do history entries. Like this:

    route history

    • /
    • /onboarding
    • /onboarding/start

    Where the android back button, web back button, or app bar back button pop the current history entry and build the declarative route stack based on the previous (now current) route.

    The only time they would differ is when to hide the app bar back button. The web/android back button would eventually take you to the website/app you were on previously, once you've exhausted the Flutter route history.

  22. lukepighetti commented on Nov 24, 2020

    @lukepighetti
    Contributor

    Friendly ping on this. Maybe the ship has sailed, but I fear that Nav 2.0 is a declarative/imperative hybrid instead of fully declarative. It's my understanding that the core design specification called for it to be 100% declarative. I might be missing something, but I think I've given the issue an appropriate amount of care in thinking it through.

  23. chunhtai commented on Nov 24, 2020

    @chunhtai
    ContributorAuthor

    We make it a hybrid for backward compatibility, you can definitely go full declarative by customizing the behavior for android back button in routerdelegate.popRoute.

  24. Milad-Akarie commented on Nov 24, 2020

    @Milad-Akarie

    @chunhtai do we refrain from awaiting routes for results in favor of passing our own pop functions?

  25. chunhtai commented on Nov 24, 2020

    @chunhtai
    ContributorAuthor

    @Milad-Akarie In a fully declarative world then yes. There are still a lot of widget that relies on imperative push/pop, dialog for example, and they makes sense to stay imperative due to their functionality. In those cases, awaiting for route is still useful.

  26. Milad-Akarie commented on Nov 26, 2020

    @Milad-Akarie

    @chulwoo-park me again! if we can decide wether or not to update pages based on our own route-data-models do we still need to use keys? if so, what are your thoughts on having multi entires (same route data) of the same page in our stack?

  27. bkoznov commented on Feb 8, 2021

    @bkoznov

    @chunhtai

    you can do Router.of(context).routerDelegate to access the router delegate directly and calls the public method on the routerdelegate to change selected book.

    Alternative, the selected book state can be hosted in an inherited widget and the widget subtree can change it directly, you just need to make sure the routerdelegate is listening to the selected book state and notify its listener when it change.

    This works great, but would you happen to know how to apply it in the case of nested routers (to use a BottomNavigationBar) as in this gist? https://gist.github.com/johnpryan/bbca91e23bbb4d39247fa922533be7c9 I had some problems where providing the app state wasn't triggering route changes, as it looks like someone else did in the comments?

  28. github-actions commented on Aug 6, 2021

    @github-actions

    This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

  29. locked as resolved and limited conversation to collaborators on Aug 6, 2021
  30. changed the title [-]Navigator 2.0[/-] [+]Router[/+] on Oct 27, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listc: new featureNothing broken; request for a new capabilityf: routesNavigator, Router, and related APIs.frameworkflutter/packages/flutter repository. See also f: labels.

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions