Skip to content

Commit

Permalink
[flutter_adaptive_scaffold] Go router sample for AdaptiveScaffold (#7452
Browse files Browse the repository at this point in the history
)

This implements a sample of using GoRouter with AdaptiveScaffold. It also helps testing advanced adaptive scenarios.

*List which issues are fixed by this PR. You must list at least one issue.*

flutter/flutter#129850
  • Loading branch information
martijn00 authored Sep 4, 2024
1 parent 6e26197 commit f408578
Show file tree
Hide file tree
Showing 20 changed files with 731 additions and 2 deletions.
4 changes: 4 additions & 0 deletions packages/flutter_adaptive_scaffold/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.2.6

* Add new sample for using AdaptiveScaffold with GoRouter.

## 0.2.5

* Fix breakpoint not being active in certain cases like foldables.
Expand Down
3 changes: 3 additions & 0 deletions packages/flutter_adaptive_scaffold/example/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Examples

There are several examples listed in this directory:
You can run the following commands in the example directory to see the appropriate demos:

Expand All @@ -7,3 +8,5 @@ You can run the following commands in the example directory to see the appropria
`flutter run lib/adaptive_layout_demo.dart` to see a simple usage of AdaptiveLayout.

`flutter run lib/adaptive_scaffold_demo.dart` to see a simple usage of AdaptiveScaffold.

`flutter run lib/go_router_demo.dart` to see usage of AdaptiveScaffold with GoRouter and some advanced scenarios like auth handling and branches.
33 changes: 33 additions & 0 deletions packages/flutter_adaptive_scaffold/example/lib/go_router_demo.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// 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 'go_router_demo/app_router.dart';

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

/// The main application widget for this example.
class MyApp extends StatelessWidget {
/// Creates a const main application widget.
const MyApp({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp.router(
restorationScopeId: 'demo',
routerConfig: AppRouter.router,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue,
dynamicSchemeVariant: DynamicSchemeVariant.vibrant,
primary: Colors.blue,
),
useMaterial3: true,
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// 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';

import 'pages/pages.dart';
import 'scaffold_shell.dart';

/// The root navigator key for the main router of the app.
final GlobalKey<NavigatorState> rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');

final GlobalKey<NavigatorState> _homeNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'home');
final GlobalKey<NavigatorState> _counterNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'counter');
final GlobalKey<NavigatorState> _moreNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'more');

/// The [AppRouter] maintains the main route configuration for the app.
///
/// Routes that are `fullScreenDialogs` should also set `_rootNavigatorKey` as
/// the `parentNavigatorKey` to ensure that the dialog is displayed correctly.
class AppRouter {
/// The authentication status of the user.
static ValueNotifier<bool> authenticatedNotifier = ValueNotifier<bool>(false);

/// The router with the routes of pages that should be displayed.
static final GoRouter router = GoRouter(
navigatorKey: rootNavigatorKey,
debugLogDiagnostics: true,
errorPageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(child: NavigationErrorPage());
},
redirect: (BuildContext context, GoRouterState state) {
if (state.uri.path == '/') {
return HomePage.path;
}
return null;
},
refreshListenable: authenticatedNotifier,
routes: <RouteBase>[
_unauthenticatedRoutes,
_authenticatedRoutes,
..._openRoutes,
],
);

static final GoRoute _unauthenticatedRoutes = GoRoute(
name: LoginPage.name,
path: LoginPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(child: LoginPage());
},
redirect: (BuildContext context, GoRouterState state) {
if (authenticatedNotifier.value) {
return HomePage.path;
}
return null;
},
routes: <RouteBase>[
GoRoute(
name: ForgotPasswordPage.name,
path: ForgotPasswordPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(
child: ForgotPasswordPage(),
);
},
),
],
);

static final StatefulShellRoute _authenticatedRoutes =
StatefulShellRoute.indexedStack(
parentNavigatorKey: rootNavigatorKey,
builder: (
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
return ScaffoldShell(navigationShell: navigationShell);
},
redirect: (BuildContext context, GoRouterState state) {
if (!authenticatedNotifier.value) {
return LoginPage.path;
}
return null;
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
navigatorKey: _homeNavigatorKey,
routes: <RouteBase>[
GoRoute(
name: HomePage.name,
path: HomePage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const NoTransitionPage<void>(
child: HomePage(),
);
},
routes: <RouteBase>[
GoRoute(
name: DetailOverviewPage.name,
path: DetailOverviewPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(
child: DetailOverviewPage(),
);
},
routes: <RouteBase>[
GoRoute(
name: DetailPage.name,
path: DetailPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return MaterialPage<void>(
child: DetailPage(
itemName: state.uri.queryParameters['itemName']!),
);
},
),
]),
GoRoute(
name: DetailModalPage.name,
path: DetailModalPage.path,
parentNavigatorKey: rootNavigatorKey,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(
fullscreenDialog: true,
child: DetailModalPage(),
);
},
),
],
),
],
),
StatefulShellBranch(
navigatorKey: _counterNavigatorKey,
routes: <RouteBase>[
GoRoute(
name: CounterPage.name,
path: CounterPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const NoTransitionPage<void>(child: CounterPage());
},
),
],
),
StatefulShellBranch(
navigatorKey: _moreNavigatorKey,
routes: <RouteBase>[
GoRoute(
name: MorePage.name,
path: MorePage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const NoTransitionPage<void>(
key: ValueKey<String>(MorePage.name),
child: MorePage(),
);
},
routes: <RouteBase>[
GoRoute(
path: ProfilePage.path,
name: ProfilePage.name,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(child: ProfilePage());
},
),
GoRoute(
name: SettingsPage.name,
path: SettingsPage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(child: SettingsPage());
},
),
],
),
],
),
],
);

static final List<GoRoute> _openRoutes = <GoRoute>[
GoRoute(
name: LanguagePage.name,
path: LanguagePage.path,
pageBuilder: (BuildContext context, GoRouterState state) {
return const MaterialPage<void>(
child: LanguagePage(),
);
},
),
];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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';

/// The counter page.
class CounterPage extends StatelessWidget {
/// Construct the counter page.
const CounterPage({super.key});

/// The path for the counter page.
static const String path = '/counter';

/// The name for the counter page.
static const String name = 'Counter';

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter Page'),
),
body: const Center(
child: Text('Counter Page'),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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';

/// The detail modal page.
class DetailModalPage extends StatelessWidget {
/// Construct the detail modal page.
const DetailModalPage({super.key});

/// The path for the detail modal page.
static const String path = 'detail-modal';

/// The name for the detail modal page.
static const String name = 'DetailModal';

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Detail Modal Page'),
),
body: const Center(
child: Text('Detail modal Page'),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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';

import 'detail_page.dart';

/// The detail overview page.
class DetailOverviewPage extends StatelessWidget {
/// Construct the detail overview page.
const DetailOverviewPage({super.key});

/// The path for the detail page.
static const String path = 'detail-overview';

/// The name for the detail page.
static const String name = 'DetailOverview';

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Detail Overview Page'),
),
body: ListView.builder(
itemCount: 10,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
onTap: () {
context.goNamed(
DetailPage.name,
queryParameters: <String, String>{'itemName': '$index'},
);
},
);
},
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// 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';

/// The detail page.
class DetailPage extends StatelessWidget {
/// Construct the detail page.
const DetailPage({super.key, required this.itemName});

/// The path for the detail page.
static const String path = 'detail';

/// The name for the detail page.
static const String name = 'Detail';

/// The item name for the detail page.
final String itemName;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Detail Page'),
),
body: Center(
child: Text('Detail Page: $itemName'),
),
);
}
}
Loading

0 comments on commit f408578

Please sign in to comment.