Skip to content

[Go Router] Popping state and re-rendering scaffold at the same time doesn't update the URL on web  #150312

@premiumbiscuit

Description

@premiumbiscuit

Steps to reproduce

In the given sample:

  1. Press Push on the root screen
  2. Press push again on route A
  3. Press "Go Back" on screen B

Expected results

The screen is popped and the URL is updated

Actual results

The screen is popped and the URL is still /#/a/b on web. This only happens if counterStream.increment(); is called just before popping. If it's omitted, then everything works as expected.

Code sample

Code sample
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class CounterStream {
  int _counter = 0;

  final _streamController = StreamController<int>.broadcast();

  Stream<int> get stateStream => _streamController.stream.asBroadcastStream();

  void increment() {
    _streamController.sink.add(++_counter);
  }

  Future<void> delayedRerender() async {
    increment();
    increment();
  }

  void dispose() {
    _streamController.close();
  }
}

final CounterStream counterStream = CounterStream();

class StreamListener extends ChangeNotifier {
  StreamListener(Stream<dynamic> stream) {
    notifyListeners();

    _subscription = stream.asBroadcastStream().listen((_) {
      notifyListeners();
    });
  }

  late final StreamSubscription<dynamic> _subscription;

  @override
  void notifyListeners() {
    super.notifyListeners();
    print("refreshing the router");
  }

  @override
  void dispose() {
    _subscription.cancel();
    super.dispose();
  }
}

void main() {
  GoRouter.optionURLReflectsImperativeAPIs = true;

  WidgetsFlutterBinding.ensureInitialized();

  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

final _rootNavigatorKey = GlobalKey<NavigatorState>();

final GoRouter _router = GoRouter(
  initialLocation: '/',
  navigatorKey: _rootNavigatorKey,
  refreshListenable: StreamListener(counterStream.stateStream),
  routes: <RouteBase>[
    ShellRoute(
      builder: (context, state, child) {
        return GenericPage(child: child);
      },
      routes: [
        GoRoute(
          path: '/',
          builder: (BuildContext context, GoRouterState state) =>
              const GenericPage(showPushButton: true, path: 'a'),
          routes: [
            GoRoute(
              path: 'a',
              name: "a",
              builder: (BuildContext context, GoRouterState state) =>
                  const GenericPage(showPushButton: true, path: 'b'),
              routes: [
                GoRoute(
                  path: 'b',
                  name: 'b',
                  builder: (BuildContext context, GoRouterState state) =>
                      const GenericPage(showBackButton: true),
                ),
              ],
            ),
          ],
        ),
      ],
    ),
  ],
);

class _MyAppState extends State<MyApp> {
  late StreamSubscription<int> _stateSubscription;
  int _currentState = 0;

  @override
  void initState() {
    super.initState();
    _stateSubscription = counterStream.stateStream.listen((state) {
      setState(() {
        _currentState = state;
      });
    });
  }

  @override
  void dispose() {
    _stateSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routerConfig: _router,
    );
  }
}

class GenericPage extends StatefulWidget {
  final Widget? child;
  final bool showPushButton;
  final bool showBackButton;
  final String? path;

  const GenericPage({
    this.child,
    Key? key,
    this.showPushButton = false,
    this.showBackButton = false,
    this.path,
  }) : super(key: key ?? const ValueKey<String>('ShellWidget'));

  @override
  State<GenericPage> createState() => _GenericPageState();
}

class _GenericPageState extends State<GenericPage> {
  late StreamSubscription<int> _stateSubscription;
  int _currentState = 0;

  @override
  void initState() {
    super.initState();
    _stateSubscription = counterStream.stateStream.listen((state) {
      setState(() {
        _currentState = state;
      });
    });
  }

  @override
  void dispose() {
    _stateSubscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: widget.child != null
            ? AppBar(
                title: Text('Count: $_currentState'),
              )
            : null,
        body: _buildWidget(context));
  }

  Widget _buildWidget(BuildContext context) {
    if (widget.child != null) {
      return widget.child!;
    }

    if (widget.showBackButton) {
      return TextButton(
        onPressed: () {
          // WHEN THE USER PRESSES THIS BUTTON, THE URL
          // DOESN'T CHANGE, BUT THE SCREEN DOES
          counterStream.increment(); // <- when removing this line the issue is gone
          GoRouter.of(context).pop();
        },
        child: Text("<- Go Back"),
      );
    }

    if (widget.showPushButton) {
      return TextButton(
        onPressed: () {
          GoRouter.of(context).goNamed(widget.path!);
        },
        child: Text("Push ->"),
      );
    }

    return Text('Current state: $_currentState');
  }
}

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.19.6, on macOS 14.5 23F79 darwin-x64, locale en-AU)
    • Flutter version 3.19.6 on channel stable at /Applications/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 54e66469a9 (8 weeks ago), 2024-04-17 13:08:03 -0700
    • Engine revision c4cd48e186
    • Dart version 3.3.4
    • DevTools version 2.31.1

[✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1)
    • Android SDK at /Users/shady/Library/Android/sdk
    • Platform android-33, build-tools 32.1.0-rc1
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15C500b
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7590822)

[✓] IntelliJ IDEA Ultimate Edition (version 2024.1.3)
    • IntelliJ at /Users/shady/Applications/IntelliJ IDEA Ultimate.app
    • Flutter plugin version 80.0.2
    • Dart plugin version 241.17890.8

[✓] VS Code (version 1.89.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] VS Code (version 1.86.0-insider)
    • VS Code at /Applications/Visual Studio Code - Insiders.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (2 available)
    • macOS (desktop)              • macos                     • darwin-x64     • macOS 14.5 23F79
      darwin-x64
    • Chrome (web)                 • chrome                    • web-javascript • Google Chrome
      125.0.6422.142

[✓] Network resources
    • All expected network resources are available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listfound in release: 3.22Found to occur in 3.22found in release: 3.23Found to occur in 3.23has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.platform-webWeb applications specificallyteam-frameworkOwned by Framework teamtriaged-frameworkTriaged by Framework team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions