Flutter go_router: push() vs go() and web URL changes
Asked Answered
I

3

6

I am facing a use case I don't know the proper way to deal with.

Please see here the simple application I have made: https://dartpad.dev/?id=5a00b315613990d8c3ead6e26bc2df4c

This tries to simulate the typical scenario where you have a record list page with data recovered from a data base, click on one of the records, make some changes and click the "Save" button to go back to the list page and see those changes updated.

If I use context.push(), I can see the changes correctly because it returns a Future so I can wait until context.pop() is executed in the detail screen and then I run getDataFromDataBase() to refresh the data from the database.

The problem I have with using context.push() is the URL is not updated when I run this for the web.

If I use context.go() instead then the URL is properly updated but since this returns void I cannot properly wait to update the records after context.pop()

I feel I am missing something because this must be a very typical scenario. Let's see if you can point me in the right direction:

  1. Is it the expected behaviour that context.push() doesn't modify the URL?
  2. In case I use context.go() instead (which by the way seems to be the recommended way to navigate according to the official documentation since imperative navigation can cause some problems: https://pub.dev/documentation/go_router/latest/topics/Navigation-topic.html). What's the proper way to manage this scenario? How to update data after context.pop()

Thank you.

Injurious answered 20/7, 2023 at 14:26 Comment(0)
G
3

ATM, I'm looking for an answer to your first question myself so I can't answer that.

As for the second one, you can work around it by creating a Promise, and pass a function fulfilling that promise into your editing dialog.

Something like this:

class MyPage extends StatelessWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      // ...
      TextButton(
        onPressed: () async {
          final promise = Completer<bool>();
          context.go('/somePath', extra: promise.complete);
          final editResult = await promise.future;
          // ....
        },
        child: Text('Edit'),
      )
    ]);
  }
}

You can then pick the function up from the extra-field in your route, pass it into your dialog and call it when editing is done.

Gilburt answered 11/8, 2023 at 9:51 Comment(3)
Thanks @tjarvstrand. Anyway I ended up moving to auto_route package as I found it more convenient for my case. This one updates the URL properly with its core functions. As per the second point, I found it easy to do it through observers to trigger certain operations on push/pop: pub.dev/packages/auto_route#navigation-observersInjurious
FWIW I found this old issue, so I don't think it's intended behavior: github.com/flutter/flutter/issues/115962. I created a new one: github.com/flutter/flutter/issues/132430. With regards to auto_router, it seems more convenient, but I don't really trust it. It's unbelievably complex for what it does, code quality is so-so, and documentation about what actually goes on under the hood is non-existent.Gilburt
Thanks @tjarvstrand. So it seems it's related to this option: pub.dev/documentation/go_router/latest/go_router/GoRouter/… but anyways it's not recommended to set it to true, imperative navigation is strongly discouraged: pub.dev/documentation/go_router/latest/topics/…. We'll try to find a workaround and follow the recommended guidelines. Thanks again.Injurious
B
7

I had the same problem and use context.push() a lot of times to make sure, my content updates when I navigate back with context.pop(). I tried the solution of @tjarvstrand but the main drawback for me is, that the navigation arrow in the appbar get's lost.

After searching a little bit, I found the following github ticket: https://github.com/flutter/flutter/issues/129893#issuecomment-1617762284

In the migration guide from version 8.0.0 there is a detailed explanation. Just put the following line in your main.dart file and the browser URL is updated:

GoRouter.optionURLReflectsImperativeAPIs = true;

In my case I wanted to have clean URLs in my web application, so my main.dart looks like this:

void main() {
  setPathUrlStrategy(); // see https://pub.dev/packages/url_strategy
  GoRouter.optionURLReflectsImperativeAPIs = true;
  ...
  runApp(MyApp());

Thanks to GoRouter.optionURLReflectsImperativeAPIs = true; my code for routing can look like this:

// navigate to another window
onTap: () async {
  var result = await context.push('/my-new-route');
  if (result == true) {
    setState(() {});
  }
},

// and navigate back again
if (context.canPop()) {
  context.pop(true); // 99% case
} else {
  context.go('/'); // if page was called directly this is my fallback url
}

and I have the default navigation arrow visible:

Flutter GUI example

Board answered 9/2 at 0:35 Comment(1)
There's something different about the url though. It doesn't maintain the new URL on app root widget rebuild. It's like it's not quite a full URL, just a visual URL only for the user to see, without actually adding an entry to the router/browser history (observable when resizing the browser window to display different layouts which rebuilds the root app widget).Pesthole
J
5

in my case GoRouter.optionURLReflectsImperativeAPIs = true; helped me and I am able to see URL changes in the browser

Future<void> main() async {

// -->>>>>> place it here
  GoRouter.optionURLReflectsImperativeAPIs = true;

  runApp(
    const MyApp(),
  );
}

Hope that will help

Jentoft answered 13/2 at 19:54 Comment(0)
G
3

ATM, I'm looking for an answer to your first question myself so I can't answer that.

As for the second one, you can work around it by creating a Promise, and pass a function fulfilling that promise into your editing dialog.

Something like this:

class MyPage extends StatelessWidget {
  const MyPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      // ...
      TextButton(
        onPressed: () async {
          final promise = Completer<bool>();
          context.go('/somePath', extra: promise.complete);
          final editResult = await promise.future;
          // ....
        },
        child: Text('Edit'),
      )
    ]);
  }
}

You can then pick the function up from the extra-field in your route, pass it into your dialog and call it when editing is done.

Gilburt answered 11/8, 2023 at 9:51 Comment(3)
Thanks @tjarvstrand. Anyway I ended up moving to auto_route package as I found it more convenient for my case. This one updates the URL properly with its core functions. As per the second point, I found it easy to do it through observers to trigger certain operations on push/pop: pub.dev/packages/auto_route#navigation-observersInjurious
FWIW I found this old issue, so I don't think it's intended behavior: github.com/flutter/flutter/issues/115962. I created a new one: github.com/flutter/flutter/issues/132430. With regards to auto_router, it seems more convenient, but I don't really trust it. It's unbelievably complex for what it does, code quality is so-so, and documentation about what actually goes on under the hood is non-existent.Gilburt
Thanks @tjarvstrand. So it seems it's related to this option: pub.dev/documentation/go_router/latest/go_router/GoRouter/… but anyways it's not recommended to set it to true, imperative navigation is strongly discouraged: pub.dev/documentation/go_router/latest/topics/…. We'll try to find a workaround and follow the recommended guidelines. Thanks again.Injurious

© 2022 - 2024 — McMap. All rights reserved.