Navigation - wurzelsand/flutter-memos GitHub Wiki
Vom Homescreen über ein Button ein Popup-Window öffnen.
import 'package:flutter/material.dart';
void main() {
runApp(const MyNavigationApp());
}
class MyNavigationApp extends StatelessWidget {
const MyNavigationApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(home: MyHomeScreen());
}
}
class MyHomeScreen extends StatelessWidget {
const MyHomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyHomeScreen')),
body: Center(
child: TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const MyDetailScreen()),
),
child: const Text('Open Popup-Window')),
),
);
}
}
class MyDetailScreen extends StatelessWidget {
const MyDetailScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyDetailScreen')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close Popup-Window'),
),
),
);
}
}
flutter.dev/cookbook/returning-data
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
title: 'Returning Data',
home: HomeScreen(),
),
);
}
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Returning Data Demo'),
),
body: const Center(
child: SelectionButton(),
),
);
}
}
class SelectionButton extends StatefulWidget {
const SelectionButton({super.key});
@override
State<SelectionButton> createState() => _SelectionButtonState();
}
class _SelectionButtonState extends State<SelectionButton> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
_navigateAndDisplaySelection(context);
},
child: const Text('Pick an option, any option!'),
);
}
// A method that launches the SelectionScreen and awaits the result from
// Navigator.pop.
Future<void> _navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
// added this myself:
if (result == null) return;
// When a BuildContext is used from a StatefulWidget, the mounted property
// must be checked after an asynchronous gap.
if (!mounted) return;
// After the Selection Screen returns a result, hide any previous snackbars
// and show the new result.
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text('$result')));
}
}
class SelectionScreen extends StatelessWidget {
const SelectionScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Pick an option'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');
},
child: const Text('Yep!'),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
// Close the screen and return "Nope." as the result.
Navigator.pop(context, 'Nope.');
},
child: const Text('Nope.'),
),
)
],
),
),
);
}
}
import 'package:flutter/material.dart';
void main() {
runApp(const MyNavigationApp());
}
class MyNavigationApp extends StatelessWidget {
const MyNavigationApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/': (ctx) => const MyHomeScreen(),
'/details': (ctx) => const MyDetailScreen(),
},
);
}
}
class MyHomeScreen extends StatelessWidget {
const MyHomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyHomeScreen')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pushNamed(
context,
'/details',
),
child: const Text('Open Popup-Window')),
),
);
}
}
class MyDetailScreen extends StatelessWidget {
const MyDetailScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyDetailScreen')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close Popup-Window'),
),
),
);
}
}
Für eine Web-App soll es auch möglich sein, die Adresse einer Seite direkt einzugeben.
import 'package:flutter/material.dart';
void main() {
runApp(const MyNavigationApp());
}
class MyNavigationApp extends StatelessWidget {
const MyNavigationApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(onGenerateRoute: (settings) {
if (settings.name == '/') {
return MaterialPageRoute(builder: (ctx) => const MyHomeScreen());
}
if (settings.name != null) {
final settingsName = settings.name!;
Uri uri = Uri.parse(settingsName);
if (uri.pathSegments.length == 2 && uri.pathSegments.first == 'details') {
final id = uri.pathSegments[1];
return MaterialPageRoute(builder: (ctx) => MyDetailScreen(id: id));
}
}
return MaterialPageRoute(builder: (ctx) => const MyUnknownScreen());
});
}
}
class MyHomeScreen extends StatelessWidget {
const MyHomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyHomeScreen')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pushNamed(
context,
'/details/1',
),
child: const Text('Open Popup-Window')),
),
);
}
}
class MyDetailScreen extends StatelessWidget {
const MyDetailScreen({Key? key, required this.id}) : super(key: key);
final String id;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('MyDetailScreen/$id')),
body: Center(
child: TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close Popup-Window'),
),
),
);
}
}
class MyUnknownScreen extends StatelessWidget {
const MyUnknownScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('MyUnknownScreen')),
body:
Center(child: TextButton(onPressed: () => Navigator.pop(context), child: const Text('Close Unknown Screen'))),
);
}
}
Die vorherige App kann bereits Eingaben in die Adressleiste so umsetzen, dass die passende Seite angezeigt wird (aber nicht umgekehrt). Ich möchte die App außerdem über den History-Button des Bowsers vor- und zurücksteuern können. Dazu schreibe ich eine möglichst minimal gehaltene App mit dem Navigator-2.0-Framework: Es besteht aus einem Detail-Screen und einem Home-Screen das mit einem Button zum Detail-Screen wechseln kann. Ein Unknown-Screen springt ein, wenn ich eine ungültige Adresse in den Browser eingebe.
import 'package:flutter/material.dart';
class MyConfiguration {
const MyConfiguration.details()
: isDetail = true,
isUnknown = false;
const MyConfiguration.unknown()
: isDetail = false,
isUnknown = true;
const MyConfiguration.home()
: isDetail = false,
isUnknown = false;
final bool isDetail;
final bool isUnknown;
}
class MyRouteParser extends RouteInformationParser<MyConfiguration> {
const MyRouteParser() : super();
@override
Future<MyConfiguration> parseRouteInformation(
RouteInformation routeInformation) async {
final location = routeInformation.location!;
final uri = Uri.parse(location);
if (uri.pathSegments.isEmpty) {
return const MyConfiguration.home();
}
if (uri.pathSegments.length == 1 && uri.pathSegments.first == 'details') {
return const MyConfiguration.details();
}
return const MyConfiguration.unknown();
}
@override
RouteInformation? restoreRouteInformation(MyConfiguration configuration) {
if (configuration.isDetail) {
return const RouteInformation(location: '/details');
}
if (configuration.isUnknown) {
return const RouteInformation(location: '/404'); // #1
}
return const RouteInformation(location: '/');
}
}
class MyRouterDelegate extends RouterDelegate<MyConfiguration>
with ChangeNotifier, PopNavigatorRouterDelegateMixin {
@override
MyConfiguration currentConfiguration = const MyConfiguration.unknown();
@override
final navigatorKey = GlobalKey<NavigatorState>();
@override
Future<void> setNewRoutePath(MyConfiguration configuration) async {
currentConfiguration = configuration; // #2
}
@override
Widget build(BuildContext context) {
final homePage = MaterialPage(
key: const ValueKey('home'),
child: Scaffold(
appBar: AppBar(title: const Text('Home')),
body: TextButton(
onPressed: () {
currentConfiguration = const MyConfiguration.details();
notifyListeners();
},
child: const Text('Show Details')),
));
final detailPage = MaterialPage(
key: const ValueKey('details'),
child: Scaffold(
appBar: AppBar(title: const Text('Details')),
));
final unknownPage = MaterialPage(
key: const ValueKey('unknown'),
child: Scaffold(
appBar: AppBar(title: const Text('Unknown')),
));
return Navigator(
key: navigatorKey,
pages: [
homePage,
if (currentConfiguration.isDetail)
detailPage
else if (currentConfiguration.isUnknown)
unknownPage,
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
currentConfiguration = const MyConfiguration.home();
notifyListeners(); // #3
return true;
},
);
}
}
void main() {
runApp(MaterialApp.router(
title: 'Navigator 2.0',
routeInformationParser: const MyRouteParser(),
routerDelegate: MyRouterDelegate()));
}
-
Ich erzeuge die
MaterialApp
mit dessen named Constructor.route
: Ich übergebe ihm einRouteInformationParser
und einRouterDelegate
. -
Ich benötige außerdem einen frei definierbaren Datentyp für sogenannte Configuration-Objekte, um Informationen zwischen dem
RouterInformationParser
und demRouterDelegate
auszutauschen. -
RouterInformationParser
:-
parseRouteInformation
liest die Adressleiste des Browsers aus und gibt dem System das entsprechende Configuration-Objekt zurück. -
restoreRouteInformation
erhält ein Configuration-Objekt und gibt dem Browser Informationen für dessen Adressleiste zurück.
-
-
RouterDelegate
:-
setNewRoutePath
erhält ein Configuration-Objekt und konfiguriert damit seine Variablen um damit die passende Seite aufzubauen. -
build
baut die Seite entsprechend seiner Variablen auf. -
currentConfiguration
gibt entsprechend seiner Variablen ein Configuration-Objekt zurück. -
navigatorKey
wird einmal zu Beginn erstellt und wird vomNavigator
-Objekt derbuild
-Funktion benötigt.
-
-
return null
scheint mir die einzige Möglichkeit zu sein, um nicht in einer 404-Schleife steckenzubleiben. -
notifyListeners
ist hier nicht nötig, denn nachsetNewRoutePath
ruft das System eigenständigbuild
auf. -
Hier scheint es zunächst so, als könne man auf
notifyListeners
verzichten:onPopPage
wird aufgerufen, wenn ich die Back-Taste der App-Bar drücke und das darunter liegendePage
erscheint wieder, ohne dass ein erneutesbuild
notwendig ist. In der Zwischenzeit könnte sich aber das Aussehen der Page, das wieder erscheinen soll, verändert haben.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
final _router = GoRouter(routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'pageA',
builder: (context, state) => const PageA(),
routes: [
GoRoute(
path: 'pageX',
builder: (context, state) => const PageAX(),
),
GoRoute(
path: 'pageY',
builder: (context, state) => const PageAY(),
),
],
),
GoRoute(
path: 'pageB',
builder: (context, state) => const PageB(),
)
],
),
]);
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('HomePage')),
body: Column(children: [
TextButton(
onPressed: () => context.go('/pageA'),
child: const Text('to pageA'),
),
TextButton(
onPressed: () => context.go('/pageB'),
child: const Text('to pageB'),
),
]),
);
}
}
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageA')),
body: Column(children: [
TextButton(
onPressed: () => context.go('/pageA/pageX'),
child: const Text('to pageX'),
),
TextButton(
onPressed: () => context.go('/pageA/pageY'),
child: const Text('to pageY'),
),
]),
);
}
}
class PageAX extends StatelessWidget {
const PageAX({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageAX')),
);
}
}
class PageAY extends StatelessWidget {
const PageAY({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageAY')),
);
}
}
class PageB extends StatelessWidget {
const PageB({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageB')),
);
}
}
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
final _router = GoRouter(routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(state: state),
routes: [
GoRoute(
path: 'pageA',
builder: (context, state) => const PageA(),
routes: [
GoRoute(
path: 'pageX',
builder: (context, state) => const PageAX(),
),
GoRoute(
path: 'pageY',
builder: (context, state) => const PageAY(),
),
],
),
GoRoute(
path: 'pageB',
builder: (context, state) => const PageB(),
)
],
),
]);
class HomePage extends StatelessWidget {
const HomePage({required this.state, super.key});
final GoRouterState state;
@override
Widget build(BuildContext context) {
// >>>
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
String? param = state.queryParams['param'];
if (param != null) {
final snackBar = SnackBar(content: Text(param));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
});
// <<<
return Scaffold(
appBar: AppBar(title: const Text('HomePage')),
body: Column(children: [
TextButton(
onPressed: () => context.go('/pageA'),
child: const Text('to pageA'),
),
TextButton(
onPressed: () => context.go('/pageB'),
child: const Text('to pageB'),
),
]),
);
}
}
class PageA extends StatelessWidget {
const PageA({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageA')),
body: Column(children: [
TextButton(
onPressed: () => context.go('/pageA/pageX'),
child: const Text('to pageX'),
),
TextButton(
onPressed: () => context.go('/pageA/pageY'),
child: const Text('to pageY'),
),
]),
);
}
}
class PageAX extends StatelessWidget {
const PageAX({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageAX')),
);
}
}
class PageAY extends StatelessWidget {
const PageAY({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageAY')),
);
}
}
class PageB extends StatelessWidget {
const PageB({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('PageB')),
// >>>
body: TextButton(
onPressed: () {
final uri = Uri(
path: '/',
queryParameters: <String, String?>{'param': '42'},
);
context.go(uri.toString());
},
child: const Text('Return 42'),
),
// <<<
);
}
}
- You cannot call
showSnackBar
withinbuild
. Therefore I usedaddPostFrameCallback
.