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.
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.
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.dart02Die 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.
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.