[go_router] cleans up examples (#2440)

This commit is contained in:
chunhtai
2022-08-11 10:09:14 -07:00
committed by GitHub
parent ad0863fdc6
commit 33bc15043c
18 changed files with 261 additions and 2618 deletions

View File

@ -1,3 +1,7 @@
## NEXT
- Cleans up examples
## 4.2.7
- Update README

View File

@ -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<String, dynamic> _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>[
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>[
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<Family> families;
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -84,11 +109,11 @@ class HomeScreen extends StatelessWidget {
),
body: ListView(
children: <Widget>[
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: <String, String>{'fid': f.id})),
params: <String, String>{'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: <Widget>[
for (final Person p in family.people)
ListTile(
title: Text(p.name),
onTap: () => context.go(context.namedLocation(
'person',
params: <String, String>{'fid': family.id, 'pid': p.id},
queryParams: <String, String>{'qid': 'quid'},
)),
),
],
),
);
Widget build(BuildContext context) {
final Map<String, dynamic> people =
_families[fid]['people'] as Map<String, dynamic>;
return Scaffold(
appBar: AppBar(title: Text(_families[fid]['name'])),
body: ListView(
children: <Widget>[
for (final String pid in people.keys)
ListTile(
title: Text(people[pid]['name']),
onTap: () => context.go(context.namedLocation(
'person',
params: <String, String>{'fid': fid, 'pid': pid},
queryParams: <String, String>{'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<String, dynamic> family = _families[fid];
final Map<String, dynamic> person = family['people'][pid];
return Scaffold(
appBar: AppBar(title: Text(person['name'])),
body: Text(
'${person['name']} ${family['name']} is ${person['age']} years old'),
);
}
}

View File

@ -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>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(
fid: state.params['fid']!,
),
routes: <GoRoute>[
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<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Family>>? _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<List<Family>>(
future: _future,
builder: (BuildContext context, AsyncSnapshot<List<Family>> 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<Family> families = snapshot.data!;
return Scaffold(
appBar: AppBar(
title: Text('${App.title}: ${families.length} families'),
),
body: ListView(
children: <Widget>[
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<FamilyScreen> createState() => _FamilyScreenState();
}
class _FamilyScreenState extends State<FamilyScreen> {
Future<Family>? _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<Family>(
future: _future,
builder: (BuildContext context, AsyncSnapshot<Family> 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: <Widget>[
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<PersonScreen> createState() => _PersonScreenState();
}
class _PersonScreenState extends State<PersonScreen> {
Future<FamilyPerson>? _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<FamilyPerson>(
future: _future,
builder: (BuildContext context, AsyncSnapshot<FamilyPerson> 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: <Widget>[
SelectableText(error.toString()),
TextButton(
onPressed: () => context.go('/'),
child: const Text('Home'),
),
],
),
);
}

View File

@ -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>[
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: <Widget>[
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: <Widget>[
CupertinoButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}

View File

@ -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<String, dynamic> _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: <GoRoute>[
@ -39,30 +62,17 @@ class App extends StatelessWidget {
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
HomeScreen(families: Families.data),
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family',
builder: (BuildContext context, GoRouterState state) {
final Map<String, Object> params =
state.extra! as Map<String, Object>;
final Family family = params['family']! as Family;
return FamilyScreen(family: family);
state.extra! as Map<String, String>;
final String fid = params['fid']! as String;
return FamilyScreen(fid: fid);
},
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person',
builder: (BuildContext context, GoRouterState state) {
final Map<String, Object> params =
state.extra! as Map<String, Object>;
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<Family> families;
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: ListView(
children: <Widget>[
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: <String, Object?>{'family': f}),
.goNamed('family', extra: <String, String>{'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: <Widget>[
for (final Person p in family.people)
ListTile(
title: Text(p.name),
onTap: () => context.go(
context.namedLocation('person'),
extra: <String, Object>{'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 <Widget>[
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<String, dynamic> people =
_families[fid]['people'] as Map<String, dynamic>;
return Scaffold(
appBar: AppBar(title: Text(_families[fid]['name'])),
body: ListView(
children: <Widget>[
for (final dynamic p in people.values)
ListTile(
title: Text(p['name']),
),
],
),
);
}
}

View File

@ -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<Repository2?> repo = ValueNotifier<Repository2?>(null);
/// Called when login status changed.
Future<void> 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<AppState>.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>[
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>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(
fid: state.params['fid']!,
),
routes: <GoRoute>[
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: <Widget>[
child,
Positioned(
top: 90,
right: 4,
child: ElevatedButton(
onPressed: () async {
// ignore: unawaited_futures
context.read<AppState>().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<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () async {
// ignore: unawaited_futures
context.read<AppState>().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 <Widget>[
CircularProgressIndicator(),
Text('loading repository...'),
],
),
),
);
}
/// The home screen.
class HomeScreen extends StatefulWidget {
/// Creates a [HomeScreen].
const HomeScreen({Key? key}) : super(key: key);
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Family>>? _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<AppState>().repo.value!;
@override
Widget build(BuildContext context) => MyFutureBuilder<List<Family>>(
future: _future,
builder: (BuildContext context, List<Family> families) => Scaffold(
appBar: AppBar(
title: Text('${App.title}: ${families.length} families'),
),
body: ListView(
children: <Widget>[
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<FamilyScreen> createState() => _FamilyScreenState();
}
class _FamilyScreenState extends State<FamilyScreen> {
Future<Family>? _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<AppState>().repo.value!;
@override
Widget build(BuildContext context) => MyFutureBuilder<Family>(
future: _future,
builder: (BuildContext context, Family family) => Scaffold(
appBar: AppBar(title: Text(family.name)),
body: ListView(
children: <Widget>[
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<PersonScreen> createState() => _PersonScreenState();
}
class _PersonScreenState extends State<PersonScreen> {
Future<FamilyPerson>? _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<AppState>().repo.value!;
@override
Widget build(BuildContext context) => MyFutureBuilder<FamilyPerson>(
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<T extends Object> 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<T>? future;
/// The builder that builds the widget subtree.
final Widget Function(BuildContext context, T data) builder;
@override
Widget build(BuildContext context) => FutureBuilder<T>(
future: future,
builder: (BuildContext context, AsyncSnapshot<T> 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: <Widget>[
SelectableText(error.toString()),
TextButton(
onPressed: () => context.go('/'),
child: const Text('Home'),
),
],
),
);
}

View File

@ -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>[
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>[
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: <String, String>{
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<LoginInfo>.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<Family> families;
const HomeScreenNoLogout({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: ListView(
children: <Widget>[
for (final Family f in families)
ListTile(
title: Text(f.name),
onTap: () => context
.goNamed('family', params: <String, String>{'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: <Widget>[
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].

View File

@ -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>[
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>[
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: <Widget>[
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<FamilyTabsScreen>
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: <Tab>[
for (final Family f in Families.data) Tab(text: f.name)
],
onTap: (int index) => _tap(context, index),
),
),
body: TabBarView(
controller: _controller,
children: <Widget>[
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<FamilyView> 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<FamilyView>
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: <Widget>[
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'),
);
}

View File

@ -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<App> createState() => _AppState();
}
class _AppState extends State<App> {
late LoggedInState loggedInState;
late GoRouter router;
@override
void initState() {
loggedInState = LoggedInState.seeded(false);
router = GoRouter(
routes: <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
HomeScreen(families: Families.data),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreen(
family: Families.family(state.params['fid']!),
),
routes: <GoRoute>[
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<LoggedInState>.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<Family> families;
@override
Widget build(BuildContext context) {
final LoggedInState info = context.read<LoggedInState>();
return Scaffold(
appBar: AppBar(
title: const Text(App.title),
actions: <Widget>[
IconButton(
onPressed: () => info.emit(false),
icon: const Icon(Icons.logout),
)
],
),
body: ListView(
children: <Widget>[
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: <Widget>[
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: <Widget>[
ElevatedButton(
onPressed: () {
// log a user in, letting all the listeners know
context.read<LoggedInState>().emit(true);
},
child: const Text('Login'),
),
],
),
),
);
}

View File

@ -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>[
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<dynamic> route, dynamic result) {
route.didPop(result);
return false; // don't pop the single page on the root navigator
},
pages: <Page<dynamic>>[
MaterialPage<void>(
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<SharedScaffold> createState() => _SharedScaffoldState();
}
class _SharedScaffoldState extends State<SharedScaffold> {
@override
Widget build(BuildContext context) => AdaptiveNavigationScaffold(
selectedIndex: widget.selectedIndex,
destinations: const <AdaptiveScaffoldDestination>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
SelectableText(error.toString()),
TextButton(
onPressed: () => context.go('/'),
child: const Text('Home'),
),
],
),
);
}

View File

@ -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>[
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: <Widget>[
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: <Widget>[
ElevatedButton(
onPressed: () => context.go('/'),
child: const Text('Go to home page'),
),
],
),
),
);
}

View File

@ -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>[
GoRoute(
name: 'home',
path: '/',
builder: (BuildContext context, GoRouterState state) =>
HomeScreen(families: Families.data),
routes: <GoRoute>[
GoRoute(
name: 'family',
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
FamilyScreenWithAdd(
family: Families.family(state.params['fid']!),
),
routes: <GoRoute>[
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<Family> families;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text(App.title)),
body: ListView(
children: <Widget>[
for (final Family f in families)
ListTile(
title: Text(f.name),
onTap: () => context
.goNamed('family', params: <String, String>{'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<FamilyScreenWithAdd> createState() => _FamilyScreenWithAddState();
}
class _FamilyScreenWithAddState extends State<FamilyScreenWithAdd> {
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text(widget.family.name),
actions: <Widget>[
IconButton(
// onPressed: () => _addPerson1(context), // Navigator-style
onPressed: () => _addPerson2(context), // GoRouter-style
tooltip: 'Add Person',
icon: const Icon(Icons.add),
),
],
),
body: ListView(
children: <Widget>[
for (final Person p in widget.family.people)
ListTile(
title: Text(p.name),
onTap: () => context.go(context.namedLocation(
'person',
params: <String, String>{
'fid': widget.family.id,
'pid': p.id
},
queryParams: <String, String>{'qid': 'quid'},
)),
),
],
),
);
// using a Navigator and a Navigator result
// ignore: unused_element
Future<void> _addPerson1(BuildContext context) async {
final Person? person = await Navigator.push<Person>(
context,
MaterialPageRoute<Person>(
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: <String, String>{
'fid': widget.family.id,
'pid': person.id,
});
}
}
// using a GoRouter page
void _addPerson2(BuildContext context) {
context.goNamed('new-person',
params: <String, String>{'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<NewPersonScreen1> createState() => _NewPersonScreen1State();
}
class _NewPersonScreen1State extends State<NewPersonScreen1> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
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: <Widget>[
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: <Widget>[
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<bool> 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<NewPersonScreen2> createState() => _NewPersonScreen2State();
}
class _NewPersonScreen2State extends State<NewPersonScreen2> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
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: <Widget>[
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: <Widget>[
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: <String, String>{
'fid': widget.family.id,
'pid': person.id,
});
}
},
child: const Text('Create'),
),
]),
],
),
),
),
),
),
);
Future<bool> 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;
}
}

View File

@ -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>[
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: <Widget>[
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: <Widget>[
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,
),
);
}

View File

@ -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<String, dynamic> _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>[
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<Family> families;
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -72,10 +101,10 @@ class HomeScreen extends StatelessWidget {
),
body: ListView(
children: <Widget>[
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<String, String> newQueries;
final List<String> names =
family.people.map<String>((Person p) => p.name).toList();
final List<String> names = _families[fid]['people']
.values
.map<String>((dynamic p) => p['name'] as String)
.toList();
names.sort();
if (asc) {
newQueries = const <String, String>{'sort': 'desc'};
} else {
newQueries = const <String, String>{'sort': 'asc'};
}
final Uri iconLink = Uri(path: curentPath, queryParameters: newQueries);
return Scaffold(
appBar: AppBar(
title: Text(family.name),
title: Text(_families[fid]['name']),
actions: <Widget>[
IconButton(
onPressed: () => context.go(iconLink.toString()),
onPressed: () => context.goNamed('family',
params: <String, String>{'fid': fid}, queryParams: newQueries),
tooltip: 'sort ascending or descending',
icon: const Icon(Icons.sort),
)

View File

@ -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.

View File

@ -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<Person> 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<Family> data = <Family>[
Family(
id: 'f1',
name: 'Sells',
people: <Person>[
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>[
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>[
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 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<void> login(String userName) async {
_userName = userName;
notifyListeners();
await Future<void>.delayed(const Duration(microseconds: 2500));
}
/// Logs out the current user.
Future<void> logout() async {
_userName = '';
notifyListeners();
await Future<void>.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<List<Family>> getFamilies() async {
// simulate network delay
await Future<void>.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<Family> getFamily(String fid) async =>
(await getFamilies()).family(fid);
/// Gets a person from the repository.
Future<FamilyPerson> 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<Repository2> get(String userName) async {
// simulate network delay
await Future<void>.delayed(const Duration(seconds: 1));
return Repository2._(userName);
}
/// A random number generator.
static final Random rnd = Random();
/// Gets the families data.
Future<List<Family>> getFamilies() async {
// simulate network delay
await Future<void>.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<Family> getFamily(String fid) async =>
(await getFamilies()).family(fid);
/// Gets a person from the repository.
Future<FamilyPerson> 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<T> {
/// Creates a [StateStream].
StateStream();
/// Creates a [StateStream] with an initial value.
StateStream.seeded(T value) : state = value {
_controller.add(value);
}
final StreamController<T> _controller = StreamController<T>();
/// The state.
late T state;
/// The [Stream] object.
Stream<T> 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<bool> {
/// Creates a [LoggedInState].
LoggedInState();
/// Creates a [LoggedInState] with an initial value.
LoggedInState.seeded(bool value) : super.seeded(value);
}

View File

@ -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 = <String, dynamic>{
@ -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);
});
}