Skip to content

[go_router] replace should reuse the same key as the popped page when possible #115902

@ValentinVignal

Description

@ValentinVignal

This comes back to this issue #114234 and this PR flutter/packages#2747.

Before the keys of pages being pushed then replaced were:

[<'/family/:fid-p1'>] // <- Pushed
[<'/family/:fid'>] // <- Replace
[<'/family/:fid'>] // <- Replace
[<'/family/:fid'>] // <- Replace
...

and I thought the pages keys should have been (what was done in flutter/packages#2747)

[<'/family/:fid-p1'>] // <- Pushed
[<'/family/:fid-p2'>] // <- Replace
[<'/family/:fid-p3'>] // <- Replace
[<'/family/:fid-p4'>] // <- Replace
...

because the example given in #114234 was with a Dialog and it wasn't rebuilding when the same key was reused.
But after more investigation, it looks like it should have rebuilt when the same key was reused and this might be a bug with the dialog route itself because it doesn't rebuild when it should. See #115639

So now, because the keys are different after a replace, the state is lost when it is being used.

So I think the keys of the page should instead be (the key of the popped page should be reused when possible to preserve the state of the widgets).

[<'/family/:fid-p1'>] // <- Pushed
[<'/family/:fid-p1'>] // <- Replace
[<'/family/:fid-p1'>] // <- Replace
[<'/family/:fid-p1'>] // <- Replace
...

Steps to Reproduce

  1. Execute flutter run on the code sample see "Code sample" section below
  2. Click on a todo in the todos list
  3. Go left and/or right to use replace.
  4. Notice that the state is lost: now (set in the initState) displays a different time on each page.

Expected results:

The state should be preserved when possible

Actual results:

The state is not preserved.

Code sample

Or you can checkout

https://github.com/ValentinVignal/flutter_app_stable/tree/go-router/replace-use-same-key

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

void main() {
  runApp(const MyApp());
}

class Todo {
  const Todo({
    required this.id,
    required this.description,
  });

  factory Todo.fromIndex(int index) {
    return Todo(
      id: index,
      description: 'Todo $index',
    );
  }

  final int id;
  final String description;
}

final router = GoRouter(
  initialLocation: '/todos',
  routes: [
    GoRoute(
      path: '/todos',
      builder: (_, __) => const TodosScreen(),
      routes: [
        GoRoute(
            path: ':id',
            builder: (_, state) {
              final uri = Uri.parse(state.location);
              final id = int.parse(uri.pathSegments.last);
              return TodoScreen(id: id);
            }),
      ],
    ),
  ],
);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router,
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Todos'),
      ),
      body: ListView.builder(
        itemBuilder: (context, index) => ListTile(
          title: Text(Todo.fromIndex(index).description),
          onTap: () => context.go('/todos/$index'),
        ),
      ),
    );
  }
}

class TodoScreen extends StatefulWidget {
  const TodoScreen({
    required this.id,
    super.key,
  });

  final int id;

  @override
  State<TodoScreen> createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  late final DateTime now;
  @override
  void initState() {
    super.initState();
    now = DateTime.now();
  }

  @override
  Widget build(BuildContext context) {
    final todo = Todo.fromIndex(widget.id);
    return Scaffold(
      appBar: AppBar(
        title: Text('Todo ${todo.id}'),
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          IconButton(
            onPressed: () {
              router.replace('/todos/${widget.id - 1}');
            },
            icon: const Icon(Icons.arrow_back),
          ),
          Expanded(
            child: Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('Todo ${todo.id}: ${todo.description}'),
                  Text('Now: $now')
                ],
              ),
            ),
          ),
          IconButton(
            onPressed: () {
              router.replace('/todos/${widget.id + 1}');
            },
            icon: const Icon(Icons.arrow_forward),
          ),
        ],
      ),
    );
  }
}
Logs
Launching lib/main.dart on macOS in debug mode...
Building macOS application...                                           
Syncing files to device macOS...                                   113ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

💪 Running with sound null safety 💪

An Observatory debugger and profiler on macOS is available at: http://127.0.0.1:64569/LEROT4Ndx0w=/
The Flutter DevTools debugger and profiler on macOS is available at:
http://127.0.0.1:9101?uri=http://127.0.0.1:64569/LEROT4Ndx0w=/
Analyzing flutter_app_stable...                                         
No issues found! (ran in 5.8s)
[✓] Flutter (Channel stable, 3.3.8, on macOS 11.6.8 20G730 darwin-x64, locale en-GB)
    • Flutter version 3.3.8 on channel stable at /Users/valentin/flutter/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 52b3dc25f6 (2 weeks ago), 2022-11-09 12:09:26 +0800
    • Engine revision 857bd6b74c
    • Dart version 2.18.4
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /usr/local/Caskroom/android-sdk/4333796
    • Platform android-33, build-tools 30.0.3
    • 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 13.2.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 13C100
    • CocoaPods version 1.11.3

[✓] 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)

[✓] VS Code (version 1.73.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.52.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 11.6.8 20G730 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 107.0.5304.110

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

• No issues found!
Video
Screen.Recording.2022-11-23.at.3.14.41.PM.mov

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.6Found to occur in 3.6has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.r: fixedIssue is closed as already fixed in a newer version

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions