Navigation und Datenübergabe zwischen Seiten in Flutter
Seitenwechsel – der Navigator
Als ich zum ersten Mal zwischen Seiten navigieren wollte, war ich etwas ratlos. „Im Web gibt es Links – wie macht Flutter das?“, fragte ich mich. Dann entdeckte ich den Navigator, und alles wurde klar.
In Flutter dient die Klasse Navigator dem Seitenwechsel. Sie arbeitet wie ein Stapel: Sie öffnen eine Seite, sie wird oben aufgelegt; Sie gehen zurück, die oberste Seite wird entfernt.
Live-Demo: der Navigator im Einsatz
Probieren Sie Seitenwechsel und Datenübergabe interaktiv aus:
💡 Falls das Beispiel oben nicht lädt, klicken Sie auf DartPad, um es in einem neuen Tab auszuführen.
Grundlegende Verwendung des Navigator
Der einfachste Weg, von einer Seite zur nächsten zu wechseln:
// Home page
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home Page')),
body: Center(
child: ElevatedButton(
child: Text('Go to Detail Page'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailPage()),
);
},
),
),
);
}
}
// Detail page
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail Page')),
body: Center(
child: Text('This is the detail page'),
),
);
}
}Navigator.push() öffnet eine neue Seite. MaterialPageRoute liefert die Animation für den Seitenwechsel.
Zurückgehen – pop()
Es gibt zwei Wege zurück:
1. Der automatische Zurück-Button
Wenn Sie eine AppBar verwenden, fügt Flutter automatisch einen Zurück-Button hinzu. Nutzer tippen darauf, um zurückzukehren.
2. Programmatisch zurückgehen
ElevatedButton(
child: Text('Go Back'),
onPressed: () {
Navigator.pop(context);
},
)Navigator.pop() entfernt die oberste Seite vom Stapel.
Daten zwischen Seiten übergeben
Jetzt wird es spannend. Um Daten an Seiten zu übergeben, verwenden wir Konstruktoren.
Die sendende Seite
class ProductListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Products')),
body: ListView(
children: [
ListTile(
title: Text('iPhone 15'),
subtitle: Text('\$999'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
productName: 'iPhone 15',
price: 999,
description: 'Next generation smartphone',
),
),
);
},
),
ListTile(
title: Text('Samsung S24'),
subtitle: Text('\$899'),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
productName: 'Samsung S24',
price: 899,
description: 'The best of Android',
),
),
);
},
),
],
),
);
}
}Die empfangende Seite
class ProductDetailPage extends StatelessWidget {
final String productName;
final int price;
final String description;
const ProductDetailPage({
Key? key,
required this.productName,
required this.price,
required this.description,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(productName)),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
productName,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
SizedBox(height: 8),
Text(
'\$${price.toString()}',
style: TextStyle(fontSize: 20, color: Colors.green),
),
SizedBox(height: 16),
Text(
description,
style: TextStyle(fontSize: 16),
),
],
),
),
);
}
}Mit required im Konstruktor machen wir Parameter verpflichtend. So müssen diese Angaben beim Öffnen der Seite übergeben werden.
Beim Zurückgehen Daten zurückgeben
Manchmal möchten Sie von der geöffneten Seite Daten zurückerhalten – etwa wenn die Nutzerin etwas ausgewählt hat und Sie diese Auswahl an die vorherige Seite übergeben wollen.
Die Seite, die Daten anfordert
class SelectCityPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Select City')),
body: Center(
child: ElevatedButton(
child: Text('Choose City'),
onPressed: () async {
// Open page and wait for result
final selectedCity = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => CityListPage()),
);
// Result received
if (selectedCity != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Selected: $selectedCity')),
);
}
},
),
),
);
}
}Die Seite, die Daten zurückgibt
class CityListPage extends StatelessWidget {
final List<String> cities = [
'New York',
'Los Angeles',
'Chicago',
'Houston',
'Phoenix',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('City List')),
body: ListView.builder(
itemCount: cities.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(cities[index]),
onTap: () {
// Send selected city back
Navigator.pop(context, cities[index]);
},
);
},
),
);
}
}Mit Navigator.pop(context, value) übergeben wir die Daten als zweiten Parameter.
pushReplacement – das Zurückgehen verhindern
Manchmal soll die Nutzerin nicht mit dem Zurück-Button auf die vorherige Seite gelangen. Ein typisches Beispiel: nach dem Login nicht zurück zur Anmeldeseite.
Normaler Wechsel mit push()
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
// User can press back to return to login pageWechsel mit pushReplacement()
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
// Back button goes to the page before login, skipping loginPraxisbeispiel: nach dem Login
class LoginPage extends StatelessWidget {
void _login(BuildContext context) {
// Login successful
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text('Login'),
onPressed: () => _login(context),
),
),
);
}
}Wenn Sie nun auf der HomePage zurückgehen, landen Sie nicht wieder auf der LoginPage, sondern verlassen die App.
Beispiel: nach der Registrierung
class RegisterPage extends StatelessWidget {
void _register(BuildContext context) {
// Registration successful
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Registration successful! You can now login.')),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Register')),
body: Center(
child: ElevatedButton(
child: Text('Complete Registration'),
onPressed: () => _register(context),
),
),
);
}
}pushAndRemoveUntil – den gesamten Stapel leeren
Manchmal möchten Sie nicht nur eine Seite, sondern den gesamten Stapel entfernen.
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomePage()),
(route) => false, // Remove all pages
);Das nutzt man, um Nutzer in einen völlig neuen Ablauf zu führen. Beim Abmelden etwa werden alle Seiten entfernt und nur der Anmeldebildschirm bleibt übrig.
Beispiel: Abmelden
void logout(BuildContext context) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => LoginPage()),
(route) => false,
);
}Benannte Routen
In größeren Projekten ist es mühsam, jedes Mal MaterialPageRoute zu schreiben. Dafür gibt es benannte Routen.
Routen in der main.dart definieren
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/detail': (context) => DetailPage(),
'/profile': (context) => ProfilePage(),
'/settings': (context) => SettingsPage(),
},
);
}
}Navigation über benannte Routen
// Normal navigation
Navigator.pushNamed(context, '/detail');
// Prevent back navigation
Navigator.pushReplacementNamed(context, '/home');
// Clear stack
Navigator.pushNamedAndRemoveUntil(
context,
'/login',
(route) => false,
);Daten über benannte Routen übergeben
// Sending data
Navigator.pushNamed(
context,
'/detail',
arguments: {
'id': 123,
'name': 'John',
},
);
// Receiving data
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as Map;
return Scaffold(
appBar: AppBar(title: Text(args['name'])),
body: Center(
child: Text('ID: ${args['id']}'),
),
);
}
}Navigation mit Animation
Sie können unterschiedliche Übergangsanimationen einsetzen:
Von rechts einschieben (Standard)
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailPage()),
);Von unten einschieben
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(),
fullscreenDialog: true,
),
);Eigene Animation
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) => DetailPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 1.0);
const end = Offset.zero;
const curve = Curves.easeInOut;
var tween = Tween(begin: begin, end: end).chain(
CurveTween(curve: curve),
);
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);Den Zurück-Button abfangen
So führen Sie unter Android beim Drücken des Zurück-Buttons eine eigene Aktion aus:
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Ask user
final shouldPop = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('Are you sure you want to exit?'),
actions: [
TextButton(
child: Text('No'),
onPressed: () => Navigator.pop(context, false),
),
TextButton(
child: Text('Yes'),
onPressed: () => Navigator.pop(context, true),
),
],
),
);
return shouldPop ?? false;
},
child: Scaffold(
appBar: AppBar(title: Text('Page')),
body: Center(child: Text('Content')),
),
);
}
}Praktische Tipps
1. Verwenden Sie den Context richtig
// ❌ Wrong - build context usage
Navigator.push(context, ...);
// ✅ Correct - proper context
Builder(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () => Navigator.push(context, ...),
child: Text('Go'),
);
},
)2. async/await nutzen
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectPage()),
);
if (result != null) {
print('Result: $result');
}3. Vermeiden Sie unnötige Navigator-Aufrufe
// ❌ Unnecessary - within the same page
Navigator.push(context, MaterialPageRoute(...));
// ✅ Use state update
setState(() {
selectedIndex = 1;
});Zusammenfassung
Navigation in Flutter:
- push(): Neue Seite öffnen
- pop(): Zurückgehen
- pushReplacement(): Zurückgehen verhindern
- pushAndRemoveUntil(): Stapel leeren
- pushNamed(): Benannte Routen nutzen
Datenübergabe:
- Daten über den Konstruktor nach vorn übergeben
- Daten über
pop(context, data)zurückgeben
Verwaltung des Back-Stacks:
- Nach Login und Registrierung
pushReplacementverwenden - Beim Abmelden den Stapel mit
pushAndRemoveUntilleeren
Der Navigator gehört zu den grundlegendsten Funktionen von Flutter. Wenn Sie sich einmal daran gewöhnt haben, ist er sehr einfach zu nutzen!
Bei der Navigation hängen geblieben?
Bis zum nächsten Artikel! 🚀