Skip to content

[go_router] Fail to restore widget states after "restart and restore" when using go_router #117683

@miquelbeltran

Description

@miquelbeltran

Steps to Reproduce

Given two similar MaterialApp, one using go_router and one who doesn't, the one using go_router is unable to restore the screen state when the app is restarted.

The best way to see this problem is by running the included code sample.

Their implementation is very similar:

  MaterialApp.router(
      restorationScopeId: 'app',
      routerConfig: GoRouter(
        restorationScopeId: 'router',
        routes: [
          GoRoute(
            path: '/',
            builder: (context, state) => const ScrollScreen(),
          ),
        ],
      ),
    );

and:

  MaterialApp(
      restorationScopeId: 'app',
      home: ScrollScreen(),
    );

The one using GoRouter is unable to restore the state of the ScrollScreen when the app is restarted.

You can see this behavior with the attached widget tests in the "code" section.

I've looked for similar reported issues, but I couldn't find one that explains this exact problem:

Expected results:

Both cases should work the same way. The screen state should be restored after restart just like when using a MaterialApp without GoRouter

Actual results:

In this case, the state of the screen when using GoRouter is not restored, and this can be seen by using the included widget tests.

Code sample

Add go_router to your project, then paste the following three classes into your main.dart.

Also, copy the two widget tests into your widget_test.dart to run them.

First class is the AppGoRoute, which is a simple MaterialApp that uses GoRouter.

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      restorationScopeId: 'app',
      title: 'Flutter Demo',
      routerConfig: GoRouter(
        restorationScopeId: 'router',
        routes: [
          GoRoute(
            path: '/',
            builder: (context, state) => const ScrollScreen(),
          ),
        ],
      ),
    );
  }
}

The second class is AppSimple, which is a simple MaterialApp that doesn't use GoRouter.

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      restorationScopeId: 'app',
      title: 'Flutter Demo',
      home: ScrollScreen(),
    );
  }
}

ScrollScreen is a simple Scaffold with a ListView of a 1000 items.

class ScrollScreen extends StatelessWidget {
  const ScrollScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      restorationId: 'scroll-screen',
      body: ListView.builder(
        restorationId: 'list',
        itemCount: 1000,
        itemBuilder: (context, index) => ListTile(
          title: Text('Item $index'),
        ),
      ),
    );
  }
}

Test 1: Fails: This test fails, as when the screen state is restored, the scroll position is lost and is back at the item 0.

This test uses the AppGoRoute, which uses GoRouter internally to handle navigation.

  testWidgets('restoration test go_router', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(
      const RootRestorationScope(
        restorationId: 'root',
        child: AppGoRoute(),
      ),
    );

    expect(find.text('Item 0'), findsOneWidget);
    expect(find.text('Item 500'), findsNothing);

    await tester.scrollUntilVisible(
      find.text('Item 100'),
      10,
      maxScrolls: 10000,
    );

    expect(find.text('Item 0'), findsNothing);
    expect(find.text('Item 100'), findsOneWidget);

    await tester.restartAndRestore();

    // scroll position restored
    expect(find.text('Item 0'), findsNothing); // <-- fails here
    expect(find.text('Item 100'), findsOneWidget);
  });

Test 2: Passes.

This test uses AppSimple, same test content, and the test passes.

  testWidgets('restoration test without go_router', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(
      const RootRestorationScope(
        restorationId: 'root',
        child: AppSimple(),
      ),
    );

    expect(find.text('Item 0'), findsOneWidget);
    expect(find.text('Item 500'), findsNothing);

    await tester.scrollUntilVisible(
      find.text('Item 100'),
      10,
      maxScrolls: 10000,
    );

    expect(find.text('Item 0'), findsNothing);
    expect(find.text('Item 100'), findsOneWidget);

    await tester.restartAndRestore();

    // scroll position restored
    expect(find.text('Item 0'), findsNothing);
    expect(find.text('Item 100'), findsOneWidget);
  });
Logs

These are the test results:

/home/miquel/dev/tools/flutter/bin/flutter --no-color test --machine --start-paused test/widget_test.dart
Testing started at 16:17 ...

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: no matching nodes in the widget tree
  Actual: _TextFinder:<exactly one widget with text "Item 0" (ignoring offstage widgets): Text("Item
0", dependencies: [DefaultSelectionStyle, DefaultTextStyle, MediaQuery])>
   Which: means one was found but none were expected

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (file:///home/miquel/tmp/go_route_push_shell/test/widget_test.dart:38:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

This was caught by the test expectation on the following line:
  file:///home/miquel/tmp/go_route_push_shell/test/widget_test.dart line 38
The test description was:
  restoration test go_router
════════════════════════════════════════════════════════════════════════════════════════════════════

Test failed. See exception logs above.
The test description was: restoration test go_router
flutter analyze
Analyzing go_route_push_shell...                                        
No issues found! (ran in 1.0s)

flutter doctor -v
[✓] Flutter (Channel stable, 3.3.10, on Ubuntu 22.04.1 LTS 5.15.0-56-generic, locale en_US.UTF-8)
    • Flutter version 3.3.10 on channel stable at /home/miquel/dev/tools/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 135454af32 (12 days ago), 2022-12-15 07:36:55 -0800
    • Engine revision 3316dd8728
    • Dart version 2.18.6
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    • Android SDK at /home/miquel/Android/Sdk
    • Platform android-33, build-tools 33.0.1
    • Java binary at:
      /home/miquel/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • Chrome at google-chrome

[✓] Linux toolchain - develop for Linux desktop
    • Homebrew clang version 15.0.5
    • cmake version 3.22.1
    • ninja version 1.10.1
    • pkg-config version 0.29.2

[✓] Android Studio (version 2021.3)
    • Android Studio at /home/miquel/.local/share/JetBrains/Toolbox/apps/AndroidStudio/ch-0/213.7172.25.2113.9123335
    • 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.13+0-b1751.21-8125866)

[✓] IntelliJ IDEA Community Edition (version 2022.3)
    • IntelliJ at /home/miquel/.local/share/JetBrains/Toolbox/apps/IDEA-C/ch-0/223.8214.52
    • Flutter plugin version 71.2.6
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart

[✓] VS Code (version 1.66.2)
    • VS Code at /usr/share/code
    • Flutter extension version 3.40.0

[✓] VS Code
    • VS Code at /snap/code/current
    • Flutter extension version 3.40.0

[✓] Connected device (2 available)
    • Linux (desktop) • linux  • linux-x64      • Ubuntu 22.04.1 LTS 5.15.0-56-generic
    • Chrome (web)    • chrome • web-javascript • Google Chrome 108.0.5359.124

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions