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
MaterialAppmit dessen named Constructor.route: Ich übergebe ihm einRouteInformationParserund einRouterDelegate. -
Ich benötige außerdem einen frei definierbaren Datentyp für sogenannte Configuration-Objekte, um Informationen zwischen dem
RouterInformationParserund demRouterDelegateauszutauschen. -
RouterInformationParser:-
parseRouteInformationliest die Adressleiste des Browsers aus und gibt dem System das entsprechende Configuration-Objekt zurück. -
restoreRouteInformationerhält ein Configuration-Objekt und gibt dem Browser Informationen für dessen Adressleiste zurück.
-
-
RouterDelegate:-
setNewRoutePatherhält ein Configuration-Objekt und konfiguriert damit seine Variablen um damit die passende Seite aufzubauen. -
buildbaut die Seite entsprechend seiner Variablen auf. -
currentConfigurationgibt entsprechend seiner Variablen ein Configuration-Objekt zurück. -
navigatorKeywird einmal zu Beginn erstellt und wird vomNavigator-Objekt derbuild-Funktion benötigt.
-
-
return nullscheint mir die einzige Möglichkeit zu sein, um nicht in einer 404-Schleife steckenzubleiben. -
notifyListenersist hier nicht nötig, denn nachsetNewRoutePathruft das System eigenständigbuildauf. -
Hier scheint es zunächst so, als könne man auf
notifyListenersverzichten:onPopPagewird aufgerufen, wenn ich die Back-Taste der App-Bar drücke und das darunter liegendePageerscheint wieder, ohne dass ein erneutesbuildnotwendig 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
showSnackBarwithinbuild. Therefore I usedaddPostFrameCallback.