mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +08:00
[go_router] cleans up examples (#2440)
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## NEXT
|
||||
|
||||
- Cleans up examples
|
||||
|
||||
## 4.2.7
|
||||
|
||||
- Update README
|
||||
|
@ -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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -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']),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -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].
|
||||
|
@ -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'),
|
||||
);
|
||||
}
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
@ -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),
|
||||
)
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user