← Flutter / Dart

Flutter / Dart · 12 / 18

Clean MVVM, Services und Repositories

Du trennst Darstellung, Fachregeln und Speicherung mit austauschbaren Verträgen.

  • Mittel
  • Fortgeschritten
  • 45 min

Voraussetzungen: Riverpod: Zustand beobachten und verändern

Clean MVVM beschreibt Zuständigkeiten, nicht bloß Ordnernamen. Die View zeigt Zustand und nimmt Aktionen entgegen. Ein ViewModel hält den Bildschirmzustand. Ein Service koordiniert einen fachlichen Ablauf. Ein Repository beschreibt den Zugriff auf gespeicherte Daten. Wir testen diese Trennung zunächst ohne Flutter. Fahrzeugakte verwendet Riverpod zur Verdrahtung und unterschiedliche Datenimplementierungen für verschiedene Plattformen.

Beispieldateien

01Den Vertrag definieren

VehicleRepository beschreibt save und all. Der Service hängt von diesem Vertrag ab. MemoryRepository ist nur ein Lernadapter. Er verliert seine Daten beim Beenden des Programms.

architecture.dart · dartDatei öffnen
abstract interface class VehicleRepository {
  Future<void> save(String name);
  Future<List<String>> all();
}

class MemoryRepository implements VehicleRepository {
  final values = <String>[];
  @override
  Future<void> save(String name) async {
    values.add(name);
  }

  @override
  Future<List<String>> all() async => List.unmodifiable(values);
}

class VehicleService {
  VehicleService(this.repository);
  final VehicleRepository repository;
  Future<void> create(String input) async {
    final name = input.trim();
    if (name.isEmpty) throw ArgumentError('Name required');
    await repository.save(name);
  }
}

class VehicleViewModel {
  VehicleViewModel(this.service);
  final VehicleService service;
  String message = '';
  Future<void> submit(String input) async {
    try {
      await service.create(input);
      message = 'Saved';
    } catch (error) {
      message = error.toString();
    }
  }
}

Future<void> main() async {
  final repository = MemoryRepository();
  final model = VehicleViewModel(VehicleService(repository));
  await model.submit('  ');
  print(model.message);
  await model.submit(' Family car ');
  print(await repository.all());
}

Starten / prüfen

dart run architecture.dart

02Die Regel im Service halten

VehicleService normalisiert den Namen und lehnt leere Eingaben ab. Das gilt unabhängig davon, ob später ein Formular, ein Import oder ein Test den Ablauf aufruft. Der Service greift nicht auf einen BuildContext zu.

03Bildschirmzustand abbilden

VehicleViewModel übersetzt Erfolg und Fehler in einen sichtbaren Zustand. Dieses kleine Modell hält nur eine Meldung. Eine echte Oberfläche benötigt zusätzlich Ladezustand und Schutz vor gleichzeitigen Schreibaktionen.

04Den Adapter austauschen

Ersetze nur die Repository-Implementierung. Der Service soll seine Regeln behalten. Vergleiche danach die verlinkten App-Dateien: Dort heißen einige kopierte Template-Bausteine noch Meter. Die Fahrzeugdomäne wird durch ihre Felder und Abläufe bestimmt, nicht durch einen nachträglich erfundenen Klassennamen.

Erwartetes Ergebnis

Ein leerer Name zeigt Name required. Der bestätigte Name wird als Family car gespeichert.

Deine Übung

Implementiere ein Repository, dessen save einen StateError wirft. Prüfe, ob Saved trotzdem angezeigt wird.

Lösung mit Erklärung anzeigen

Der Fehler wird nach await an das ViewModel weitergegeben. Saved wird nicht gesetzt. Eine echte Oberfläche sollte eine übersetzte Fehlermeldung zeigen und den Entwurf erhalten. Rohes error.toString dient hier nur zum Lernen.

Projektbezug und Quellen

Das Beispiel ist zum Lernen vereinfacht. Die verlinkte Codeversion zeigt die Anwendung im Projekt und die dortigen Prüfungen.