diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 3e73bd1300..471351f7a2 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## NEXT + +- Cleans up examples + ## 4.2.7 - Update README diff --git a/packages/go_router/example/lib/named_routes.dart b/packages/go_router/example/lib/named_routes.dart index ef58c520db..a8f9ce3f74 100644 --- a/packages/go_router/example/lib/named_routes.dart +++ b/packages/go_router/example/lib/named_routes.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'shared/data.dart'; - // This scenario demonstrates how to navigate using named locations instead of // URLs. // @@ -15,6 +15,37 @@ import 'shared/data.dart'; // then be used in context.namedLocation to be translate back to the actual URL // location. +final Map _families = const JsonDecoder().convert(''' +{ + "f1": { + "name": "Doe", + "people": { + "p1": { + "name": "Jane", + "age": 23 + }, + "p2": { + "name": "John", + "age": 6 + } + } + }, + "f2": { + "name": "Wong", + "people": { + "p1": { + "name": "June", + "age": 51 + }, + "p2": { + "name": "Xin", + "age": 44 + } + } + } +} +'''); + void main() => runApp(App()); /// The main app. @@ -41,23 +72,20 @@ class App extends StatelessWidget { name: 'home', path: '/', builder: (BuildContext context, GoRouterState state) => - HomeScreen(families: Families.data), + const HomeScreen(), routes: [ GoRoute( name: 'family', path: 'family/:fid', builder: (BuildContext context, GoRouterState state) => - FamilyScreen( - family: Families.family(state.params['fid']!), - ), + FamilyScreen(fid: state.params['fid']!), routes: [ GoRoute( name: 'person', path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - final Person person = family.person(state.params['pid']!); - return PersonScreen(family: family, person: person); + return PersonScreen( + fid: state.params['fid']!, pid: state.params['pid']!); }, ), ], @@ -71,10 +99,7 @@ class App extends StatelessWidget { /// The home screen that shows a list of families. class HomeScreen extends StatelessWidget { /// Creates a [HomeScreen]. - const HomeScreen({required this.families, Key? key}) : super(key: key); - - /// The list of families. - final List families; + const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -84,11 +109,11 @@ class HomeScreen extends StatelessWidget { ), body: ListView( children: [ - for (final Family f in families) + for (final String fid in _families.keys) ListTile( - title: Text(f.name), + title: Text(_families[fid]['name']), onTap: () => context.go(context.namedLocation('family', - params: {'fid': f.id})), + params: {'fid': fid})), ) ], ), @@ -99,45 +124,54 @@ class HomeScreen extends StatelessWidget { /// The screen that shows a list of persons in a family. class FamilyScreen extends StatelessWidget { /// Creates a [FamilyScreen]. - const FamilyScreen({required this.family, Key? key}) : super(key: key); + const FamilyScreen({required this.fid, Key? key}) : super(key: key); - /// The family to display. - final Family family; + /// The id family to display. + final String fid; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go(context.namedLocation( - 'person', - params: {'fid': family.id, 'pid': p.id}, - queryParams: {'qid': 'quid'}, - )), - ), - ], - ), - ); + Widget build(BuildContext context) { + final Map people = + _families[fid]['people'] as Map; + return Scaffold( + appBar: AppBar(title: Text(_families[fid]['name'])), + body: ListView( + children: [ + for (final String pid in people.keys) + ListTile( + title: Text(people[pid]['name']), + onTap: () => context.go(context.namedLocation( + 'person', + params: {'fid': fid, 'pid': pid}, + queryParams: {'qid': 'quid'}, + )), + ), + ], + ), + ); + } } /// The person screen. class PersonScreen extends StatelessWidget { /// Creates a [PersonScreen]. - const PersonScreen({required this.family, required this.person, Key? key}) + const PersonScreen({required this.fid, required this.pid, Key? key}) : super(key: key); - /// The family this person belong to. - final Family family; + /// The id of family this person belong to. + final String fid; - /// The person to be displayed. - final Person person; + /// The id of the person to be displayed. + final String pid; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); + Widget build(BuildContext context) { + final Map family = _families[fid]; + final Map person = family['people'][pid]; + return Scaffold( + appBar: AppBar(title: Text(person['name'])), + body: Text( + '${person['name']} ${family['name']} is ${person['age']} years old'), + ); + } } diff --git a/packages/go_router/example/lib/others/async_data.dart b/packages/go_router/example/lib/others/async_data.dart deleted file mode 100644 index fbe87e23f9..0000000000 --- a/packages/go_router/example/lib/others/async_data.dart +++ /dev/null @@ -1,293 +0,0 @@ -// 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. - -// ignore_for_file: use_late_for_private_fields_and_variables - -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../shared/data.dart'; - -void main() => runApp(App()); - -/// The main app. -class App extends StatelessWidget { - /// Creates a [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Async Data'; - - /// Repository to query data from. - static final Repository repo = Repository(); - - @override - Widget build(BuildContext context) => MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - ); - - late final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - const HomeScreen(), - routes: [ - GoRoute( - path: 'family/:fid', - builder: (BuildContext context, GoRouterState state) => - FamilyScreen( - fid: state.params['fid']!, - ), - routes: [ - GoRoute( - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) => - PersonScreen( - fid: state.params['fid']!, - pid: state.params['pid']!, - ), - ), - ], - ), - ], - ), - ], - ); -} - -/// The home screen. -class HomeScreen extends StatefulWidget { - /// Creates a [HomeScreen]. - const HomeScreen({Key? key}) : super(key: key); - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - Future>? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant HomeScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - _fetch(); - } - - void _fetch() => _future = App.repo.getFamilies(); - - @override - Widget build(BuildContext context) => FutureBuilder>( - future: _future, - builder: (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Scaffold( - appBar: AppBar(title: const Text('${App.title}: Loading...')), - body: const Center(child: CircularProgressIndicator()), - ); - } - - if (snapshot.hasError) { - return Scaffold( - appBar: AppBar(title: const Text('${App.title}: Error')), - body: SnapshotError(snapshot.error!), - ); - } - - assert(snapshot.hasData); - final List families = snapshot.data!; - return Scaffold( - appBar: AppBar( - title: Text('${App.title}: ${families.length} families'), - ), - body: ListView( - children: [ - for (final Family f in families) - ListTile( - title: Text(f.name), - onTap: () => context.go('/family/${f.id}'), - ) - ], - ), - ); - }, - ); -} - -/// The family screen. -class FamilyScreen extends StatefulWidget { - /// Creates a [FamilyScreen]. - const FamilyScreen({required this.fid, Key? key}) : super(key: key); - - /// The id of the family. - final String fid; - - @override - State createState() => _FamilyScreenState(); -} - -class _FamilyScreenState extends State { - Future? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant FamilyScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - if (oldWidget.fid != widget.fid) { - _fetch(); - } - } - - void _fetch() => _future = App.repo.getFamily(widget.fid); - - @override - Widget build(BuildContext context) => FutureBuilder( - future: _future, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Scaffold( - appBar: AppBar(title: const Text('Loading...')), - body: const Center(child: CircularProgressIndicator()), - ); - } - - if (snapshot.hasError) { - return Scaffold( - appBar: AppBar(title: const Text('Error')), - body: SnapshotError(snapshot.error!), - ); - } - - assert(snapshot.hasData); - final Family family = snapshot.data!; - return Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go( - '/family/${family.id}/person/${p.id}', - ), - ), - ], - ), - ); - }, - ); -} - -/// The person screen. -class PersonScreen extends StatefulWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.fid, required this.pid, Key? key}) - : super(key: key); - - /// The id of family the person is in. - final String fid; - - /// The id of the person. - final String pid; - - @override - State createState() => _PersonScreenState(); -} - -class _PersonScreenState extends State { - Future? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant PersonScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - if (oldWidget.fid != widget.fid || oldWidget.pid != widget.pid) { - _fetch(); - } - } - - void _fetch() => _future = App.repo.getPerson(widget.fid, widget.pid); - - @override - Widget build(BuildContext context) => FutureBuilder( - future: _future, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Scaffold( - appBar: AppBar(title: const Text('Loading...')), - body: const Center(child: CircularProgressIndicator()), - ); - } - - if (snapshot.hasError) { - return Scaffold( - appBar: AppBar(title: const Text('Error')), - body: SnapshotError(snapshot.error!), - ); - } - - assert(snapshot.hasData); - final FamilyPerson famper = snapshot.data!; - return Scaffold( - appBar: AppBar(title: Text(famper.person.name)), - body: Text( - '${famper.person.name} ${famper.family.name} is ' - '${famper.person.age} years old', - ), - ); - }, - ); -} - -/// The Error page. -class SnapshotError extends StatelessWidget { - /// Creates an error page. - SnapshotError(Object error, {Key? key}) - : error = error is Exception ? error : Exception(error), - super(key: key); - - /// The error. - final Exception error; - - @override - Widget build(BuildContext context) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SelectableText(error.toString()), - TextButton( - onPressed: () => context.go('/'), - child: const Text('Home'), - ), - ], - ), - ); -} diff --git a/packages/go_router/example/lib/others/cupertino.dart b/packages/go_router/example/lib/others/cupertino.dart deleted file mode 100644 index 6a8a704a6f..0000000000 --- a/packages/go_router/example/lib/others/cupertino.dart +++ /dev/null @@ -1,84 +0,0 @@ -// 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/cupertino.dart'; -import 'package:go_router/go_router.dart'; - -void main() => runApp(App()); - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Cupertino App'; - - @override - Widget build(BuildContext context) => CupertinoApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - ); - - final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - const Page1Screen(), - ), - GoRoute( - path: '/page2', - builder: (BuildContext context, GoRouterState state) => - const Page2Screen(), - ), - ], - ); -} - -/// The screen of the first page. -class Page1Screen extends StatelessWidget { - /// Creates a [Page1Screen]. - const Page1Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(middle: Text(App.title)), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CupertinoButton( - onPressed: () => context.go('/page2'), - child: const Text('Go to page 2'), - ), - ], - ), - ), - ); -} - -/// The screen of the second page. -class Page2Screen extends StatelessWidget { - /// Creates a [Page2Screen]. - const Page2Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => CupertinoPageScaffold( - navigationBar: const CupertinoNavigationBar(middle: Text(App.title)), - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CupertinoButton( - onPressed: () => context.go('/'), - child: const Text('Go to home page'), - ), - ], - ), - ), - ); -} diff --git a/packages/go_router/example/lib/others/extra_param.dart b/packages/go_router/example/lib/others/extra_param.dart index c1624a056a..7917bbb6ef 100644 --- a/packages/go_router/example/lib/others/extra_param.dart +++ b/packages/go_router/example/lib/others/extra_param.dart @@ -2,11 +2,41 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import '../shared/data.dart'; +final Map _families = const JsonDecoder().convert(''' +{ + "f1": { + "name": "Doe", + "people": { + "p1": { + "name": "Jane", + "age": 23 + }, + "p2": { + "name": "John", + "age": 6 + } + } + }, + "f2": { + "name": "Wong", + "people": { + "p1": { + "name": "June", + "age": 51 + }, + "p2": { + "name": "Xin", + "age": 44 + } + } + } +} +'''); void main() => runApp(App()); @@ -18,20 +48,13 @@ class App extends StatelessWidget { /// The title of the app. static const String title = 'GoRouter Example: Extra Parameter'; - static const bool _alertOnWeb = true; - @override - Widget build(BuildContext context) => _alertOnWeb && kIsWeb - ? const MaterialApp( - title: title, - home: NoExtraParamOnWebScreen(), - ) - : MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - ); + Widget build(BuildContext context) => MaterialApp.router( + routeInformationProvider: _router.routeInformationProvider, + routeInformationParser: _router.routeInformationParser, + routerDelegate: _router.routerDelegate, + title: title, + ); late final GoRouter _router = GoRouter( routes: [ @@ -39,30 +62,17 @@ class App extends StatelessWidget { name: 'home', path: '/', builder: (BuildContext context, GoRouterState state) => - HomeScreen(families: Families.data), + const HomeScreen(), routes: [ GoRoute( name: 'family', path: 'family', builder: (BuildContext context, GoRouterState state) { final Map params = - state.extra! as Map; - final Family family = params['family']! as Family; - return FamilyScreen(family: family); + state.extra! as Map; + final String fid = params['fid']! as String; + return FamilyScreen(fid: fid); }, - routes: [ - GoRoute( - name: 'person', - path: 'person', - builder: (BuildContext context, GoRouterState state) { - final Map params = - state.extra! as Map; - final Family family = params['family']! as Family; - final Person person = params['person']! as Person; - return PersonScreen(family: family, person: person); - }, - ), - ], ), ], ), @@ -73,21 +83,18 @@ class App extends StatelessWidget { /// The home screen that shows a list of families. class HomeScreen extends StatelessWidget { /// Creates a [HomeScreen]. - const HomeScreen({required this.families, Key? key}) : super(key: key); - - /// The list of families. - final List families; + const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), body: ListView( children: [ - for (final Family f in families) + for (final String fid in _families.keys) ListTile( - title: Text(f.name), + title: Text(_families[fid]['name']), onTap: () => context - .goNamed('family', extra: {'family': f}), + .goNamed('family', extra: {'fid': fid}), ) ], ), @@ -97,65 +104,25 @@ class HomeScreen extends StatelessWidget { /// The screen that shows a list of persons in a family. class FamilyScreen extends StatelessWidget { /// Creates a [FamilyScreen]. - const FamilyScreen({required this.family, Key? key}) : super(key: key); + const FamilyScreen({required this.fid, Key? key}) : super(key: key); /// The family to display. - final Family family; + final String fid; @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go( - context.namedLocation('person'), - extra: {'family': family, 'person': p}, - ), - ), - ], - ), - ); -} - -/// The person screen. -class PersonScreen extends StatelessWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.family, required this.person, Key? key}) - : super(key: key); - - /// The family this person belong to. - final Family family; - - /// The person to be displayed. - final Person person; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); -} - -/// A screen that explains this example does not work on web platform. -class NoExtraParamOnWebScreen extends StatelessWidget { - /// Creates a [NoExtraParamOnWebScreen]. - const NoExtraParamOnWebScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - Text("The `extra` param doesn't mix with the web:"), - Text("There's no support for the brower's Back button or" - ' deep linking'), - ], - ), - ), - ); + Widget build(BuildContext context) { + final Map people = + _families[fid]['people'] as Map; + return Scaffold( + appBar: AppBar(title: Text(_families[fid]['name'])), + body: ListView( + children: [ + for (final dynamic p in people.values) + ListTile( + title: Text(p['name']), + ), + ], + ), + ); + } } diff --git a/packages/go_router/example/lib/others/loading_page.dart b/packages/go_router/example/lib/others/loading_page.dart deleted file mode 100644 index 2c64389391..0000000000 --- a/packages/go_router/example/lib/others/loading_page.dart +++ /dev/null @@ -1,434 +0,0 @@ -// 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 'package:provider/provider.dart'; - -import '../shared/data.dart'; - -void main() => runApp(App()); - -/// The app state data class. -class AppState extends ChangeNotifier { - /// Creates an [AppState]. - AppState() { - loginInfo.addListener(loginChange); - repo.addListener(notifyListeners); - } - - /// The login status. - final LoginInfo2 loginInfo = LoginInfo2(); - - /// The repository to query data from. - final ValueNotifier repo = ValueNotifier(null); - - /// Called when login status changed. - Future loginChange() async { - notifyListeners(); - - // this will call notifyListeners(), too - repo.value = - loginInfo.loggedIn ? await Repository2.get(loginInfo.userName) : null; - } - - @override - void dispose() { - loginInfo.removeListener(loginChange); - repo.removeListener(notifyListeners); - super.dispose(); - } -} - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Loading Page'; - - final AppState _appState = AppState(); - - @override - Widget build(BuildContext context) => ChangeNotifierProvider.value( - value: _appState, - child: MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - debugShowCheckedModeBanner: false, - ), - ); - - late final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/login', - builder: (BuildContext context, GoRouterState state) => - const LoginScreen(), - ), - GoRoute( - path: '/loading', - builder: (BuildContext context, GoRouterState state) => - const LoadingScreen(), - ), - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - const HomeScreen(), - routes: [ - GoRoute( - path: 'family/:fid', - builder: (BuildContext context, GoRouterState state) => - FamilyScreen( - fid: state.params['fid']!, - ), - routes: [ - GoRoute( - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) => - PersonScreen( - fid: state.params['fid']!, - pid: state.params['pid']!, - ), - ), - ], - ), - ], - ), - ], - redirect: (GoRouterState state) { - // if the user is not logged in, they need to login - final bool loggedIn = _appState.loginInfo.loggedIn; - final bool loggingIn = state.subloc == '/login'; - final String subloc = state.subloc; - final String fromp1 = subloc == '/' ? '' : '?from=$subloc'; - if (!loggedIn) { - return loggingIn ? null : '/login$fromp1'; - } - - // if the user is logged in but the repository is not loaded, they need to - // wait while it's loaded - final bool loaded = _appState.repo.value != null; - final bool loading = state.subloc == '/loading'; - final String? from = state.queryParams['from']; - final String fromp2 = from == null ? '' : '?from=$from'; - if (!loaded) { - return loading ? null : '/loading$fromp2'; - } - - // if the user is logged in and the repository is loaded, send them where - // they were going before (or home if they weren't going anywhere) - if (loggingIn || loading) { - return from ?? '/'; - } - - // no need to redirect at all - return null; - }, - refreshListenable: _appState, - navigatorBuilder: - (BuildContext context, GoRouterState state, Widget child) => - _appState.loginInfo.loggedIn ? AuthOverlay(child: child) : child, - ); -} - -/// A simple class for placing an exit button on top of all screens. -class AuthOverlay extends StatelessWidget { - /// Creates an [AuthOverlay]. - const AuthOverlay({ - required this.child, - Key? key, - }) : super(key: key); - - /// The child subtree. - final Widget child; - - @override - Widget build(BuildContext context) => Stack( - children: [ - child, - Positioned( - top: 90, - right: 4, - child: ElevatedButton( - onPressed: () async { - // ignore: unawaited_futures - context.read().loginInfo.logout(); - // ignore: use_build_context_synchronously - context.go('/'); // clear query parameters - }, - child: const Icon(Icons.logout), - ), - ), - ], - ); -} - -/// The login screen. -class LoginScreen extends StatefulWidget { - /// Creates a [LoginScreen]. - const LoginScreen({Key? key}) : super(key: key); - - @override - State createState() => _LoginScreenState(); -} - -class _LoginScreenState extends State { - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () async { - // ignore: unawaited_futures - context.read().loginInfo.login('test-user'); - }, - child: const Text('Login'), - ), - ], - ), - ), - ); -} - -/// The loading screen. -class LoadingScreen extends StatelessWidget { - /// Creates a [LoadingScreen]. - const LoadingScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - CircularProgressIndicator(), - Text('loading repository...'), - ], - ), - ), - ); -} - -/// The home screen. -class HomeScreen extends StatefulWidget { - /// Creates a [HomeScreen]. - const HomeScreen({Key? key}) : super(key: key); - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - Future>? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant HomeScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - _fetch(); - } - - void _fetch() => _future = _repo.getFamilies(); - Repository2 get _repo => context.read().repo.value!; - - @override - Widget build(BuildContext context) => MyFutureBuilder>( - future: _future, - builder: (BuildContext context, List families) => Scaffold( - appBar: AppBar( - title: Text('${App.title}: ${families.length} families'), - ), - body: ListView( - children: [ - for (final Family f in families) - ListTile( - title: Text(f.name), - onTap: () => context.go('/family/${f.id}'), - ) - ], - ), - ), - ); -} - -/// The family screen. -class FamilyScreen extends StatefulWidget { - /// Creates a [FamilyScreen]. - const FamilyScreen({required this.fid, Key? key}) : super(key: key); - - /// The family id. - final String fid; - - @override - State createState() => _FamilyScreenState(); -} - -class _FamilyScreenState extends State { - Future? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant FamilyScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - if (oldWidget.fid != widget.fid) { - _fetch(); - } - } - - void _fetch() => _future = _repo.getFamily(widget.fid); - Repository2 get _repo => context.read().repo.value!; - - @override - Widget build(BuildContext context) => MyFutureBuilder( - future: _future, - builder: (BuildContext context, Family family) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go( - '/family/${family.id}/person/${p.id}', - ), - ), - ], - ), - ), - ); -} - -/// The person screen. -class PersonScreen extends StatefulWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.fid, required this.pid, Key? key}) - : super(key: key); - - /// The id of family the person belongs to. - final String fid; - - /// The person id. - final String pid; - - @override - State createState() => _PersonScreenState(); -} - -class _PersonScreenState extends State { - Future? _future; - - @override - void initState() { - super.initState(); - _fetch(); - } - - @override - void didUpdateWidget(covariant PersonScreen oldWidget) { - super.didUpdateWidget(oldWidget); - - // refresh cached data - if (oldWidget.fid != widget.fid || oldWidget.pid != widget.pid) { - _fetch(); - } - } - - void _fetch() => _future = _repo.getPerson(widget.fid, widget.pid); - Repository2 get _repo => context.read().repo.value!; - - @override - Widget build(BuildContext context) => MyFutureBuilder( - future: _future, - builder: (BuildContext context, FamilyPerson famper) => Scaffold( - appBar: AppBar(title: Text(famper.person.name)), - body: Text( - '${famper.person.name} ${famper.family.name} is ' - '${famper.person.age} years old', - ), - ), - ); -} - -/// A custom [Future] builder. -class MyFutureBuilder extends StatelessWidget { - /// Creates a [MyFutureBuilder]. - const MyFutureBuilder({required this.future, required this.builder, Key? key}) - : super(key: key); - - /// The [Future] to depend on. - final Future? future; - - /// The builder that builds the widget subtree. - final Widget Function(BuildContext context, T data) builder; - - @override - Widget build(BuildContext context) => FutureBuilder( - future: future, - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return Scaffold( - appBar: AppBar(title: const Text('Loading...')), - body: const Center(child: CircularProgressIndicator()), - ); - } - - if (snapshot.hasError) { - return Scaffold( - appBar: AppBar(title: const Text('Error')), - body: SnapshotError(snapshot.error!), - ); - } - - assert(snapshot.hasData); - return builder(context, snapshot.data!); - }, - ); -} - -/// The error widget. -class SnapshotError extends StatelessWidget { - /// Creates a [SnapshotError]. - SnapshotError(Object error, {Key? key}) - : error = error is Exception ? error : Exception(error), - super(key: key); - - /// The error to display. - final Exception error; - - @override - Widget build(BuildContext context) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SelectableText(error.toString()), - TextButton( - onPressed: () => context.go('/'), - child: const Text('Home'), - ), - ], - ), - ); -} diff --git a/packages/go_router/example/lib/others/nav_builder.dart b/packages/go_router/example/lib/others/nav_builder.dart index 5e80007956..c3a3bbb795 100644 --- a/packages/go_router/example/lib/others/nav_builder.dart +++ b/packages/go_router/example/lib/others/nav_builder.dart @@ -6,7 +6,27 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import '../shared/data.dart'; +/// The login information. +class LoginInfo extends ChangeNotifier { + /// The username of login. + String get userName => _userName; + String _userName = ''; + + /// Whether a user has logged in. + bool get loggedIn => _userName.isNotEmpty; + + /// Logs in a user. + void login(String userName) { + _userName = userName; + notifyListeners(); + } + + /// Logs out the current user. + void logout() { + _userName = ''; + notifyListeners(); + } +} void main() => runApp(App()); @@ -35,28 +55,7 @@ class App extends StatelessWidget { name: 'home', path: '/', builder: (BuildContext context, GoRouterState state) => - HomeScreenNoLogout(families: Families.data), - routes: [ - GoRoute( - name: 'family', - path: 'family/:fid', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - return FamilyScreen(family: family); - }, - routes: [ - GoRoute( - name: 'person', - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - final Person person = family.person(state.params['pid']!); - return PersonScreen(family: family, person: person); - }, - ), - ], - ), - ], + const HomeScreenNoLogout(), ), GoRoute( name: 'login', @@ -66,40 +65,24 @@ class App extends StatelessWidget { ), ], - // redirect to the login page if the user is not logged in - redirect: (GoRouterState state) { - // if the user is not logged in, they need to login - final bool loggedIn = _loginInfo.loggedIn; - final String loginloc = state.namedLocation('login'); - final bool loggingIn = state.subloc == loginloc; - - // bundle the location the user is coming from into a query parameter - final String homeloc = state.namedLocation('home'); - final String fromloc = state.subloc == homeloc ? '' : state.subloc; - if (!loggedIn) { - return loggingIn - ? null - : state.namedLocation( - 'login', - queryParams: { - if (fromloc.isNotEmpty) 'from': fromloc - }, - ); - } - - // if the user is logged in, send them where they were going before (or - // home if they weren't going anywhere) - if (loggingIn) { - return state.queryParams['from'] ?? homeloc; - } - - // no need to redirect at all - return null; - }, - // changes on the listenable will cause the router to refresh it's route refreshListenable: _loginInfo, + // redirect to the login page if the user is not logged in + redirect: (GoRouterState state) { + final bool loggedIn = _loginInfo.loggedIn; + const String loginLocation = '/login'; + final bool loggingIn = state.subloc == loginLocation; + + if (!loggedIn) { + return loggingIn ? null : loginLocation; + } + if (loggingIn) { + return state.namedLocation('home'); + } + return null; + }, + // add a wrapper around the navigator to: // - put loginInfo into the widget tree, and to // - add an overlay to show a logout option @@ -108,7 +91,6 @@ class App extends StatelessWidget { ChangeNotifierProvider.value( value: _loginInfo, builder: (BuildContext context, Widget? _) { - debugPrint('navigatorBuilder: ${state.subloc}'); return _loginInfo.loggedIn ? AuthOverlay(child: child) : child; }, ), @@ -145,70 +127,17 @@ class AuthOverlay extends StatelessWidget { /// The home screen without a logout button. class HomeScreenNoLogout extends StatelessWidget { /// Creates a [HomeScreenNoLogout]. - const HomeScreenNoLogout({required this.families, Key? key}) - : super(key: key); - - /// The list of families. - final List families; + const HomeScreenNoLogout({Key? key}) : super(key: key); @override Widget build(BuildContext context) => Scaffold( appBar: AppBar(title: const Text(App.title)), - body: ListView( - children: [ - for (final Family f in families) - ListTile( - title: Text(f.name), - onTap: () => context - .goNamed('family', params: {'fid': f.id}), - ) - ], + body: const Center( + child: Text('home screen'), ), ); } -/// The screen that shows a list of persons in a family. -class FamilyScreen extends StatelessWidget { - /// Creates a [FamilyScreen]. - const FamilyScreen({required this.family, Key? key}) : super(key: key); - - /// The family to display. - final Family family; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go('/family/${family.id}/person/${p.id}'), - ), - ], - ), - ); -} - -/// The person screen. -class PersonScreen extends StatelessWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.family, required this.person, Key? key}) - : super(key: key); - - /// The family this person belong to. - final Family family; - - /// The person to be displayed. - final Person person; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); -} - /// The login screen. class LoginScreen extends StatelessWidget { /// Creates a [LoginScreen]. diff --git a/packages/go_router/example/lib/others/nested_nav.dart b/packages/go_router/example/lib/others/nested_nav.dart deleted file mode 100644 index 049e4183ad..0000000000 --- a/packages/go_router/example/lib/others/nested_nav.dart +++ /dev/null @@ -1,201 +0,0 @@ -// 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 '../shared/data.dart'; - -void main() => runApp(App()); - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Nested Navigation'; - - @override - Widget build(BuildContext context) => MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - ); - - late final GoRouter _router = GoRouter( - routes: [ - GoRoute( - path: '/', - redirect: (_) => '/family/${Families.data[0].id}', - ), - GoRoute( - path: '/family/:fid', - builder: (BuildContext context, GoRouterState state) => - FamilyTabsScreen( - key: state.pageKey, - selectedFamily: Families.family(state.params['fid']!), - ), - routes: [ - GoRoute( - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - final Person person = family.person(state.params['pid']!); - - return PersonScreen(family: family, person: person); - }, - ), - ], - ), - ], - - // show the current router location as the user navigates page to page; note - // that this is not required for nested navigation but it is useful to show - // the location as it changes - navigatorBuilder: - (BuildContext context, GoRouterState state, Widget child) => Material( - child: Column( - children: [ - Expanded(child: child), - Padding( - padding: const EdgeInsets.all(8), - child: Text(state.location), - ), - ], - ), - ), - ); -} - -/// The family tabs screen. -class FamilyTabsScreen extends StatefulWidget { - /// Creates a [FamilyTabsScreen]. - FamilyTabsScreen({required Family selectedFamily, Key? key}) - : index = - Families.data.indexWhere((Family f) => f.id == selectedFamily.id), - super(key: key) { - assert(index != -1); - } - - /// The tab index. - final int index; - - @override - _FamilyTabsScreenState createState() => _FamilyTabsScreenState(); -} - -class _FamilyTabsScreenState extends State - with TickerProviderStateMixin { - late final TabController _controller; - - @override - void initState() { - super.initState(); - _controller = TabController( - length: Families.data.length, - vsync: this, - initialIndex: widget.index, - ); - } - - @override - void dispose() { - _controller.dispose(); - super.dispose(); - } - - @override - void didUpdateWidget(FamilyTabsScreen oldWidget) { - super.didUpdateWidget(oldWidget); - _controller.index = widget.index; - } - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: const Text(App.title), - bottom: TabBar( - controller: _controller, - tabs: [ - for (final Family f in Families.data) Tab(text: f.name) - ], - onTap: (int index) => _tap(context, index), - ), - ), - body: TabBarView( - controller: _controller, - children: [ - for (final Family f in Families.data) FamilyView(family: f) - ], - ), - ); - - void _tap(BuildContext context, int index) => - context.go('/family/${Families.data[index].id}'); -} - -/// The family view. -class FamilyView extends StatefulWidget { - /// Creates a [FamilyView]. - const FamilyView({required this.family, Key? key}) : super(key: key); - - /// The family to display. - final Family family; - - @override - State createState() => _FamilyViewState(); -} - -/// Use the [AutomaticKeepAliveClientMixin] to keep the state, like scroll -/// position and text fields when switching tabs, as well as when popping back -/// from sub screens. To use the mixin override [wantKeepAlive] and call -/// `super.build(context)` in build. -/// -/// In this example if you make a web build and make the browser window so low -/// that you have to scroll to see the last person on each family tab, you will -/// see that state is kept when you switch tabs and when you open a person -/// screen and pop back to the family. -class _FamilyViewState extends State - with AutomaticKeepAliveClientMixin { - // Override `wantKeepAlive` when using `AutomaticKeepAliveClientMixin`. - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - // Call `super.build` when using `AutomaticKeepAliveClientMixin`. - super.build(context); - return ListView( - children: [ - for (final Person p in widget.family.people) - ListTile( - title: Text(p.name), - onTap: () => - context.go('/family/${widget.family.id}/person/${p.id}'), - ), - ], - ); - } -} - -/// The person screen. -class PersonScreen extends StatelessWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.family, required this.person, Key? key}) - : super(key: key); - - /// The family this person belong to. - final Family family; - - /// The person to be displayed. - final Person person; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); -} diff --git a/packages/go_router/example/lib/others/router_stream_refresh.dart b/packages/go_router/example/lib/others/router_stream_refresh.dart deleted file mode 100644 index a04208424b..0000000000 --- a/packages/go_router/example/lib/others/router_stream_refresh.dart +++ /dev/null @@ -1,223 +0,0 @@ -// 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 'package:provider/provider.dart'; - -import '../shared/data.dart'; - -void main() => runApp(const App()); - -/// The main app. -class App extends StatefulWidget { - /// Creates an [App]. - const App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Stream Refresh'; - - @override - State createState() => _AppState(); -} - -class _AppState extends State { - late LoggedInState loggedInState; - late GoRouter router; - - @override - void initState() { - loggedInState = LoggedInState.seeded(false); - router = GoRouter( - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - HomeScreen(families: Families.data), - routes: [ - GoRoute( - path: 'family/:fid', - builder: (BuildContext context, GoRouterState state) => - FamilyScreen( - family: Families.family(state.params['fid']!), - ), - routes: [ - GoRoute( - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - final Person person = family.person(state.params['pid']!); - return PersonScreen(family: family, person: person); - }, - ), - ], - ), - ], - ), - GoRoute( - path: '/login', - builder: (BuildContext context, GoRouterState state) => - const LoginScreen(), - ), - ], - - // redirect to the login page if the user is not logged in - redirect: (GoRouterState state) { - // if the user is not logged in, they need to login - final bool loggedIn = loggedInState.state; - final bool loggingIn = state.subloc == '/login'; - - // bundle the location the user is coming from into a query parameter - final String fromp = state.subloc == '/' ? '' : '?from=${state.subloc}'; - if (!loggedIn) { - return loggingIn ? null : '/login$fromp'; - } - - // if the user is logged in, send them where they were going before (or - // home if they weren't going anywhere) - if (loggingIn) { - return state.queryParams['from'] ?? '/'; - } - - // no need to redirect at all - return null; - }, - // changes on the listenable will cause the router to refresh it's route - // TODO(johnpryan): Deprecate GoRouterRefreshStream - // See https://github.com/flutter/flutter/issues/108128 - refreshListenable: GoRouterRefreshStream(loggedInState.stream), - ); - super.initState(); - } - - // add the login info into the tree as app state that can change over time - @override - Widget build(BuildContext context) => Provider.value( - value: loggedInState, - child: MaterialApp.router( - routeInformationProvider: router.routeInformationProvider, - routeInformationParser: router.routeInformationParser, - routerDelegate: router.routerDelegate, - title: App.title, - debugShowCheckedModeBanner: false, - ), - ); - - @override - void dispose() { - loggedInState.dispose(); - super.dispose(); - } -} - -/// The home screen that shows a list of families. -class HomeScreen extends StatelessWidget { - /// Creates a [HomeScreen]. - const HomeScreen({ - required this.families, - Key? key, - }) : super(key: key); - - /// The list of families. - final List families; - - @override - Widget build(BuildContext context) { - final LoggedInState info = context.read(); - - return Scaffold( - appBar: AppBar( - title: const Text(App.title), - actions: [ - IconButton( - onPressed: () => info.emit(false), - icon: const Icon(Icons.logout), - ) - ], - ), - body: ListView( - children: [ - for (final Family f in families) - ListTile( - title: Text(f.name), - onTap: () => context.go('/family/${f.id}'), - ) - ], - ), - ); - } -} - -/// The screen that shows a list of persons in a family. -class FamilyScreen extends StatelessWidget { - /// Creates a [FamilyScreen]. - const FamilyScreen({ - required this.family, - Key? key, - }) : super(key: key); - - /// The family to display. - final Family family; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(family.name)), - body: ListView( - children: [ - for (final Person p in family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go('/family/${family.id}/person/${p.id}'), - ), - ], - ), - ); -} - -/// The person screen. -class PersonScreen extends StatelessWidget { - /// Creates a [PersonScreen]. - const PersonScreen({ - required this.family, - required this.person, - Key? key, - }) : super(key: key); - - /// The family this person belong to. - final Family family; - - /// The person to be displayed. - final Person person; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); -} - -/// The login screen. -class LoginScreen extends StatelessWidget { - /// Creates a [LoginScreen]. - const LoginScreen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () { - // log a user in, letting all the listeners know - context.read().emit(true); - }, - child: const Text('Login'), - ), - ], - ), - ), - ); -} diff --git a/packages/go_router/example/lib/others/shared_scaffold.dart b/packages/go_router/example/lib/others/shared_scaffold.dart deleted file mode 100644 index fd2e72c4c8..0000000000 --- a/packages/go_router/example/lib/others/shared_scaffold.dart +++ /dev/null @@ -1,213 +0,0 @@ -// 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:adaptive_navigation/adaptive_navigation.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:package_info_plus/package_info_plus.dart'; - -void main() => runApp(App()); - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Shared Scaffold'; - - @override - Widget build(BuildContext context) => MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - ); - - late final GoRouter _router = GoRouter( - debugLogDiagnostics: true, - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - _build(const Page1View()), - ), - GoRoute( - path: '/page2', - builder: (BuildContext context, GoRouterState state) => - _build(const Page2View()), - ), - ], - errorBuilder: (BuildContext context, GoRouterState state) => - _build(ErrorView(state.error!)), - - // use the navigatorBuilder to keep the SharedScaffold from being animated - // as new pages as shown; wrappiong that in single-page Navigator at the - // root provides an Overlay needed for the adaptive navigation scaffold and - // a root Navigator to show the About box - navigatorBuilder: - (BuildContext context, GoRouterState state, Widget child) => Navigator( - onPopPage: (Route route, dynamic result) { - route.didPop(result); - return false; // don't pop the single page on the root navigator - }, - pages: >[ - MaterialPage( - child: state.error != null - ? ErrorScaffold(body: child) - : SharedScaffold( - selectedIndex: state.subloc == '/' ? 0 : 1, - body: child, - ), - ), - ], - ), - ); - - // wrap the view widgets in a Scaffold to get the exit animation just right on - // the page being replaced - Widget _build(Widget child) => Scaffold(body: child); -} - -/// A scaffold with multiple pages. -class SharedScaffold extends StatefulWidget { - /// Creates a shared scaffold. - const SharedScaffold({ - required this.selectedIndex, - required this.body, - Key? key, - }) : super(key: key); - - /// The selected index. - final int selectedIndex; - - /// The body of the page. - final Widget body; - - @override - State createState() => _SharedScaffoldState(); -} - -class _SharedScaffoldState extends State { - @override - Widget build(BuildContext context) => AdaptiveNavigationScaffold( - selectedIndex: widget.selectedIndex, - destinations: const [ - AdaptiveScaffoldDestination(title: 'Page 1', icon: Icons.first_page), - AdaptiveScaffoldDestination(title: 'Page 2', icon: Icons.last_page), - AdaptiveScaffoldDestination(title: 'About', icon: Icons.info), - ], - appBar: AdaptiveAppBar(title: const Text(App.title)), - navigationTypeResolver: (BuildContext context) => - _drawerSize ? NavigationType.drawer : NavigationType.bottom, - onDestinationSelected: (int index) async { - // if there's a drawer, close it - if (_drawerSize) { - Navigator.pop(context); - } - - switch (index) { - case 0: - context.go('/'); - break; - case 1: - context.go('/page2'); - break; - case 2: - final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - showAboutDialog( - context: context, - applicationName: packageInfo.appName, - applicationVersion: 'v${packageInfo.version}', - applicationLegalese: 'Copyright © 2022, Acme, Corp.', - ); - break; - default: - throw Exception('Invalid index'); - } - }, - body: widget.body, - ); - - bool get _drawerSize => MediaQuery.of(context).size.width >= 600; -} - -/// The content of the first page. -class Page1View extends StatelessWidget { - /// Creates a [Page1View]. - const Page1View({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => context.go('/page2'), - child: const Text('Go to page 2'), - ), - ], - ), - ); -} - -/// The content of the second page. -class Page2View extends StatelessWidget { - /// Creates a [Page2View]. - const Page2View({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => context.go('/'), - child: const Text('Go to home page'), - ), - ], - ), - ); -} - -/// The error scaffold. -class ErrorScaffold extends StatelessWidget { - /// Creates an [ErrorScaffold]. - const ErrorScaffold({ - required this.body, - Key? key, - }) : super(key: key); - - /// The body of this scaffold. - final Widget body; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AdaptiveAppBar(title: const Text('Page Not Found')), - body: body, - ); -} - -/// A view to display error message. -class ErrorView extends StatelessWidget { - /// Creates an [ErrorView]. - const ErrorView(this.error, {Key? key}) : super(key: key); - - /// The error to display. - final Exception error; - - @override - Widget build(BuildContext context) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SelectableText(error.toString()), - TextButton( - onPressed: () => context.go('/'), - child: const Text('Home'), - ), - ], - ), - ); -} diff --git a/packages/go_router/example/lib/others/url_strategy.dart b/packages/go_router/example/lib/others/url_strategy.dart deleted file mode 100644 index 0bebb1b306..0000000000 --- a/packages/go_router/example/lib/others/url_strategy.dart +++ /dev/null @@ -1,95 +0,0 @@ -// 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'; - -void main() { - // turn on the # in the URLs on the web (default) - // GoRouter.setUrlPathStrategy(UrlPathStrategy.hash); - - // turn off the # in the URLs on the web - // GoRouter.setUrlPathStrategy(UrlPathStrategy.path); - - runApp(App()); -} - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: URL Path Strategy'; - - @override - Widget build(BuildContext context) => MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: App.title, - ); - - final GoRouter _router = GoRouter( - // turn off the # in the URLs on the web - urlPathStrategy: UrlPathStrategy.path, - - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - const Page1Screen(), - ), - GoRoute( - path: '/page2', - builder: (BuildContext context, GoRouterState state) => - const Page2Screen(), - ), - ], - ); -} - -/// The screen of the first page. -class Page1Screen extends StatelessWidget { - /// Creates a [Page1Screen]. - const Page1Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => context.go('/page2'), - child: const Text('Go to page 2'), - ), - ], - ), - ), - ); -} - -/// The screen of the second page. -class Page2Screen extends StatelessWidget { - /// Creates a [Page2Screen]. - const Page2Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: () => context.go('/'), - child: const Text('Go to home page'), - ), - ], - ), - ), - ); -} diff --git a/packages/go_router/example/lib/others/user_input.dart b/packages/go_router/example/lib/others/user_input.dart deleted file mode 100644 index 93c972b019..0000000000 --- a/packages/go_router/example/lib/others/user_input.dart +++ /dev/null @@ -1,399 +0,0 @@ -// 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:adaptive_dialog/adaptive_dialog.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -import '../shared/data.dart'; - -void main() => runApp(App()); - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: Navigator Integration'; - - @override - Widget build(BuildContext context) => MaterialApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - debugShowCheckedModeBanner: false, - ); - - late final GoRouter _router = GoRouter( - routes: [ - GoRoute( - name: 'home', - path: '/', - builder: (BuildContext context, GoRouterState state) => - HomeScreen(families: Families.data), - routes: [ - GoRoute( - name: 'family', - path: 'family/:fid', - builder: (BuildContext context, GoRouterState state) => - FamilyScreenWithAdd( - family: Families.family(state.params['fid']!), - ), - routes: [ - GoRoute( - name: 'person', - path: 'person/:pid', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - final Person person = family.person(state.params['pid']!); - return PersonScreen(family: family, person: person); - }, - ), - GoRoute( - name: 'new-person', - path: 'new-person', - builder: (BuildContext context, GoRouterState state) { - final Family family = Families.family(state.params['fid']!); - return NewPersonScreen2(family: family); - }, - ), - ], - ), - ], - ), - ], - ); -} - -/// The home screen that shows a list of families. -class HomeScreen extends StatelessWidget { - /// Creates a [HomeScreen]. - const HomeScreen({required this.families, Key? key}) : super(key: key); - - /// The list of families. - final List families; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: const Text(App.title)), - body: ListView( - children: [ - for (final Family f in families) - ListTile( - title: Text(f.name), - onTap: () => context - .goNamed('family', params: {'fid': f.id}), - ) - ], - ), - ); -} - -/// The family screen. -class FamilyScreenWithAdd extends StatefulWidget { - /// Creates a [FamilyScreenWithAdd]. - const FamilyScreenWithAdd({required this.family, Key? key}) : super(key: key); - - /// The family to display. - final Family family; - - @override - State createState() => _FamilyScreenWithAddState(); -} - -class _FamilyScreenWithAddState extends State { - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text(widget.family.name), - actions: [ - IconButton( - // onPressed: () => _addPerson1(context), // Navigator-style - onPressed: () => _addPerson2(context), // GoRouter-style - tooltip: 'Add Person', - icon: const Icon(Icons.add), - ), - ], - ), - body: ListView( - children: [ - for (final Person p in widget.family.people) - ListTile( - title: Text(p.name), - onTap: () => context.go(context.namedLocation( - 'person', - params: { - 'fid': widget.family.id, - 'pid': p.id - }, - queryParams: {'qid': 'quid'}, - )), - ), - ], - ), - ); - - // using a Navigator and a Navigator result - // ignore: unused_element - Future _addPerson1(BuildContext context) async { - final Person? person = await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - NewPersonScreen1(family: widget.family), - ), - ); - - if (person != null) { - setState(() => widget.family.people.add(person)); - - // ignore: use_build_context_synchronously - context.goNamed('person', params: { - 'fid': widget.family.id, - 'pid': person.id, - }); - } - } - - // using a GoRouter page - void _addPerson2(BuildContext context) { - context.goNamed('new-person', - params: {'fid': widget.family.id}); - } -} - -/// The person screen. -class PersonScreen extends StatelessWidget { - /// Creates a [PersonScreen]. - const PersonScreen({required this.family, required this.person, Key? key}) - : super(key: key); - - /// The family this person belong to. - final Family family; - - /// The person to be displayed. - final Person person; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar(title: Text(person.name)), - body: Text('${person.name} ${family.name} is ${person.age} years old'), - ); -} - -// returning a Navigator result -/// The screen to add a new person into the family. -class NewPersonScreen1 extends StatefulWidget { - /// Creates a [NewPersonScreen1]. - const NewPersonScreen1({required this.family, Key? key}) : super(key: key); - - /// The family to be added to. - final Family family; - - @override - State createState() => _NewPersonScreen1State(); -} - -class _NewPersonScreen1State extends State { - final GlobalKey _formKey = GlobalKey(); - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _ageController = TextEditingController(); - - @override - void dispose() { - super.dispose(); - _nameController.dispose(); - _ageController.dispose(); - } - - @override - Widget build(BuildContext context) => WillPopScope( - // ask the user if they'd like to adandon their data - onWillPop: () async => abandonNewPerson(context), - child: Scaffold( - appBar: AppBar( - title: Text('New person for family ${widget.family.name}'), - ), - body: Form( - key: _formKey, - child: Center( - child: SizedBox( - width: 400, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _nameController, - decoration: const InputDecoration(labelText: 'name'), - validator: (String? value) => - value == null || value.isEmpty - ? 'Please enter a name' - : null, - ), - TextFormField( - controller: _ageController, - decoration: const InputDecoration(labelText: 'age'), - validator: (String? value) => value == null || - value.isEmpty || - int.tryParse(value) == null - ? 'Please enter an age' - : null, - ), - ButtonBar(children: [ - TextButton( - onPressed: () async { - // ask the user if they'd like to adandon their data - if (await abandonNewPerson(context)) { - Navigator.pop(context); - } - }, - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - final Person person = Person( - id: 'p${widget.family.people.length + 1}', - name: _nameController.text, - age: int.parse(_ageController.text), - ); - - Navigator.pop(context, person); - } - }, - child: const Text('Create'), - ), - ]), - ], - ), - ), - ), - ), - ), - ); - - Future abandonNewPerson(BuildContext context) async { - final OkCancelResult result = await showOkCancelAlertDialog( - context: context, - title: 'Abandon New Person', - message: 'Are you sure you abandon this new person?', - okLabel: 'Keep', - cancelLabel: 'Abandon', - ); - - return result == OkCancelResult.cancel; - } -} - -// adding the result to the data directly (GoRouter page) -/// The screen to add a new person into the family. -class NewPersonScreen2 extends StatefulWidget { - /// Creates a [NewPersonScreen1]. - const NewPersonScreen2({required this.family, Key? key}) : super(key: key); - - /// The family to display. - final Family family; - - @override - State createState() => _NewPersonScreen2State(); -} - -class _NewPersonScreen2State extends State { - final GlobalKey _formKey = GlobalKey(); - final TextEditingController _nameController = TextEditingController(); - final TextEditingController _ageController = TextEditingController(); - - @override - void dispose() { - super.dispose(); - _nameController.dispose(); - _ageController.dispose(); - } - - @override - Widget build(BuildContext context) => WillPopScope( - // ask the user if they'd like to adandon their data - onWillPop: () async => abandonNewPerson(context), - child: Scaffold( - appBar: AppBar( - title: Text('New person for family ${widget.family.name}'), - ), - body: Form( - key: _formKey, - child: Center( - child: SizedBox( - width: 400, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _nameController, - decoration: const InputDecoration(labelText: 'name'), - validator: (String? value) => - value == null || value.isEmpty - ? 'Please enter a name' - : null, - ), - TextFormField( - controller: _ageController, - decoration: const InputDecoration(labelText: 'age'), - validator: (String? value) => value == null || - value.isEmpty || - int.tryParse(value) == null - ? 'Please enter an age' - : null, - ), - ButtonBar(children: [ - TextButton( - onPressed: () async { - // ask the user if they'd like to adandon their data - if (await abandonNewPerson(context)) { - // Navigator.pop(context) would work here, too - context.pop(); - } - }, - child: const Text('Cancel'), - ), - ElevatedButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - final Person person = Person( - id: 'p${widget.family.people.length + 1}', - name: _nameController.text, - age: int.parse(_ageController.text), - ); - - widget.family.people.add(person); - - context.goNamed('person', params: { - 'fid': widget.family.id, - 'pid': person.id, - }); - } - }, - child: const Text('Create'), - ), - ]), - ], - ), - ), - ), - ), - ), - ); - - Future abandonNewPerson(BuildContext context) async { - final OkCancelResult result = await showOkCancelAlertDialog( - context: context, - title: 'Abandon New Person', - message: 'Are you sure you abandon this new person?', - okLabel: 'Keep', - cancelLabel: 'Abandon', - ); - - return result == OkCancelResult.cancel; - } -} diff --git a/packages/go_router/example/lib/others/widgets_app.dart b/packages/go_router/example/lib/others/widgets_app.dart deleted file mode 100644 index cd4cc50b42..0000000000 --- a/packages/go_router/example/lib/others/widgets_app.dart +++ /dev/null @@ -1,131 +0,0 @@ -// 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:flutter/widgets.dart'; -import 'package:go_router/go_router.dart'; - -void main() => runApp(App()); - -const Color _kBlue = Color(0xFF2196F3); -const Color _kWhite = Color(0xFFFFFFFF); - -/// The main app. -class App extends StatelessWidget { - /// Creates an [App]. - App({Key? key}) : super(key: key); - - /// The title of the app. - static const String title = 'GoRouter Example: WidgetsApp'; - - @override - Widget build(BuildContext context) => WidgetsApp.router( - routeInformationProvider: _router.routeInformationProvider, - routeInformationParser: _router.routeInformationParser, - routerDelegate: _router.routerDelegate, - title: title, - color: _kBlue, - textStyle: const TextStyle(color: _kBlue), - ); - - final GoRouter _router = GoRouter( - debugLogDiagnostics: true, - routes: [ - GoRoute( - path: '/', - builder: (BuildContext context, GoRouterState state) => - const Page1Screen(), - ), - GoRoute( - path: '/page2', - builder: (BuildContext context, GoRouterState state) => - const Page2Screen(), - ), - ], - ); -} - -/// The screen of the first page. -class Page1Screen extends StatelessWidget { - /// Creates a [Page1Screen]. - const Page1Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => SafeArea( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - App.title, - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - Button( - onPressed: () => context.go('/page2'), - child: const Text( - 'Go to page 2', - style: TextStyle(color: _kWhite), - ), - ), - ], - ), - ), - ); -} - -/// The screen of the second page. -class Page2Screen extends StatelessWidget { - /// Creates a [Page2Screen]. - const Page2Screen({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) => SafeArea( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - App.title, - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 16), - Button( - onPressed: () => context.go('/'), - child: const Text( - 'Go to home page', - style: TextStyle(color: _kWhite), - ), - ), - ], - ), - ), - ); -} - -/// A custom button. -class Button extends StatelessWidget { - /// Creates a [Button]. - const Button({ - required this.onPressed, - required this.child, - Key? key, - }) : super(key: key); - - /// Called when user pressed the button. - final VoidCallback onPressed; - - /// The child subtree. - final Widget child; - - @override - Widget build(BuildContext context) => GestureDetector( - onTap: onPressed, - child: Container( - padding: const EdgeInsets.all(8), - color: _kBlue, - child: child, - ), - ); -} diff --git a/packages/go_router/example/lib/path_and_query_parameters.dart b/packages/go_router/example/lib/path_and_query_parameters.dart index 6a91c1528b..38af018735 100755 --- a/packages/go_router/example/lib/path_and_query_parameters.dart +++ b/packages/go_router/example/lib/path_and_query_parameters.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'shared/data.dart'; - // This scenario demonstrates how to use path parameters and query parameters. // // The route segments that start with ':' are treated as path parameters when @@ -15,6 +15,37 @@ import 'shared/data.dart'; // // The query parameters are automatically stored in GoRouterState.queryParams. +final Map _families = const JsonDecoder().convert(''' +{ + "f1": { + "name": "Doe", + "people": { + "p1": { + "name": "Jane", + "age": 23 + }, + "p2": { + "name": "John", + "age": 6 + } + } + }, + "f2": { + "name": "Wong", + "people": { + "p1": { + "name": "June", + "age": 51 + }, + "p2": { + "name": "Xin", + "age": 44 + } + } + } +} +'''); + void main() => runApp(App()); /// The main app. @@ -40,13 +71,14 @@ class App extends StatelessWidget { GoRoute( path: '/', builder: (BuildContext context, GoRouterState state) => - HomeScreen(families: Families.data), + const HomeScreen(), routes: [ GoRoute( - path: 'family/:id', + name: 'family', + path: 'family/:fid', builder: (BuildContext context, GoRouterState state) { return FamilyScreen( - family: Families.family(state.params['id']!), + fid: state.params['fid']!, asc: state.queryParams['sort'] == 'asc', ); }), @@ -59,10 +91,7 @@ class App extends StatelessWidget { /// The home screen that shows a list of families. class HomeScreen extends StatelessWidget { /// Creates a [HomeScreen]. - const HomeScreen({required this.families, Key? key}) : super(key: key); - - /// The list of families. - final List families; + const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -72,10 +101,10 @@ class HomeScreen extends StatelessWidget { ), body: ListView( children: [ - for (final Family f in families) + for (final String fid in _families.keys) ListTile( - title: Text(f.name), - onTap: () => context.go('/family/${f.id}'), + title: Text(_families[fid]['name']), + onTap: () => context.go('/family/$fid'), ) ], ), @@ -86,34 +115,35 @@ class HomeScreen extends StatelessWidget { /// The screen that shows a list of persons in a family. class FamilyScreen extends StatelessWidget { /// Creates a [FamilyScreen]. - const FamilyScreen({required this.family, required this.asc, Key? key}) + const FamilyScreen({required this.fid, required this.asc, Key? key}) : super(key: key); /// The family to display. - final Family family; + final String fid; /// Whether to sort the name in ascending order. final bool asc; @override Widget build(BuildContext context) { - final String curentPath = Uri.parse(GoRouter.of(context).location).path; final Map newQueries; - final List names = - family.people.map((Person p) => p.name).toList(); + final List names = _families[fid]['people'] + .values + .map((dynamic p) => p['name'] as String) + .toList(); names.sort(); if (asc) { newQueries = const {'sort': 'desc'}; } else { newQueries = const {'sort': 'asc'}; } - final Uri iconLink = Uri(path: curentPath, queryParameters: newQueries); return Scaffold( appBar: AppBar( - title: Text(family.name), + title: Text(_families[fid]['name']), actions: [ IconButton( - onPressed: () => context.go(iconLink.toString()), + onPressed: () => context.goNamed('family', + params: {'fid': fid}, queryParams: newQueries), tooltip: 'sort ascending or descending', icon: const Icon(Icons.sort), ) diff --git a/packages/go_router/example/lib/redirection.dart b/packages/go_router/example/lib/redirection.dart index 5697fd65c9..d57b5d446c 100644 --- a/packages/go_router/example/lib/redirection.dart +++ b/packages/go_router/example/lib/redirection.dart @@ -6,14 +6,34 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; -import 'shared/data.dart'; - // This scenario demonstrates how to use redirect to handle a sign-in flow. // // The GoRouter.redirect method is called before the app is navigate to a // new page. You can choose to redirect to a different page by returning a // non-null URL string. +/// The login information. +class LoginInfo extends ChangeNotifier { + /// The username of login. + String get userName => _userName; + String _userName = ''; + + /// Whether a user has logged in. + bool get loggedIn => _userName.isNotEmpty; + + /// Logs in a user. + void login(String userName) { + _userName = userName; + notifyListeners(); + } + + /// Logs out the current user. + void logout() { + _userName = ''; + notifyListeners(); + } +} + void main() => runApp(App()); /// The main app. diff --git a/packages/go_router/example/lib/shared/data.dart b/packages/go_router/example/lib/shared/data.dart deleted file mode 100644 index a9a84b73f3..0000000000 --- a/packages/go_router/example/lib/shared/data.dart +++ /dev/null @@ -1,268 +0,0 @@ -// 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 'dart:async'; -import 'dart:math'; - -import 'package:flutter/foundation.dart'; - -/// Person data class. -class Person { - /// Creates a [Person]. - Person({required this.id, required this.name, required this.age}); - - /// The id of the person. - final String id; - - /// The name of the person. - final String name; - - /// The age of the person. - final int age; -} - -/// Family data class. -class Family { - /// Creates a [Family]. - Family({required this.id, required this.name, required this.people}); - - /// The id of the family. - final String id; - - /// The name of the family. - final String name; - - /// The list of [Person]s in the family. - final List people; - - /// Gets the [Person] with the given id in this family. - Person person(String pid) => people.singleWhere( - (Person p) => p.id == pid, - orElse: () => throw Exception('unknown person $pid for family $id'), - ); -} - -/// The mock of families data. -class Families { - Families._(); - - /// The data. - static final List data = [ - Family( - id: 'f1', - name: 'Sells', - people: [ - Person(id: 'p1', name: 'Chris', age: 52), - Person(id: 'p2', name: 'John', age: 27), - Person(id: 'p3', name: 'Tom', age: 26), - ], - ), - Family( - id: 'f2', - name: 'Addams', - people: [ - Person(id: 'p1', name: 'Gomez', age: 55), - Person(id: 'p2', name: 'Morticia', age: 50), - Person(id: 'p3', name: 'Pugsley', age: 10), - Person(id: 'p4', name: 'Wednesday', age: 17), - ], - ), - Family( - id: 'f3', - name: 'Hunting', - people: [ - Person(id: 'p1', name: 'Mom', age: 54), - Person(id: 'p2', name: 'Dad', age: 55), - Person(id: 'p3', name: 'Will', age: 20), - Person(id: 'p4', name: 'Marky', age: 21), - Person(id: 'p5', name: 'Ricky', age: 22), - Person(id: 'p6', name: 'Danny', age: 23), - Person(id: 'p7', name: 'Terry', age: 24), - Person(id: 'p8', name: 'Mikey', age: 25), - Person(id: 'p9', name: 'Davey', age: 26), - Person(id: 'p10', name: 'Timmy', age: 27), - Person(id: 'p11', name: 'Tommy', age: 28), - Person(id: 'p12', name: 'Joey', age: 29), - Person(id: 'p13', name: 'Robby', age: 30), - Person(id: 'p14', name: 'Johnny', age: 31), - Person(id: 'p15', name: 'Brian', age: 32), - ], - ), - ]; - - /// Looks up a family in the data. - static Family family(String fid) => data.family(fid); -} - -extension on List { - Family family(String fid) => singleWhere( - (Family f) => f.id == fid, - orElse: () => throw Exception('unknown family $fid'), - ); -} - -/// The login information. -class LoginInfo extends ChangeNotifier { - /// The username of login. - String get userName => _userName; - String _userName = ''; - - /// Whether a user has logged in. - bool get loggedIn => _userName.isNotEmpty; - - /// Logs in a user. - void login(String userName) { - _userName = userName; - notifyListeners(); - } - - /// Logs out the current user. - void logout() { - _userName = ''; - notifyListeners(); - } -} - -/// The login information. -class LoginInfo2 extends ChangeNotifier { - /// The username of login. - String get userName => _userName; - String _userName = ''; - - /// Whether a user has logged in. - bool get loggedIn => _userName.isNotEmpty; - - /// Logs in a user. - Future login(String userName) async { - _userName = userName; - notifyListeners(); - await Future.delayed(const Duration(microseconds: 2500)); - } - - /// Logs out the current user. - Future logout() async { - _userName = ''; - notifyListeners(); - await Future.delayed(const Duration(microseconds: 2500)); - } -} - -/// The relation of a person in a family. -class FamilyPerson { - /// Creates a [FamilyPerson]. - FamilyPerson({required this.family, required this.person}); - - /// The family. - final Family family; - - /// the person. - final Person person; -} - -/// The repository. -class Repository { - /// A random number generator. - static final Random rnd = Random(); - - /// Gets the families data. - Future> getFamilies() async { - // simulate network delay - await Future.delayed(const Duration(seconds: 1)); - - // simulate error - // if (rnd.nextBool()) throw Exception('error fetching families'); - - // return data "fetched over the network" - return Families.data; - } - - /// Gets a family from the repository. - Future getFamily(String fid) async => - (await getFamilies()).family(fid); - - /// Gets a person from the repository. - Future getPerson(String fid, String pid) async { - final Family family = await getFamily(fid); - return FamilyPerson(family: family, person: family.person(pid)); - } -} - -/// The repository. -class Repository2 { - Repository2._(this.userName); - - /// The username. - final String userName; - - /// Gets a repository with the username. - static Future get(String userName) async { - // simulate network delay - await Future.delayed(const Duration(seconds: 1)); - return Repository2._(userName); - } - - /// A random number generator. - static final Random rnd = Random(); - - /// Gets the families data. - Future> getFamilies() async { - // simulate network delay - await Future.delayed(const Duration(seconds: 1)); - - // simulate error - // if (rnd.nextBool()) throw Exception('error fetching families'); - - // return data "fetched over the network" - return Families.data; - } - - /// Gets a family from the repository. - Future getFamily(String fid) async => - (await getFamilies()).family(fid); - - /// Gets a person from the repository. - Future getPerson(String fid, String pid) async { - final Family family = await getFamily(fid); - return FamilyPerson(family: family, person: family.person(pid)); - } -} - -/// A state stream. -abstract class StateStream { - /// Creates a [StateStream]. - StateStream(); - - /// Creates a [StateStream] with an initial value. - StateStream.seeded(T value) : state = value { - _controller.add(value); - } - - final StreamController _controller = StreamController(); - - /// The state. - late T state; - - /// The [Stream] object. - Stream get stream => _controller.stream; - - /// Pipes a new state into the stream. - void emit(T state) { - this.state = state; - _controller.add(state); - } - - /// Disposes the stream. - void dispose() { - _controller.close(); - } -} - -/// Stream for whether the user is currently logged in. -class LoggedInState extends StateStream { - /// Creates a [LoggedInState]. - LoggedInState(); - - /// Creates a [LoggedInState] with an initial value. - LoggedInState.seeded(bool value) : super.seeded(value); -} diff --git a/packages/go_router/example/test/path_and_query_params_test.dart b/packages/go_router/example/test/path_and_query_params_test.dart index 02e23624f4..b59c8c8440 100644 --- a/packages/go_router/example/test/path_and_query_params_test.dart +++ b/packages/go_router/example/test/path_and_query_params_test.dart @@ -24,8 +24,8 @@ void main() { await tester.pumpAndSettle(); // 'Chris' should be higher than 'Tom'. expect( - tester.getCenter(find.text('Chris')).dy < - tester.getCenter(find.text('Tom')).dy, + tester.getCenter(find.text('Jane')).dy < + tester.getCenter(find.text('John')).dy, isTrue); testRouteInformation = { @@ -40,8 +40,8 @@ void main() { await tester.pumpAndSettle(); // 'Chris' should be lower than 'Tom'. expect( - tester.getCenter(find.text('Chris')).dy > - tester.getCenter(find.text('Tom')).dy, + tester.getCenter(find.text('Jane')).dy > + tester.getCenter(find.text('John')).dy, isTrue); }); } diff --git a/packages/go_router/example/test/reidrection_test.dart b/packages/go_router/example/test/redirection_test.dart similarity index 100% rename from packages/go_router/example/test/reidrection_test.dart rename to packages/go_router/example/test/redirection_test.dart