ZuZweit – Gemeinsame Wünsche & Erinnerungen

Von Moritz Leopold

Erstellt am 9. März 2026


Zum GitHub Repository →

ZuZweit ist eine Android‑App für Paare, die gemeinsame Wünsche sammelt und erfüllte Erlebnisse als Erinnerungen festhält. Ziel des Projekts war es, Android‑Entwicklung von Grund auf zu lernen und gleichzeitig ein realistisches Produkt mit Auth, Datenmodell, Bildpipeline und Push‑Flows umzusetzen.


🎯 Zielsetzung

Warum dieses Projekt? Viele Paar‑Apps sind überladen. Ich wollte eine fokussierte, schnelle App, die den Kern abdeckt: gemeinsame Wünsche, Erinnerungen, Fotos und persönliche Notizen. Das Projekt diente als Lernpfad für Jetpack Compose, Firebase und saubere App‑Architektur.

Lernziele

  • Jetpack Compose UI + State‑Management
  • Firebase Auth, Firestore, Storage, Messaging
  • Bildhandling (Kamera, Galerie, Kompression)
  • Cloud Functions für serverseitige Logik
  • Offline‑Strategien und UX‑Feedback

✨ Überblick

Funktionen

  • Registrierung, Login, Passwort‑Reset per Deep Link
  • Partner‑Verknüpfung via Verbindungscode
  • Gemeinsame Wunschliste mit CRUD
  • Wunsch‑Erfüllung per Kamera oder Galerie
  • Erinnerungs‑Detailseite mit Journal‑Eintrag
  • Dynamische UI‑Farben aus dem Erinnerungsbild
  • Push‑Benachrichtigungen bei neuen Wünschen

User Journey (kurz)

  • Nutzer registriert sich und erhält einen Verbindungscode
  • Partner verbindet sich mit Code und vergibt einen Spitznamen
  • Beide erstellen Wünsche, markieren sie als erledigt und laden Bilder hoch
  • Erledigte Wünsche erscheinen als Erinnerungen mit Galerie und Journal

🧱 Architektur & Datenfluss

Architektur

  • MVVM mit StateFlow/SharedFlow in viewmodel/*
  • Firestore Snapshot‑Listener für Live‑Updates
  • Trennung von UI (Compose) und Datenzugriff (Repository, ViewModels)
  • Bildpipeline: Kamera/Galerie → Kompression → Storage → Farben aus Bild → dynamische UI
  • Partner‑Kontext als globaler Filter (Pair‑ID) für Wünsche/Erinnerungen

Module

  • app/ Android‑App
  • functions/ Firebase Cloud Functions

🔍 Stand der Technik (im Projektkontext)

Jetpack Compose Deklaratives UI‑Framework für Android. Im Projekt verwendet für alle Screens, Bottom‑Navigation, Bottom‑Sheets und Dialoge.

Firebase Realtime‑Backend mit Auth, Firestore und Storage. Snapshot‑Listener sorgen für Live‑Updates in der Wunschliste und Erinnerungen.

Push‑Benachrichtigungen FCM‑Tokens werden clientseitig gepflegt, das Push‑Event wird serverseitig über Cloud Functions ausgelöst.


🧠 Code‑Highlights

Die Highlights sind nach Themen gruppiert. So bleiben die Tabs übersichtlich, obwohl es mehrere Beispiele gibt.

Wunsch wird zur Erinnerung (Upload + Farben + Firestore Update)
app/src/main/java/de/moorizzle/zuzweit/viewmodel/WishlistViewModel.kt

val downloadUrls = imageRepository.uploadCompressedImages(context, imageUris, wish.id)

val updatedData = mutableMapOf(
    "completed" to true,
    "completedDate" to Timestamp.now(),
    "completedImageUrls" to FieldValue.arrayUnion(*downloadUrls.toTypedArray())
)

val dynamicColors = imageRepository.extractColorsFromImageUrl(context, downloadUrls.first())
updatedData["dynamicContainerColor"] = dynamicColors.containerColor.toHexString()
updatedData["dynamicContentColor"] = dynamicColors.contentColor.toHexString()

db.collection("wishes").document(wish.id).update(updatedData).await()

Partner‑System mit Pair‑ID & Live‑Switching
app/src/main/java/de/moorizzle/zuzweit/viewmodel/ActivePartnerViewModel.kt

val activePartner: StateFlow<Partner?> = combine(allPartners, _activePartnerId) { partners, activeId ->
    partners.find { it.partnerId == activeId } ?: partners.firstOrNull()
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)

🧩 Datenmodell (Firestore)

Collections

  • users mit Profil, Verbindungscode und Partnerliste
  • wishes mit Pair‑ID, Status, Bildern, Farben und Journal‑Eintrag

Wunsch‑Struktur (vereinfacht)

  • title, description, creatorId, pairId
  • completed, completedDate, completedImageUrls
  • journalEntry, dynamicContainerColor, dynamicContentColor

Partner‑Beziehung

  • pairId basiert auf beiden UIDs und dient als gemeinsamer Filter
  • Pro User wird eine Partnerliste gepflegt, mehrere Verbindungen sind möglich

🎨 UX‑Entscheidungen

  • Bottom‑Navigation statt Navigation Drawer für schnellen Wechsel
  • Pull‑To‑Refresh auf Wunschliste und Erinnerungen
  • Modal Bottom Sheets für Add‑Flow und Bildquelle
  • Farben aus Erinnerungsbild, um die Detailseite persönlicher wirken zu lassen

⚠️ Herausforderungen

  • Berechtigungen & SDK‑Unterschiede: Kamera + Media Storage je nach Android Version (READ_MEDIA_IMAGES vs. READ_EXTERNAL_STORAGE).
  • State‑Management in Compose: UI‑State sauber über StateFlow und Compose‑State synchron halten.
  • Firestore‑Datenmodell: Partner‑Verknüpfung über pairId, Live‑Listener, Offline‑Cache.
  • Bildpipeline: Kompression und Upload‑Performance, sinnvolle Qualitäts‑Balance.
  • Push‑Notifications: FCM‑Tokens zuverlässig aktualisieren und im Backend nutzen.
  • Fehler‑Handling: UX‑Feedback mit Snackbars und Dialogen für Netzwerk‑ und Permission‑Fehler.

✅ Was ich dabei gelernt habe

  • Den kompletten Android‑Stack im Zusammenspiel: UI, State, Daten, Backend
  • Firebase in der Praxis: Auth‑Flows, Firestore‑Modeling, Storage‑Uploads
  • Architektur‑Denken: MVVM, Repository‑Pattern, klare Zustandsflüsse
  • UX‑Themen: Bildhandling, dynamische UI, Dialoge, Bottom Sheets
  • Umgang mit Permissions, Activity‑Result APIs und Deep‑Links

🧪 Tests & Qualität

  • Erste Unit‑Tests für den Auth‑Flow in app/src/test/java/de/moorizzle/zuzweit/viewmodel/AuthViewModelTest.kt
  • MainCoroutineRule für Coroutine‑Tests in app/src/test/java/de/moorizzle/zuzweit/MainCoroutineRule.kt
  • Potenzieller Ausbau: UI‑Tests für Compose‑Screens und Firestore‑Mocks

📈 Ausblick

  • Erinnerungssuche und Filter nach Datum oder Tags
  • Bessere Offline‑UX mit klaren Sync‑Statusanzeigen
  • In‑App Onboarding für neue Paare
  • Mehr Analytics im Profil für gemeinsame Aktivität