Kodokon kodokon.com

Strukturierte Zustandsverwaltung: ChangeNotifier und Provider

Teile den Anwendungszustand mit ChangeNotifier und Provider und beherrsche watch, read und select für gezielte Rebuilds.

10 Min. · 3 Fragen

Diese Lektion in Kodokon öffnen

setState genügt, solange der Zustand mit einem einzigen Bildschirm lebt und stirbt. Sobald Daten geteilt werden - Warenkorb, Sitzung, Einstellungen - wird das Durchreichen von Konstruktor zu Konstruktor unbeherrschbar. ChangeNotifier liefert das minimale Observable des SDK; provider injiziert es in den Baum und baut nur die abonnierten Widgets neu. Im Vergleich zu Riverpod oder Bloc bleibt dieses Duo am leichtesten überprüfbar: wenig Magie, eine einzige Abhängigkeit, ein Mechanismus, den du in einem Satz erklären kannst.

BASH
flutter pub add provider

Ein gut entworfener Notifier kapselt: private Felder, nur lesende Getter, Änderungen ausschließlich über Methoden, die mit notifyListeners() enden. Genau dieser Vertrag garantiert, dass keine Zustandsänderung an der UI vorbeirutscht.

DART
import 'package:flutter/foundation.dart';

class CartModel extends ChangeNotifier {
  final List<String> _items = [];

  List<String> get items => List.unmodifiable(_items);

  int get count => _items.length;

  void add(String item) {
    _items.add(item);
    notifyListeners();
  }

  void remove(String item) {
    _items.remove(item);
    notifyListeners();
  }
}
Privater Zustand, nach außen nur lesbar, Benachrichtigung.

Drei Einstiegspunkte, drei Verwendungen. context.watch<T>() abonniert das Widget: Es wird bei jedem notifyListeners neu gebaut. context.read<T>() liest, ohne zu abonnieren: Es ist das Werkzeug für Callbacks wie onPressed. context.select<T, R>() abonniert nur eine Projektion: Das Widget wird nur dann neu gebaut, wenn sich genau dieser Wert ändert. Goldene Regel: watch und select nur in build, read in den Handlern - niemals umgekehrt.

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

class CounterModel extends ChangeNotifier {
  int _value = 0;

  int get value => _value;

  void increment() {
    _value++;
    notifyListeners();
  }
}

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CounterModel(),
      child: const CounterApp(),
    ),
  );
}

class CounterApp extends StatelessWidget {
  const CounterApp({super.key});

  @override
  Widget build(BuildContext context) {
    final value = context.watch<CounterModel>().value;
    return MaterialApp(
      home: Scaffold(
        body: Center(child: Text('Count: $value')),
        floatingActionButton: FloatingActionButton(
          onPressed: () =>
              context.read<CounterModel>().increment(),
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}
watch in build zum Lesen, read im Callback zum Handeln.
DART
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class CartModel extends ChangeNotifier {
  final List<String> _items = [];

  int get count => _items.length;
}

class CartBadge extends StatelessWidget {
  const CartBadge({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.select(
      (CartModel cart) => cart.count,
    );
    return Badge(
      label: Text('$count'),
      child: const Icon(Icons.shopping_cart),
    );
  }
}
CartBadge wird nur neu gebaut, wenn sich count ändert.

Wissenscheck

Stelle sicher, dass du die wichtigsten Punkte dieser Lektion behalten hast.

  1. Wie löst du innerhalb eines onPressed ein cart.add(...) aus, ohne ein unnötiges Abonnement zu erzeugen?
    • context.watch<CartModel>().add(...)
    • context.read<CartModel>().add(...)
    • context.select((CartModel c) => c.add(...))
  2. Was genau löst notifyListeners() aus?
    • Den Rebuild nur der Widgets, die die geänderten Daten verwenden.
    • Einen vollständigen Rebuild der Anwendung.
    • Den Rebuild aller Widgets, die den Notifier abonniert haben, unabhängig davon, welche Daten sich geändert haben.
  3. Warum sollte man List.unmodifiable(_items) statt _items direkt nach außen geben?
    • Um die Leseperformance der Liste zu verbessern.
    • Um eine externe Änderung zu verhindern, die den Zustand ohne notifyListeners und damit ohne Rebuild verändern würde.
    • Weil Provider sich weigert, veränderliche Listen zu injizieren.