mirror of
https://github.com/flutter/packages.git
synced 2025-07-04 01:33:59 +08:00
[go_router] cleans up examples (#2440)
This commit is contained in:
@ -1,3 +1,7 @@
|
|||||||
|
## NEXT
|
||||||
|
|
||||||
|
- Cleans up examples
|
||||||
|
|
||||||
## 4.2.7
|
## 4.2.7
|
||||||
|
|
||||||
- Update README
|
- Update README
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'shared/data.dart';
|
|
||||||
|
|
||||||
// This scenario demonstrates how to navigate using named locations instead of
|
// This scenario demonstrates how to navigate using named locations instead of
|
||||||
// URLs.
|
// URLs.
|
||||||
//
|
//
|
||||||
@ -15,6 +15,37 @@ import 'shared/data.dart';
|
|||||||
// then be used in context.namedLocation to be translate back to the actual URL
|
// then be used in context.namedLocation to be translate back to the actual URL
|
||||||
// location.
|
// 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());
|
void main() => runApp(App());
|
||||||
|
|
||||||
/// The main app.
|
/// The main app.
|
||||||
@ -41,23 +72,20 @@ class App extends StatelessWidget {
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
builder: (BuildContext context, GoRouterState state) =>
|
||||||
HomeScreen(families: Families.data),
|
const HomeScreen(),
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'family',
|
name: 'family',
|
||||||
path: 'family/:fid',
|
path: 'family/:fid',
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
builder: (BuildContext context, GoRouterState state) =>
|
||||||
FamilyScreen(
|
FamilyScreen(fid: state.params['fid']!),
|
||||||
family: Families.family(state.params['fid']!),
|
|
||||||
),
|
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'person',
|
name: 'person',
|
||||||
path: 'person/:pid',
|
path: 'person/:pid',
|
||||||
builder: (BuildContext context, GoRouterState state) {
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
final Family family = Families.family(state.params['fid']!);
|
return PersonScreen(
|
||||||
final Person person = family.person(state.params['pid']!);
|
fid: state.params['fid']!, pid: state.params['pid']!);
|
||||||
return PersonScreen(family: family, person: person);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -71,10 +99,7 @@ class App extends StatelessWidget {
|
|||||||
/// The home screen that shows a list of families.
|
/// The home screen that shows a list of families.
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
/// Creates a [HomeScreen].
|
/// Creates a [HomeScreen].
|
||||||
const HomeScreen({required this.families, Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
/// The list of families.
|
|
||||||
final List<Family> families;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -84,11 +109,11 @@ class HomeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
for (final Family f in families)
|
for (final String fid in _families.keys)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(f.name),
|
title: Text(_families[fid]['name']),
|
||||||
onTap: () => context.go(context.namedLocation('family',
|
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.
|
/// The screen that shows a list of persons in a family.
|
||||||
class FamilyScreen extends StatelessWidget {
|
class FamilyScreen extends StatelessWidget {
|
||||||
/// Creates a [FamilyScreen].
|
/// 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.
|
/// The id family to display.
|
||||||
final Family family;
|
final String fid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) {
|
||||||
appBar: AppBar(title: Text(family.name)),
|
final Map<String, dynamic> people =
|
||||||
body: ListView(
|
_families[fid]['people'] as Map<String, dynamic>;
|
||||||
children: <Widget>[
|
return Scaffold(
|
||||||
for (final Person p in family.people)
|
appBar: AppBar(title: Text(_families[fid]['name'])),
|
||||||
ListTile(
|
body: ListView(
|
||||||
title: Text(p.name),
|
children: <Widget>[
|
||||||
onTap: () => context.go(context.namedLocation(
|
for (final String pid in people.keys)
|
||||||
'person',
|
ListTile(
|
||||||
params: <String, String>{'fid': family.id, 'pid': p.id},
|
title: Text(people[pid]['name']),
|
||||||
queryParams: <String, String>{'qid': 'quid'},
|
onTap: () => context.go(context.namedLocation(
|
||||||
)),
|
'person',
|
||||||
),
|
params: <String, String>{'fid': fid, 'pid': pid},
|
||||||
],
|
queryParams: <String, String>{'qid': 'quid'},
|
||||||
),
|
)),
|
||||||
);
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The person screen.
|
/// The person screen.
|
||||||
class PersonScreen extends StatelessWidget {
|
class PersonScreen extends StatelessWidget {
|
||||||
/// Creates a [PersonScreen].
|
/// 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);
|
: super(key: key);
|
||||||
|
|
||||||
/// The family this person belong to.
|
/// The id of family this person belong to.
|
||||||
final Family family;
|
final String fid;
|
||||||
|
|
||||||
/// The person to be displayed.
|
/// The id of the person to be displayed.
|
||||||
final Person person;
|
final String pid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) {
|
||||||
appBar: AppBar(title: Text(person.name)),
|
final Map<String, dynamic> family = _families[fid];
|
||||||
body: Text('${person.name} ${family.name} is ${person.age} years old'),
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.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());
|
void main() => runApp(App());
|
||||||
|
|
||||||
@ -18,20 +48,13 @@ class App extends StatelessWidget {
|
|||||||
/// The title of the app.
|
/// The title of the app.
|
||||||
static const String title = 'GoRouter Example: Extra Parameter';
|
static const String title = 'GoRouter Example: Extra Parameter';
|
||||||
|
|
||||||
static const bool _alertOnWeb = true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => _alertOnWeb && kIsWeb
|
Widget build(BuildContext context) => MaterialApp.router(
|
||||||
? const MaterialApp(
|
routeInformationProvider: _router.routeInformationProvider,
|
||||||
title: title,
|
routeInformationParser: _router.routeInformationParser,
|
||||||
home: NoExtraParamOnWebScreen(),
|
routerDelegate: _router.routerDelegate,
|
||||||
)
|
title: title,
|
||||||
: MaterialApp.router(
|
);
|
||||||
routeInformationProvider: _router.routeInformationProvider,
|
|
||||||
routeInformationParser: _router.routeInformationParser,
|
|
||||||
routerDelegate: _router.routerDelegate,
|
|
||||||
title: title,
|
|
||||||
);
|
|
||||||
|
|
||||||
late final GoRouter _router = GoRouter(
|
late final GoRouter _router = GoRouter(
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
@ -39,30 +62,17 @@ class App extends StatelessWidget {
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
builder: (BuildContext context, GoRouterState state) =>
|
||||||
HomeScreen(families: Families.data),
|
const HomeScreen(),
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'family',
|
name: 'family',
|
||||||
path: 'family',
|
path: 'family',
|
||||||
builder: (BuildContext context, GoRouterState state) {
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
final Map<String, Object> params =
|
final Map<String, Object> params =
|
||||||
state.extra! as Map<String, Object>;
|
state.extra! as Map<String, String>;
|
||||||
final Family family = params['family']! as Family;
|
final String fid = params['fid']! as String;
|
||||||
return FamilyScreen(family: family);
|
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.
|
/// The home screen that shows a list of families.
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
/// Creates a [HomeScreen].
|
/// Creates a [HomeScreen].
|
||||||
const HomeScreen({required this.families, Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
/// The list of families.
|
|
||||||
final List<Family> families;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
appBar: AppBar(title: const Text(App.title)),
|
appBar: AppBar(title: const Text(App.title)),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
for (final Family f in families)
|
for (final String fid in _families.keys)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(f.name),
|
title: Text(_families[fid]['name']),
|
||||||
onTap: () => context
|
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.
|
/// The screen that shows a list of persons in a family.
|
||||||
class FamilyScreen extends StatelessWidget {
|
class FamilyScreen extends StatelessWidget {
|
||||||
/// Creates a [FamilyScreen].
|
/// 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.
|
/// The family to display.
|
||||||
final Family family;
|
final String fid;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) {
|
||||||
appBar: AppBar(title: Text(family.name)),
|
final Map<String, dynamic> people =
|
||||||
body: ListView(
|
_families[fid]['people'] as Map<String, dynamic>;
|
||||||
children: <Widget>[
|
return Scaffold(
|
||||||
for (final Person p in family.people)
|
appBar: AppBar(title: Text(_families[fid]['name'])),
|
||||||
ListTile(
|
body: ListView(
|
||||||
title: Text(p.name),
|
children: <Widget>[
|
||||||
onTap: () => context.go(
|
for (final dynamic p in people.values)
|
||||||
context.namedLocation('person'),
|
ListTile(
|
||||||
extra: <String, Object>{'family': family, 'person': p},
|
title: Text(p['name']),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// 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'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -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:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.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());
|
void main() => runApp(App());
|
||||||
|
|
||||||
@ -35,28 +55,7 @@ class App extends StatelessWidget {
|
|||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
builder: (BuildContext context, GoRouterState state) =>
|
||||||
HomeScreenNoLogout(families: Families.data),
|
const HomeScreenNoLogout(),
|
||||||
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);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
name: 'login',
|
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
|
// changes on the listenable will cause the router to refresh it's route
|
||||||
refreshListenable: _loginInfo,
|
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:
|
// add a wrapper around the navigator to:
|
||||||
// - put loginInfo into the widget tree, and to
|
// - put loginInfo into the widget tree, and to
|
||||||
// - add an overlay to show a logout option
|
// - add an overlay to show a logout option
|
||||||
@ -108,7 +91,6 @@ class App extends StatelessWidget {
|
|||||||
ChangeNotifierProvider<LoginInfo>.value(
|
ChangeNotifierProvider<LoginInfo>.value(
|
||||||
value: _loginInfo,
|
value: _loginInfo,
|
||||||
builder: (BuildContext context, Widget? _) {
|
builder: (BuildContext context, Widget? _) {
|
||||||
debugPrint('navigatorBuilder: ${state.subloc}');
|
|
||||||
return _loginInfo.loggedIn ? AuthOverlay(child: child) : child;
|
return _loginInfo.loggedIn ? AuthOverlay(child: child) : child;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -145,70 +127,17 @@ class AuthOverlay extends StatelessWidget {
|
|||||||
/// The home screen without a logout button.
|
/// The home screen without a logout button.
|
||||||
class HomeScreenNoLogout extends StatelessWidget {
|
class HomeScreenNoLogout extends StatelessWidget {
|
||||||
/// Creates a [HomeScreenNoLogout].
|
/// Creates a [HomeScreenNoLogout].
|
||||||
const HomeScreenNoLogout({required this.families, Key? key})
|
const HomeScreenNoLogout({Key? key}) : super(key: key);
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
/// The list of families.
|
|
||||||
final List<Family> families;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
Widget build(BuildContext context) => Scaffold(
|
||||||
appBar: AppBar(title: const Text(App.title)),
|
appBar: AppBar(title: const Text(App.title)),
|
||||||
body: ListView(
|
body: const Center(
|
||||||
children: <Widget>[
|
child: Text('home screen'),
|
||||||
for (final Family f in families)
|
|
||||||
ListTile(
|
|
||||||
title: Text(f.name),
|
|
||||||
onTap: () => context
|
|
||||||
.goNamed('family', params: <String, String>{'fid': 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.
|
/// The login screen.
|
||||||
class LoginScreen extends StatelessWidget {
|
class LoginScreen extends StatelessWidget {
|
||||||
/// Creates a [LoginScreen].
|
/// 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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import 'shared/data.dart';
|
|
||||||
|
|
||||||
// This scenario demonstrates how to use path parameters and query parameters.
|
// This scenario demonstrates how to use path parameters and query parameters.
|
||||||
//
|
//
|
||||||
// The route segments that start with ':' are treated as path parameters when
|
// 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.
|
// 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());
|
void main() => runApp(App());
|
||||||
|
|
||||||
/// The main app.
|
/// The main app.
|
||||||
@ -40,13 +71,14 @@ class App extends StatelessWidget {
|
|||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/',
|
path: '/',
|
||||||
builder: (BuildContext context, GoRouterState state) =>
|
builder: (BuildContext context, GoRouterState state) =>
|
||||||
HomeScreen(families: Families.data),
|
const HomeScreen(),
|
||||||
routes: <GoRoute>[
|
routes: <GoRoute>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'family/:id',
|
name: 'family',
|
||||||
|
path: 'family/:fid',
|
||||||
builder: (BuildContext context, GoRouterState state) {
|
builder: (BuildContext context, GoRouterState state) {
|
||||||
return FamilyScreen(
|
return FamilyScreen(
|
||||||
family: Families.family(state.params['id']!),
|
fid: state.params['fid']!,
|
||||||
asc: state.queryParams['sort'] == 'asc',
|
asc: state.queryParams['sort'] == 'asc',
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@ -59,10 +91,7 @@ class App extends StatelessWidget {
|
|||||||
/// The home screen that shows a list of families.
|
/// The home screen that shows a list of families.
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
/// Creates a [HomeScreen].
|
/// Creates a [HomeScreen].
|
||||||
const HomeScreen({required this.families, Key? key}) : super(key: key);
|
const HomeScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
/// The list of families.
|
|
||||||
final List<Family> families;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -72,10 +101,10 @@ class HomeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
for (final Family f in families)
|
for (final String fid in _families.keys)
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(f.name),
|
title: Text(_families[fid]['name']),
|
||||||
onTap: () => context.go('/family/${f.id}'),
|
onTap: () => context.go('/family/$fid'),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -86,34 +115,35 @@ class HomeScreen extends StatelessWidget {
|
|||||||
/// The screen that shows a list of persons in a family.
|
/// The screen that shows a list of persons in a family.
|
||||||
class FamilyScreen extends StatelessWidget {
|
class FamilyScreen extends StatelessWidget {
|
||||||
/// Creates a [FamilyScreen].
|
/// 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);
|
: super(key: key);
|
||||||
|
|
||||||
/// The family to display.
|
/// The family to display.
|
||||||
final Family family;
|
final String fid;
|
||||||
|
|
||||||
/// Whether to sort the name in ascending order.
|
/// Whether to sort the name in ascending order.
|
||||||
final bool asc;
|
final bool asc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final String curentPath = Uri.parse(GoRouter.of(context).location).path;
|
|
||||||
final Map<String, String> newQueries;
|
final Map<String, String> newQueries;
|
||||||
final List<String> names =
|
final List<String> names = _families[fid]['people']
|
||||||
family.people.map<String>((Person p) => p.name).toList();
|
.values
|
||||||
|
.map<String>((dynamic p) => p['name'] as String)
|
||||||
|
.toList();
|
||||||
names.sort();
|
names.sort();
|
||||||
if (asc) {
|
if (asc) {
|
||||||
newQueries = const <String, String>{'sort': 'desc'};
|
newQueries = const <String, String>{'sort': 'desc'};
|
||||||
} else {
|
} else {
|
||||||
newQueries = const <String, String>{'sort': 'asc'};
|
newQueries = const <String, String>{'sort': 'asc'};
|
||||||
}
|
}
|
||||||
final Uri iconLink = Uri(path: curentPath, queryParameters: newQueries);
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(family.name),
|
title: Text(_families[fid]['name']),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => context.go(iconLink.toString()),
|
onPressed: () => context.goNamed('family',
|
||||||
|
params: <String, String>{'fid': fid}, queryParams: newQueries),
|
||||||
tooltip: 'sort ascending or descending',
|
tooltip: 'sort ascending or descending',
|
||||||
icon: const Icon(Icons.sort),
|
icon: const Icon(Icons.sort),
|
||||||
)
|
)
|
||||||
|
@ -6,14 +6,34 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'shared/data.dart';
|
|
||||||
|
|
||||||
// This scenario demonstrates how to use redirect to handle a sign-in flow.
|
// 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
|
// 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
|
// new page. You can choose to redirect to a different page by returning a
|
||||||
// non-null URL string.
|
// 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());
|
void main() => runApp(App());
|
||||||
|
|
||||||
/// The main 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();
|
await tester.pumpAndSettle();
|
||||||
// 'Chris' should be higher than 'Tom'.
|
// 'Chris' should be higher than 'Tom'.
|
||||||
expect(
|
expect(
|
||||||
tester.getCenter(find.text('Chris')).dy <
|
tester.getCenter(find.text('Jane')).dy <
|
||||||
tester.getCenter(find.text('Tom')).dy,
|
tester.getCenter(find.text('John')).dy,
|
||||||
isTrue);
|
isTrue);
|
||||||
|
|
||||||
testRouteInformation = <String, dynamic>{
|
testRouteInformation = <String, dynamic>{
|
||||||
@ -40,8 +40,8 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
// 'Chris' should be lower than 'Tom'.
|
// 'Chris' should be lower than 'Tom'.
|
||||||
expect(
|
expect(
|
||||||
tester.getCenter(find.text('Chris')).dy >
|
tester.getCenter(find.text('Jane')).dy >
|
||||||
tester.getCenter(find.text('Tom')).dy,
|
tester.getCenter(find.text('John')).dy,
|
||||||
isTrue);
|
isTrue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user