Beispielarbeiten · Software & Web-Apps
Flutter: Aufgaben-App mit Formular, Prüfung und lokaler Speicherung
Eine kleine App mit Liste, Formular, Abhaken, Löschen und lokaler Speicherung. Analyse ohne Meldung, Unit- und Widget-Tests grün, als Web-App gebaut und im Browser gerendert.
Musterarbeit (Demo), kein Kundenauftrag. Gebaut als Probeaufgabe mit erfundenen Beispieldaten; die Prüfzahlen unten stammen aus dem automatischen Prüflauf und sind nicht geschönt.
Was gebaut wurde
- Liste, Formular mit Eingabeprüfung, Abhaken und Löschen
- Doppelte und zu kurze Titel werden abgelehnt, die Liste bleibt nach dem Neustart erhalten
- Unit- und Widget-Tests, Analyse ohne Meldung
- Gegenprobe: absichtlich kaputte Fassungen werden erkannt

tests.txt herunterladen flutter-app-quellcode.zip herunterladen
Gebaut und gerendert nur als Web-App. Ein Android- oder iOS-Paket ist nicht entstanden, es lief nichts auf einem Gerät oder Emulator; Kamera, GPS, Push und Berechtigungen sind nicht geprüft. Keine Store-Veröffentlichung, keine Signierung, keine Anbindung an Firebase oder ein Backend. Zustand nur mit setState.
Wie geprüft wurde
Automatischer Prüflauf vom 01.11.2026, aus dem Protokoll (Testdaten erfunden, Zahlen gemessen):
- flutter analyze: keine Meldung
- flutter test: 13 Tests (Unit + Widget) gruen
- flutter build web --release: ok, main.dart.js 2388 KB
- Chrome headless: Seitentext ok, Navigation zum Formular ok, Bild 480x800, Textpixel 1578, Markenfarbe 8791
- Gegenprobe 3/3 kaputte Fassungen erkannt
Grenzen
Was diese Probe nicht abdeckt, steht hier, damit niemand mehr erwartet:
- Flutter 3.47.9 / Dart 3.13.7 getestet; nur Web-Ziel gebaut und im Browser (Chrome headless) gerendert; Android-APK/AAB und iOS-IPA nicht gebaut (kein Android SDK/Android Studio, kein Mac/Xcode) …
- kein Lauf auf echtem Geraet oder Emulator: Touch-Gesten, Tastatur, Kamera, GPS, Push, Sensoren, plattformspezifischer Code (Kotlin/Swift, Method Channels), Berechtigungen nicht geprueft
- keine Store-Veroeffentlichung (Google Play, App Store), keine Signierung/Zertifikate/Bundle-IDs; Firebase, Backend-/REST-Anbindung, Login, In-App-Kaeufe, Zahlungen nicht geprueft
- Speicherung nur lokal (shared_preferences); State-Management nur mit setState, kein Provider/Riverpod/Bloc; Integrationstests (integration_test) und Golden-Tests nicht Teil der Probe
- Web-Rendering mit Software-Grafik; Leistung, Ladezeit, Barrierefreiheit und andere Browser (Safari, Firefox) nicht gemessen; Pakete aus pub.dev ausser shared_preferences nicht geprueft
Die Tests
import 'package:aufgaben_app/aufgaben.dart';
import 'package:aufgaben_app/main.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
setUp(() => SharedPreferences.setMockInitialValues({}));
group('Pruefung des Titels', () {
test('leer wird abgelehnt', () {
expect(pruefeTitel('', []), 'Bitte einen Titel eingeben');
expect(pruefeTitel(' ', []), 'Bitte einen Titel eingeben');
expect(pruefeTitel(null, []), 'Bitte einen Titel eingeben');
});
test('zu kurz und zu lang', () {
expect(pruefeTitel('ab', []), 'Mindestens 3 Zeichen');
expect(pruefeTitel('x' * 41, []), 'Hoechstens 40 Zeichen');
expect(pruefeTitel('x' * 40, []), isNull);
});
test('Doppelte werden abgelehnt, egal wie geschrieben', () {
final v = [Aufgabe(titel: 'Einkaufen')];
expect(pruefeTitel('einkaufen ', v), 'Diese Aufgabe gibt es schon');
expect(pruefeTitel('Waesche', v), isNull);
});
});
group('Liste und Speicherung', () {
test('hinzufuegen, umschalten, zaehlen, entfernen', () {
final l = AufgabenListe();
l.hinzufuegen(' Eins ', 'a');
l.hinzufuegen('Zwei', '');
expect(l.eintraege.first.titel, 'Eins');
expect(l.offen, 2);
l.umschalten(0);
expect(l.offen, 1);
expect(l.eintraege[0].erledigt, isTrue);
l.entfernen(0);
expect(l.eintraege.length, 1);
expect(l.eintraege.single.titel, 'Zwei');
});
test('Speichern und Laden ergibt dieselbe Liste', () async {
final l = AufgabenListe();
l.hinzufuegen('Steuer', 'bis Freitag');
l.hinzufuegen('Sport', '');
l.umschalten(1);
await l.speichern();
final neu = await AufgabenListe.laden();
expect(neu.eintraege.map((a) => a.titel), ['Steuer', 'Sport']);
expect(neu.eintraege[0].notiz, 'bis Freitag');
expect(neu.eintraege[1].erledigt, isTrue);
expect(neu.offen, 1);
});
test('ohne gespeicherte Daten ist die Liste leer', () async {
expect((await AufgabenListe.laden()).eintraege, isEmpty);
});
});
group('Bedienung', () {
Future<void> neueAufgabe(WidgetTester t, String titel,
[String notiz = '']) async {
await t.tap(find.byKey(const Key('neu')));
await t.pumpAndSettle();
await t.enterText(find.byKey(const Key('feld-titel')), titel);
if (notiz.isNotEmpty) {
await t.enterText(find.byKey(const Key('feld-notiz')), notiz);
}
await t.tap(find.byKey(const Key('speichern')));
await t.pumpAndSettle();
}
testWidgets('Start zeigt leere Liste', (t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
expect(find.text('Meine Aufgaben'), findsOneWidget);
expect(find.byKey(const Key('leer')), findsOneWidget);
expect(find.text('0 offen'), findsOneWidget);
});
testWidgets('Navigation zum Formular und zurueck', (t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
await t.tap(find.byKey(const Key('neu')));
await t.pumpAndSettle();
expect(find.text('Neue Aufgabe'), findsWidgets);
expect(find.byKey(const Key('feld-titel')), findsOneWidget);
await t.pageBack();
await t.pumpAndSettle();
expect(find.text('Meine Aufgaben'), findsOneWidget);
expect(find.byKey(const Key('feld-titel')), findsNothing);
});
testWidgets('Validierung zeigt Fehler und bleibt im Formular', (t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
await t.tap(find.byKey(const Key('neu')));
await t.pumpAndSettle();
await t.tap(find.byKey(const Key('speichern')));
await t.pumpAndSettle();
expect(find.text('Bitte einen Titel eingeben'), findsOneWidget);
await t.enterText(find.byKey(const Key('feld-titel')), 'ab');
await t.tap(find.byKey(const Key('speichern')));
await t.pumpAndSettle();
expect(find.text('Mindestens 3 Zeichen'), findsOneWidget);
expect(find.byKey(const Key('feld-titel')), findsOneWidget);
});
testWidgets('Eingabe legt Aufgabe an und speichert sie', (t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
await neueAufgabe(t, 'Rechnung schreiben', 'fuer Kunde A');
expect(find.text('Rechnung schreiben'), findsOneWidget);
expect(find.text('fuer Kunde A'), findsOneWidget);
expect(find.text('1 offen'), findsOneWidget);
expect(find.byKey(const Key('leer')), findsNothing);
final gespeichert = await AufgabenListe.laden();
expect(gespeichert.eintraege.single.titel, 'Rechnung schreiben');
});
testWidgets('Doppelte Aufgabe wird im Formular abgelehnt', (t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
await neueAufgabe(t, 'Putzen');
await t.tap(find.byKey(const Key('neu')));
await t.pumpAndSettle();
await t.enterText(find.byKey(const Key('feld-titel')), 'putzen');
await t.tap(find.byKey(const Key('speichern')));
await t.pumpAndSettle();
expect(find.text('Diese Aufgabe gibt es schon'), findsOneWidget);
});
testWidgets('Abhaken und Loeschen wirken und werden gespeichert',
(t) async {
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
await neueAufgabe(t, 'Anruf');
await neueAufgabe(t, 'Mail');
expect(find.text('2 offen'), findsOneWidget);
await t.tap(find.byType(Checkbox).first);
await t.pumpAndSettle();
expect(find.text('1 offen'), findsOneWidget);
expect((await AufgabenListe.laden()).eintraege.first.erledigt, isTrue);
await t.tap(find.byIcon(Icons.delete_outline).first);
await t.pumpAndSettle();
expect(find.text('Anruf'), findsNothing);
expect(find.text('Mail'), findsOneWidget);
expect((await AufgabenListe.laden()).eintraege.length, 1);
});
testWidgets('Gespeicherte Aufgaben erscheinen beim Neustart', (t) async {
SharedPreferences.setMockInitialValues({
AufgabenListe.schluessel:
'[{"titel":"Altlast","notiz":"","erledigt":false}]',
});
await t.pumpWidget(const AufgabenApp());
await t.pumpAndSettle();
expect(find.text('Altlast'), findsOneWidget);
expect(find.text('1 offen'), findsOneWidget);
});
});
}
So etwas für deinen Betrieb?
Beschreib kurz, was du brauchst. Du bekommst einen Festpreis, bevor ich anfange; ohne dein Ja passiert nichts.