ZuZweit โ€“ Shared Wishes & Memories

By Moritz Leopold

Created on March 9, 2026


GitHub Repository โ†’

ZuZweit is an Android app for couples that collects shared wishes and preserves fulfilled experiences as memories. The goal of this project was to learn Android development from scratch while building a realistic product with auth, data modeling, an image pipeline, and push flows.


๐ŸŽฏ Goals

Why this project? Many couple apps are bloated. I wanted a focused, fast app that covers the core: shared wishes, memories, photos, and personal notes. The project served as a learning path for Jetpack Compose, Firebase, and clean app architecture.

Learning goals

  • Jetpack Compose UI + state management
  • Firebase Auth, Firestore, Storage, Messaging
  • Image handling (camera, gallery, compression)
  • Cloud Functions for server-side logic
  • Offline strategies and UX feedback

โœจ Overview

Features

  • Registration, login, password reset via deep link
  • Partner linking via connection code
  • Shared wishlist with CRUD
  • Wish completion via camera or gallery
  • Memory detail page with journal entry
  • Dynamic UI colors extracted from the memory image
  • Push notifications for new wishes

User journey (short)

  • User registers and receives a connection code
  • Partner connects with the code and sets a nickname
  • Both create wishes, mark them as completed, and upload images
  • Completed wishes appear as memories with gallery and journal

๐Ÿงฑ Architecture & Data Flow

Architecture

  • MVVM with StateFlow/SharedFlow in viewmodel/*
  • Firestore snapshot listeners for live updates
  • Separation of UI (Compose) and data access (Repository, ViewModels)
  • Image pipeline: camera/gallery โ†’ compression โ†’ storage โ†’ colors from image โ†’ dynamic UI
  • Partner context as global filter (pair ID) for wishes/memories

Modules

  • app/ Android app
  • functions/ Firebase Cloud Functions

๐Ÿ” State of the Art (Project Context)

Jetpack Compose Declarative UI framework for Android. Used for all screens, bottom navigation, bottom sheets, and dialogs.

Firebase Realtime backend with Auth, Firestore, and Storage. Snapshot listeners provide live updates in the wishlist and memories.

Push notifications FCM tokens are maintained client-side, the push event is triggered server-side via Cloud Functions.


๐Ÿง  Code Highlights

The highlights are grouped by topic. This keeps the tabs readable even with multiple examples.

Wish becomes a memory (upload + colors + 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 with 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)

๐Ÿงฉ Data Model (Firestore)

Collections

  • users with profile, connection code, and partner list
  • wishes with pair ID, status, images, colors, and journal entry

Wish structure (simplified)

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

Partner relationship

  • pairId is based on both UIDs and acts as a shared filter
  • Each user maintains a partner list, multiple connections are possible

๐ŸŽจ UX Decisions

  • Bottom navigation instead of a drawer for quick switching
  • Pull-to-refresh on wishlist and memories
  • Modal bottom sheets for add flow and image source
  • Colors extracted from the memory image to make the detail screen more personal

โš ๏ธ Challenges

  • Permissions & SDK differences: camera + media storage vary by Android version (READ_MEDIA_IMAGES vs. READ_EXTERNAL_STORAGE).
  • State management in Compose: keeping UI state clean via StateFlow and Compose state.
  • Firestore data model: partner linking via pairId, live listeners, offline cache.
  • Image pipeline: compression and upload performance, quality trade-offs.
  • Push notifications: reliably updating FCM tokens and using them in the backend.
  • Error handling: UX feedback with snackbars and dialogs for network and permission errors.

โœ… What I learned

  • The full Android stack in practice: UI, state, data, backend
  • Firebase in practice: auth flows, Firestore modeling, storage uploads
  • Architectural thinking: MVVM, repository pattern, clear state flows
  • UX topics: image handling, dynamic UI, dialogs, bottom sheets
  • Working with permissions, Activity Result APIs, and deep links

๐Ÿงช Tests & Quality

  • Initial unit tests for the auth flow in app/src/test/java/de/moorizzle/zuzweit/viewmodel/AuthViewModelTest.kt
  • MainCoroutineRule for coroutine tests in app/src/test/java/de/moorizzle/zuzweit/MainCoroutineRule.kt
  • Potential next steps: Compose UI tests and Firestore mocks

๐Ÿ“ˆ Outlook

  • Memory search and filtering by date or tags
  • Better offline UX with clear sync status
  • In-app onboarding for new couples
  • More analytics in the profile for shared activity