İçeriğe geç / Skip to content / Zum Inhalt
Ahmet Balaman LogoAhmet Balaman

Navigation und Datenübergabe zwischen Seiten in Flutter

Ahmet Balaman
FlutterNavigatorRoutingNavigationState Management

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 page

Wechsel mit pushReplacement()

Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => HomePage()),
);
// Back button goes to the page before login, skipping login

Praxisbeispiel: 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 pushReplacement verwenden
  • Beim Abmelden den Stapel mit pushAndRemoveUntil leeren

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! 🚀

Kommentare