Skip to content

Commit

Permalink
[go_router] Refactored RouteMatchList and imperative APIs (flutter#5497)
Browse files Browse the repository at this point in the history
This pr refactor RouteMatchList to be a tree structure.

Added a common base class RouteMatchBase. It is extended by both RouteMatch and ShellRouteMatch.

The RouteMatch is for GoRoute, and is always a leaf node

The ShellRouteMatch is for ShellRouteBase, and is always and intermediate node with a list of child RouteMatchBase[s].

This pr also redo how push is processed. Will add a doc explain this shortly.

This is a breaking change, will write a migration guide soon.

fixes flutter/flutter#134524
fixes flutter/flutter#130406
fixes flutter/flutter#126365
fixes flutter/flutter#125752
fixes flutter/flutter#120791
fixes flutter/flutter#120665
fixes flutter/flutter#113001
fixes flutter/flutter#110512
  • Loading branch information
chunhtai authored and arc-yong committed Jun 14, 2024
1 parent 5e79393 commit 662ad05
Show file tree
Hide file tree
Showing 23 changed files with 1,688 additions and 892 deletions.
7 changes: 7 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 13.0.0

- Refactors `RouteMatchList` and imperative APIs.
- **BREAKING CHANGE**:
- RouteMatchList structure changed.
- Matching logic updated.

## 12.1.3

* Fixes a typo in `navigation.md`.
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ See the API documentation for details on the following topics:
- [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html)

## Migration Guides
- [Migrating to 13.0.0](https://flutter.dev/go/go-router-v13-breaking-changes).
- [Migrating to 12.0.0](https://flutter.dev/go/go-router-v12-breaking-changes).
- [Migrating to 11.0.0](https://flutter.dev/go/go-router-v11-breaking-changes).
- [Migrating to 10.0.0](https://flutter.dev/go/go-router-v10-breaking-changes).
Expand Down Expand Up @@ -67,4 +68,3 @@ The project follows the same priority system as flutter framework.
[P3](https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc+label%3Ateam-go_router+label%3AP3+)

[Package PRs](https://github.com/flutter/packages/pulls?q=is%3Apr+is%3Aopen+label%3A%22p%3A+go_router%22%2C%22p%3A+go_router_builder%22)

21 changes: 21 additions & 0 deletions packages/go_router/doc/navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,27 @@ Navigator.of(context).push(
);
```

The behavior may change depends on the shell route in current screen and the new screen.

If pushing a new screen without any shell route onto the current screen with shell route, the new
screen is placed entirely on top of the current screen.

![An animation shows a new screen push on top of current screen](https://flutter.github.io/assets-for-api-docs/assets/go_router/push_regular_route.gif)

If pushing a new screen with the same shell route as the current screen, the new
screen is placed inside of the shell.

![An animation shows pushing a new screen with the same shell as current screen](https://flutter.github.io/assets-for-api-docs/assets/go_router/push_same_shell.gif)

If pushing a new screen with the different shell route as the current screen, the new
screen along with the shell is placed entirely on top of the current screen.

![An animation shows pushing a new screen with the different shell as current screen](https://flutter.github.io/assets-for-api-docs/assets/go_router/push_different_shell.gif)

To try out the behavior yourself, see
[push_with_shell_route.dart](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/extra_codec.dart).


## Returning values
Waiting for a value to be returned:

Expand Down
160 changes: 160 additions & 0 deletions packages/go_router/example/lib/push_with_shell_route.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

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

// This scenario demonstrates the behavior when pushing ShellRoute in various
// scenario.
//
// This example have three routes, /shell1, /shell2, and /regular-route. The
// /shell1 and /shell2 are nested in different ShellRoutes. The /regular-route
// is a simple GoRoute.

void main() {
runApp(PushWithShellRouteExampleApp());
}

/// An example demonstrating how to use [ShellRoute]
class PushWithShellRouteExampleApp extends StatelessWidget {
/// Creates a [PushWithShellRouteExampleApp]
PushWithShellRouteExampleApp({super.key});

final GoRouter _router = GoRouter(
initialLocation: '/home',
debugLogDiagnostics: true,
routes: <RouteBase>[
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldForShell1(child: child);
},
routes: <RouteBase>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) {
return const Home();
},
),
GoRoute(
path: '/shell1',
pageBuilder: (_, __) => const NoTransitionPage<void>(
child: Center(
child: Text('shell1 body'),
),
),
),
],
),
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldForShell2(child: child);
},
routes: <RouteBase>[
GoRoute(
path: '/shell2',
builder: (BuildContext context, GoRouterState state) {
return const Center(child: Text('shell2 body'));
},
),
],
),
GoRoute(
path: '/regular-route',
builder: (BuildContext context, GoRouterState state) {
return const Scaffold(
body: Center(child: Text('regular route')),
);
},
),
],
);

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

/// Builds the "shell" for /shell1
class ScaffoldForShell1 extends StatelessWidget {
/// Constructs an [ScaffoldForShell1].
const ScaffoldForShell1({
required this.child,
super.key,
});

/// The widget to display in the body of the Scaffold.
/// In this sample, it is a Navigator.
final Widget child;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('shell1')),
body: child,
);
}
}

/// Builds the "shell" for /shell1
class ScaffoldForShell2 extends StatelessWidget {
/// Constructs an [ScaffoldForShell1].
const ScaffoldForShell2({
required this.child,
super.key,
});

/// The widget to display in the body of the Scaffold.
/// In this sample, it is a Navigator.
final Widget child;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('shell2')),
body: child,
);
}
}

/// The screen for /home
class Home extends StatelessWidget {
/// Constructs a [Home] widget.
const Home({super.key});

@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextButton(
onPressed: () {
GoRouter.of(context).push('/shell1');
},
child: const Text('push the same shell route /shell1'),
),
TextButton(
onPressed: () {
GoRouter.of(context).push('/shell2');
},
child: const Text('push the different shell route /shell2'),
),
TextButton(
onPressed: () {
GoRouter.of(context).push('/regular-route');
},
child: const Text('push the regular route /regular-route'),
),
],
),
);
}
}
45 changes: 45 additions & 0 deletions packages/go_router/example/test/push_with_shell_route_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter_test/flutter_test.dart';
import 'package:go_router/go_router.dart';
import 'package:go_router_examples/push_with_shell_route.dart' as example;

void main() {
testWidgets('example works', (WidgetTester tester) async {
await tester.pumpWidget(example.PushWithShellRouteExampleApp());
expect(find.text('shell1'), findsOneWidget);

await tester.tap(find.text('push the same shell route /shell1'));
await tester.pumpAndSettle();
expect(find.text('shell1'), findsOneWidget);
expect(find.text('shell1 body'), findsOneWidget);

find.text('shell1 body').evaluate().first.pop();
await tester.pumpAndSettle();
expect(find.text('shell1'), findsOneWidget);
expect(find.text('shell1 body'), findsNothing);

await tester.tap(find.text('push the different shell route /shell2'));
await tester.pumpAndSettle();
expect(find.text('shell1'), findsNothing);
expect(find.text('shell2'), findsOneWidget);
expect(find.text('shell2 body'), findsOneWidget);

find.text('shell2 body').evaluate().first.pop();
await tester.pumpAndSettle();
expect(find.text('shell1'), findsOneWidget);
expect(find.text('shell2'), findsNothing);

await tester.tap(find.text('push the regular route /regular-route'));
await tester.pumpAndSettle();
expect(find.text('shell1'), findsNothing);
expect(find.text('regular route'), findsOneWidget);

find.text('regular route').evaluate().first.pop();
await tester.pumpAndSettle();
expect(find.text('shell1'), findsOneWidget);
expect(find.text('regular route'), findsNothing);
});
}
Loading

0 comments on commit 662ad05

Please sign in to comment.