Grundgerüst für die Emergency-App: Flutter-Projekt für Android/iOS, Kern-Packages (url_launcher, geolocator, geocoding, permission_handler, provider, shared_preferences, intl), Ordnerstruktur laut docs/plan.md und l10n-Grundgerüst. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.6 KiB
Architektur- & Implementierungsplan: Emergency App
Status: entscheidungsreif (alle offenen Fragen aus der Architektur-Review geklärt am 2026-07-24)
Basis: ../anforderung.md
1. Kontext
Flutter-App (Android + iOS) mit sicherheitskritischer Kernfunktion: Notruf auslösen. Die App ermittelt das aktuelle Land, zeigt einen oder mehrere große Buttons mit den landesspezifischen Notrufnummern (Allgemein / Feuerwehr / Polizei / Rettung), ein Tap öffnet die native Telefon-App mit vorgewählter Nummer.
Grundsatz: Diese App ist ein Sicherheitswerkzeug. Sie muss offline funktionieren, schnell starten, und darf niemals in einem Zustand landen, in dem keine Notrufnummer erreichbar ist. Jede Architekturentscheidung ordnet sich diesem Grundsatz unter.
2. Getroffene Entscheidungen
| Thema | Entscheidung | Begründung |
|---|---|---|
| Persistenz | shared_preferences (kein Hive) |
Nur 1-2 Strings zu cachen (letzter Ländercode, manuelle Auswahl); Notrufnummern sind read-only JSON-Asset. Kein build_runner/TypeAdapter-Overhead nötig. |
| Ländererkennung | SIM/Netz-Ländercode zuerst, GPS+Reverse-Geocoding als Fallback | Offline verfügbar; zeigt an, in welchem Mobilfunknetz man eingebucht ist – genau dieses Netz wickelt den Notruf ab. Bei Konflikt (Reisender mit Heimat-SIM) gewinnt automatisch die SIM-Quelle, kein manueller Auswahldialog nötig. |
| Notruf-Auslösung | tel:-Scheme öffnet Wähler, kein Auto-Dial |
Sicherer gegen Fehlauslösung; identisches Verhalten auf Android und iOS (Auto-Dial auf iOS technisch ohnehin nicht möglich, auf Android nur mit heikler CALL_PHONE-Permission). |
| Panikmodus | Globaler 112/911-Button immer sichtbar, auch während Ländererkennung läuft | Kein Ladezustand darf ohne funktionierenden Notruf-Button sein. |
| Lokalisierung | Sprache an erkanntes Land gekoppelt | Automatische UI-Sprache passend zum Land statt nur DE/EN. Mehr ARB-Dateien/Übersetzungsaufwand bereits in v1 eingeplant. |
| Disclaimer | Permanent sichtbar/abrufbar (z. B. Info-/Einstellungsbereich) | Haftungsrisiko bei falschen/veralteten Nummern; Hinweis „im Zweifel 112 wählen". |
| Datenpflege | Statisches JSON-Asset, kein Remote-Update-Mechanismus | Passt zum Offline-Grundsatz; Aktualisierung nur über App-Updates. |
3. Projekt-Setup
flutter create --org co.selfhost.hhml --platforms=android,ios emergency
Danach git init.
Packages (pubspec.yaml)
| Package | Zweck |
|---|---|
url_launcher |
tel:-Scheme → native Telefon-App |
geolocator |
GPS-Position (Fallback-Quelle) |
geocoding |
Reverse-Geocoding lat/lng → ISO-Ländercode (braucht ggf. Netz) |
Platform-Channel bzw. Package für SIM-/Netz-Ländercode (Android: TelephonyManager.getNetworkCountryIso(); iOS: CoreTelephony, ab iOS 16 eingeschränkt zuverlässig – Rechercheposten in AP4) |
Primärquelle Ländererkennung |
permission_handler |
Standort-Berechtigungs-UX |
provider |
State Management (ChangeNotifier) |
shared_preferences |
Cache: letzter Ländercode, manuelle Auswahl |
flutter_localizations / intl |
Lokalisierung, gekoppelt an erkanntes Land |
4. Datenmodell & Datenhaltung
Quelle: Wikipedia „List of emergency telephone numbers" als Ausgangsbasis, Gegenprüfung via ITU-T/offizielle Behördenseiten. Einmalig aufbereitet, als JSON versioniert, kein Live-Fetch.
Format (assets/data/emergency_numbers.json), ISO 3166-1 alpha-2 als Schlüssel:
{
"DE": {
"name_en": "Germany", "name_de": "Deutschland", "flag": "🇩🇪",
"numbers": [
{ "type": "general", "number": "112", "label_de": "Notruf", "label_en": "Emergency" },
{ "type": "police", "number": "110", "label_de": "Polizei", "label_en": "Police" }
]
}
}
type-Enum: general | police | fire | ambulance | sea_rescue | mountain_rescue (für typspezifische Icons/Farben).
Dart-Modelle: EmergencyNumber, Country (mit fromJson).
Pflege: JSON im Repo, reviewbar per Diff. Optionales Hilfsskript (tool/) zur Generierung aus CSV, kein App-Bestandteil.
5. App-Architektur
State Management: Ein EmergencyProvider (ChangeNotifier).
EmergencyProvider
├─ state: EmergencyState { status, country, numbers, errorType }
│ status ∈ { initializing, locating, ready, permissionDenied,
│ countryUnknown, error }
├─ init() → startet Ermittlungskette
├─ retry()
└─ setCountryManually()
Ordnerstruktur
lib/
├─ main.dart
├─ app.dart
├─ models/
│ ├─ country.dart
│ ├─ emergency_number.dart
│ └─ emergency_type.dart
├─ repositories/
│ └─ emergency_number_repository.dart
├─ services/
│ ├─ location_service.dart # Ermittlungskette
│ ├─ country_code_sources/ # SIM, GPS, cached, manual
│ └─ dialer_service.dart
├─ providers/
│ └─ emergency_provider.dart
├─ features/
│ └─ home/
│ ├─ home_screen.dart
│ └─ widgets/
│ ├─ emergency_button.dart # inkl. globaler Panik-Button
│ ├─ country_header.dart
│ └─ status_banner.dart
├─ l10n/
│ └─ app_<lang>.arb # pro unterstütztem Land/Sprache
└─ core/
├─ constants.dart # Fallback-Nummern, Default-Land
└─ result.dart
assets/data/emergency_numbers.json
Schichtentrennung: UI kennt nur den Provider; Provider orchestriert Services + Repository ohne Flutter-Context; Services/Repository sind plattformnah bzw. datennah und gut testbar.
6. Ablauf
App-Start
→ EmergencyProvider.init()
→ globaler 112/911-Button sofort sichtbar (Panikmodus)
→ LocationService.resolveCountryCode():
1. SIM/Netz-Ländercode (offline, primär)
2. GPS + Reverse-Geocoding (Fallback, ggf. Netz nötig)
3. zuletzt gespeicherter Code (shared_preferences)
4. sonst → status = countryUnknown
→ Repository.lookup(isoCode) → Country + numbers
→ Cache: isoCode in shared_preferences speichern
→ UI-Sprache an erkanntes Land koppeln
→ status = ready
→ HomeScreen rendert länderspezifische EmergencyButtons zusätzlich zum Panik-Button
→ Tap → DialerService.call(number) → url_launcher launchUrl(Uri(scheme:'tel', path:number))
→ Telefon-App öffnet sich mit vorgewählter Nummer, Nutzer bestätigt selbst
7. Fehlerfälle / Edge Cases
| Fall | Verhalten |
|---|---|
| Standort-Berechtigung verweigert | App bleibt voll nutzbar; SIM-Ländercode/Cache/manuelle Auswahl, nicht-blockierender Banner. |
| GPS aus / kein Fix | Kette fällt auf SIM/Cache/manuell zurück. |
| Kein Internet | Kernfunktion voll erhalten (JSON-Asset lokal, SIM-Code offline, Wähler braucht kein Netz); nur Reverse-Geocoding kann ausfallen. |
| Land nicht in Datenbasis | Fallback auf 112 (GSM-weit) und 911, mit Hinweis. |
| Land nicht ermittelbar | countryUnknown-Screen: Panik-Button + Länder-Suchfeld. |
| Mehrere Nummern pro Land | Liste von Buttons, general zuoberst/am größten. |
tel: nicht öffnbar (z. B. Tablet) |
canLaunchUrl-Check; sonst Nummer groß anzeigen + Copy-Button. |
| Ladezustand | Panik-Button von Anfang an sichtbar (siehe Ablauf). |
Plattformspezifika
- Android:
ACCESS_FINE/COARSE_LOCATION-Permissions;<queries>-Eintrag fürtel:-Scheme (Android 11+ Package Visibility). - iOS:
NSLocationWhenInUseUsageDescription;LSApplicationQueriesSchemesmittel. SIM-Ländercode über CoreTelephony ab iOS 16 unzuverlässig → GPS-Fallback wichtiger auf iOS. - Kein Web/Desktop (Scope laut Anforderung nur Android/iOS).
8. Schritt-für-Schritt-Implementierungsplan
- AP0 – Setup:
flutter create, Git-Init, Packages, Ordnerstruktur, l10n-Grundgerüst. - AP1 – Datenbasis: Notrufnummern recherchieren/aufbereiten →
emergency_numbers.json; Modelle;EmergencyNumberRepository+ Unit-Tests. - AP2 – Dialer:
DialerServicemiturl_launcher,canLaunchUrl-Guard; Manifest/Plist-Einträge. - AP3 – Statisches UI:
HomeScreen+EmergencyButtoninkl. Panik-Button, zunächst mit hartkodiertem Land, damit der Notruf-Flow früh end-to-end testbar ist. - AP4 – Ländererkennung:
LocationServicemit priorisierter Kette (SIM → GPS/Geocoding → Cache → unknown). Rechercheposten: verlässliches Package/Platform-Channel für SIM-Ländercode je Plattform, insbesondere iOS-16-Einschränkung. - AP5 – State-Integration:
EmergencyProvider, Verdrahtung Service↔Repository↔UI, allestatus-Zustände. - AP6 – Fallbacks & manuelle Auswahl:
CountryPickerScreen, Cache viashared_preferences, globale 112/911-Fallbacks. - AP7 – Lokalisierung: UI-Sprache an erkanntes Land koppeln, ARB-Dateien für Zielsprachen.
- AP8 – Disclaimer & Feinschliff: Permanent abrufbarer Haftungsausschluss, Barrierefreiheit (große Touch-Ziele, hoher Kontrast, Screenreader-Labels), Datenstand-Hinweis, App-Icon/Name.
- AP9 – Tests & Härtung: Unit-Tests Repository/Service-Kette, Widget-Tests HomeScreen-Zustände, manuelle Tests auf realen Geräten (Flugmodus, ohne SIM, Ausland-Simulation).
- AP10 – Store-Vorbereitung: Datenschutzerklärung (Standortnutzung), Store-Texte, Screenshots.
9. Verbleibendes Risiko
- SIM-Ländercode auf iOS 16+:
CTCarrier.isoCountryCodeist deprecated und liefert teils unzuverlässige Werte. Konkrete Lösung (Package-Wahl oder Eigenimplementierung via Platform-Channel) muss in AP4 recherchiert/prototypisch verifiziert werden. - Rechtliche Absicherung des Disclaimers: Formulierung sollte vor Store-Veröffentlichung juristisch geprüft werden.