Skip to content

Kapitel 4: Flutter Kernkonzepte

4.1 Widget (Komponente)

Was ist ein Widget?

In Flutter ist alles ein Widget!

  • UI-Elemente (Text, Button, Image) sind Widgets
  • Layout-Elemente (Row, Column, Container) sind Widgets
  • Gesamte Seiten sind Widgets
Widget-Baum (Widget Tree):
MyApp (Widget)
└── MaterialApp (Widget)
    └── Scaffold (Widget)
        ├── AppBar (Widget)
        └── Body (Widget)
            ├── Text (Widget)
            └── Button (Widget)

Widget-Klassifizierung

1. StatelessWidget (Zustandslose Komponente)

Eigenschaften:

  • ✅ Unveränderlich (immutable)
  • ✅ Kein interner Zustand
  • ✅ Wird nur neu gebaut, wenn externe Daten sich ändern
  • ✅ Gut für statische Inhalte
dart
class MeinText extends StatelessWidget {
  final String text;  // Final = kann nicht geändert werden
  
  const MeinText(this.text, {super.key});  // Konstruktor
  
  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: TextStyle(fontSize: 20),
    );
  }
}

// Verwendung:
MeinText('Hallo Welt')  // Zeigt Text an, ändert sich nie

Anwendungsfälle für StatelessWidget:

  • Texte, Bilder, Icons
  • Statische Menüs
  • About-Seiten
  • Header/Footer

2. StatefulWidget (Zustandsbehaftete Komponente)

Eigenschaften:

  • ✅ Hat einen veränderlichen Zustand (State)
  • ✅ UI aktualisiert sich bei Zustandsänderung (durch setState())
  • ✅ Gut für interaktive Inhalte
dart
class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int _zaehler = 0;  // Zustandsvariable
  
  void _erhoehen() {
    setState(() {  // WICHTIG: setState() aktualisiert UI
      _zaehler++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Zähler: $_zaehler'),
        ElevatedButton(
          onPressed: _erhoehen,
          child: Text('Erhöhen'),
        ),
      ],
    );
  }
}

// Verwendung:
Counter()  // Zeigt Zähler an, der sich ändert

Anwendungsfälle für StatefulWidget:

  • Formulareingaben
  • Animations-Steuerung
  • Zähler, Timer
  • API-Datenabruf

Unterschied: Stateless vs Stateful

MerkmalStatelessWidgetStatefulWidget
ZustandKein ZustandHat Zustand (State)
VeränderlichkeitUnveränderlichVeränderlich
PerformanceBesser (weniger Overhead)Schlechter (Rebuilding)
KomplexitätEinfacherKomplexer
AnwendungStatische InhalteInteraktive Inhalte

Entscheidungshilfe:

Brauche ich interaktive Elemente?
├── NEIN → StatelessWidget
└── JA → StatefulWidget
    ├── Einfache Interaktion → StatefulWidget
    └── Komplexe Zustandsverwaltung → Provider/Bloc (später)

4.2 BuildContext (Kontext)

Was ist BuildContext?

BuildContext ist der Standort eines Widgets im Widget-Baum.

dart
Widget build(BuildContext context) {  // ← Das ist der BuildContext!
  return Container();
}

Wofür wird BuildContext verwendet?

  1. Auf übergeordnete Widgets zugreifen
dart
// Theme abrufen (von übergeordnetem MaterialApp-Widget)
final farbe = Theme.of(context).primaryColor;

// MediaQuery abrufen (Bildschirmgröße)
final breite = MediaQuery.of(context).size.width;
  1. Routing (Seitennavigation)
dart
// Zu einer neuen Seite navigieren
Navigator.of(context).push(
  MaterialPageRoute(builder: (context) => NeueSeite()),
);
  1. Snackbar anzeigen
dart
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(content: Text('Hallo!')),
);

Wichtig für Anfänger:

  • context ist immer verfügbar in der build()-Methode
  • context wird verwendet, um auf übergeordnete Widgets zuzugreifen
  • ❌ Nicht in asynchronen Operationen nach await verwenden (Gefahr von "Unmounted Widget" Fehler)
dart
// RICHTIG: Context vor await verwenden
Future<void> ladeDaten() async {
  final navigator = Navigator.of(context);  // Context speichern
  await Future.delayed(Duration(seconds: 2));
  navigator.push(...);  // Navigator verwenden
}

// FALSCH: Context nach await verwenden (Widget könnte bereits zerstört sein)
Future<void> ladeDaten() async {
  await Future.delayed(Duration(seconds: 2));
  Navigator.of(context).push(...);  // FEHLER: Context könnte ungültig sein!
}

4.3 Hot Reload & Hot Restart

Hot Reload (r)

Was es tut:

  • ✅ Aktualisiert UI sofort
  • ✅ Behält App-Zustand bei (Variablen, Daten)
  • ✅ Funktioniert nur im Debug-Modus
  • ✅ Sehr schnell (Millisekunden)

Wann verwenden:

  • UI-Änderungen (Farben, Texte, Layout)
  • Widget-Struktur ändern
  • Styling anpassen

Einschränkungen:

  • ❌ Keine Änderungen an main() oder initState()
  • ❌ Keine statische Variablen-Änderungen
  • ❌ Keine Änderungen an Enums oder Typen
dart
// Beispiel: Hot Reload funktioniert
// Ändern Sie dies und speichern Sie (r drücken):
Text('Hallo') → Text('Welt')

// BEISPIEL: Hot Reload funktioniert NICHT (Hot Restart R benötigt)
int zaehler = 0;  // Statische Variable
// Ändern zu: int zaehler = 100;  → Hot Reload reicht nicht!

Hot Restart (R)

Was es tut:

  • ✅ Startet App neu
  • ✅ Setzt App-Zustand zurück
  • ✅ Etwas langsamer als Hot Reload

Wann verwenden:

  • Änderungen an main()
  • Änderungen an initState()
  • Statische Variablen ändern
  • Enums oder Typen ändern

Full Restart

Was es tut:

  • ✅ Stoppt und startet App komplett neu
  • ✅ Erforderlich bei Änderungen an nativen Dateien

Wann verwenden:

  • Änderungen an AndroidManifest.xml oder Info.plist
  • Neue Plugins hinzufügen (pubspec.yaml Änderungen)
  • Native Code-Änderungen

4.4 Flutter-Rendering (vereinfacht)

3 Bäume in Flutter

Flutter verwendet drei Bäume für das Rendering:

1. Widget Tree (Widget-Baum)
   └── Beschreibung der UI (Konfiguration)
       "Ich möchte einen blauen Button mit Text 'Klick'"

2. Element Tree (Element-Baum)
   └── Verbindung zwischen Widget und Render-Baum
       Verwaltet die Lebensdauer von Widgets

3. Render Tree (Render-Baum)
   └── Tatsächliches Rendering (Größe, Position, Malen)
       Berechnet: Wo? Wie groß? Welche Farbe?

Vereinfachter Ablauf:

1. Sie schreiben Widgets (Widget Tree)

2. Flutter erstellt Elemente (Element Tree)

3. Flutter berechnet Layout & Malen (Render Tree)

4. Benutzer sieht UI auf dem Bildschirm

Warum das wichtig ist:

  • ✅ Durch Hot Reload: Nur betroffene Widgets werden neu gebaut
  • ✅ Durch setState(): Nur dieser Teilbaum wird neu gerendert
  • ✅ Performance: Flutter ist schnell, weil es die Bäume intelligent verwaltet

Vereinfachtes Beispiel:

dart
// Widget Tree (Ihre Code)
Container(
  color: Colors.blue,
  child: Text('Hallo'),
)

// Element Tree (Flutter intern)
ContainerElement
└── TextElement

// Render Tree (Flutter intern)
RenderPositionedBox (Layout)
└── RenderParagraph (Text rendern)

4.5 Praxisbeispiel: Widgets erstellen & Hot Reload testen

Schritt 1: StatelessWidget erstellen

dart
import 'package:flutter/material.dart';

class Begruessung extends StatelessWidget {
  final String name;
  
  const Begruessung(this.name, {super.key});
  
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.blue.shade100,
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        'Hallo, $name!',
        style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
      ),
    );
  }
}

Schritt 2: StatefulWidget erstellen

dart
class ZaehlerWidget extends StatefulWidget {
  const ZaehlerWidget({super.key});

  @override
  State<ZaehlerWidget> createState() => _ZaehlerWidgetState();
}

class _ZaehlerWidgetState extends State<ZaehlerWidget> {
  int _zaehler = 0;
  
  void _erhoehen() {
    setState(() {
      _zaehler++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Text('Zähler: $_zaehler', style: const TextStyle(fontSize: 32)),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: _erhoehen,
          child: const Text('Erhöhen'),
        ),
      ],
    );
  }
}

Schritt 3: In main.dart verwenden

dart
// In Ihrer main.dart, in der build()-Methode:
body: Center(
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: <Widget>[
      Begruessung('Max'),  // StatelessWidget
      const SizedBox(height: 40),
      ZaehlerWidget(),       // StatefulWidget
    ],
  ),
),

Schritt 4: Hot Reload testen

  1. App ausführen (flutter run)
  2. Ändern Sie Colors.blue.shade100 zu Colors.green.shade100
  3. Speichern Sie die Datei (Ctrl + S)
  4. Beobachten Sie: Farbe ändert sich sofort!

Schritt 5: StatefulWidget testen

  1. Klicken Sie auf "Erhöhen" → Zähler erhöht sich
  2. Ändern Sie Text('Zähler: $_zaehler') zu Text('Wert: $_zaehler')
  3. Hot Reload (r) → Text ändert sich, aber Zählerwert bleibt erhalten!
  4. Hot Restart (R) → Zähler wird auf 0 zurückgesetzt

Zusammenfassung

In diesem Kapitel haben Sie:

  • ✅ Verstanden, dass alles in Flutter ein Widget ist
  • ✅ Den Unterschied zwischen StatelessWidget und StatefulWidget gelernt
  • ✅ BuildContext verstanden (Kontext im Widget-Baum)
  • ✅ Hot Reload & Hot Restart meisterhaft eingesetzt
  • ✅ Das Flutter-Rendering (3 Bäume) vereinfacht verstanden
  • ✅ Praxisbeispiele erstellt und Hot Reload getestet

Nächstes Kapitel: Wir werden Flutter-Basis-Widgets lernen (Text, Image, Button, TextField).


Übungsaufgaben:

  1. Erstellen Sie ein StatelessWidget, das ein Profilbild (CircleAvatar) anzeigt
  2. Erstellen Sie ein StatefulWidget, das eine To-Do-Liste mit Hinzufügen-Button implementiert
  3. Testen Sie Hot Reload: Ändern Sie Farben, Texte und beobachten Sie die sofortige Aktualisierung
  4. Testen Sie Hot Restart: Ändern Sie eine Initialisierungsvariable und beobachten Sie den Unterschied

Häufige Fehler:

  • setState() vergessen → UI aktualisiert sich nicht
  • ❌ BuildContext nach await verwenden → "Unmounted Widget" Fehler
  • ❌ Zustand in StatelessWidget speichern wollen → Verwenden Sie StatefulWidget!
  • ❌ Hot Reload erwarten, wenn main() geändert wurde → Verwenden Sie Hot Restart

Frei für alle Anfänger