Back to Portfolio

Our Family Archive

A private Android app for cataloging family heirlooms and keepsakes — real-time cloud sync across all family devices, multi-photo support, Google Sign-In with an email allowlist, and a full Firebase backend

v0.3.0 — Active Development
Private — Family Access Only
v0.3.0
Current Version
Firebase
Cloud Backend
Real-Time
Cross-Device Sync
Compose
UI Toolkit

Overview

Our Family Archive is a native Android app built to solve a real family problem: cataloging inherited heirlooms, keepsakes, and sentimental items before memories and context are lost. Family members can add items with photos, detailed descriptions, origin stories, storage locations, and tags — and all of it syncs in real-time across every authorized device via Firebase.

Access is strictly controlled. New users must be invited by an admin and added to an allowlist stored in Firestore before Google Sign-In is permitted. An in-app admin panel manages the allowlist, tracks invite status (invited vs. signed up), and sends invite emails with download and install instructions. The app is distributed privately via a hosted APK rather than the Play Store, keeping it within the family.

The architecture follows MVVM with a Repository layer, Hilt for dependency injection, Room for local-first storage, and Firestore snapshot listeners for real-time two-way sync. Photos upload to Firebase Cloud Storage and are accessible by URL on any device — with a local file fallback for offline use and a backfill upload that catches any pre-existing local photos on sign-in.

Room
Local-First DB
Live
Firestore Sync
Invite
Access Control
SDK 26+
Android Target

Tech Stack

Modern Android architecture with a Firebase backend — built for reliability, offline capability, and real-time multi-device sync.

Kotlin
Primary Language
Jetpack Compose
Declarative UI Toolkit
Material 3
Design System
Room
Local SQLite Database
Hilt
Dependency Injection
Firebase Auth
Google Sign-In + Allowlist
Cloud Firestore
Real-Time Cloud Sync
Firebase Cloud Storage
Photo Hosting
DataStore
Preferences & Settings
MVVM + Repository
Architecture Pattern
Firebase Hosting
assetlinks.json Hosting
GitHub
Version Control

Key Features

A complete family cataloging system — add, search, organize, and share memories across all family devices in real time.

Item Management

  • Multi-step Add Item flow: photos → details → story → review & save
  • Camera capture and gallery picker with persistent local storage
  • Edit existing items with field-level change history
  • Delete items with confirmation — cleans up local files and cloud storage
  • "Created by" user attribution on every item
  • "Sending to Our Archive..." button state while saving

Photo Management

  • Multiple photos per item — add from camera or gallery
  • Full-screen photo viewer with pinch-to-zoom, pan, and double-tap reset
  • Swipe between photos in the viewer
  • Photo thumbnails on Home, Archive, Review, and People screens
  • Automatic upload to Firebase Cloud Storage — accessible on all devices
  • Local file fallback when offline or before upload completes

Organization & Search

  • Home screen with recently added items
  • Archive screen with full-text search (title, description, story, tags)
  • Archive filters: category, owner, location, status
  • Review Queue for marking unidentified items as identified
  • People screen with item counts and person detail pages
  • Storage Locations with room/area dropdowns and custom entries

Authentication & Access Control

  • Google Sign-In via Firebase Auth — no passwords to manage
  • Email allowlist in Firestore — unauthorized sign-ins are blocked
  • Admin panel to add and remove allowed email addresses
  • Three-state invite tracking: not invited → invited (pending) → signed up
  • Invite emails with APK download and install instructions
  • Invite send timestamp recorded before opening email composer

Real-Time Cloud Sync

  • Firestore two-way sync for items, people, locations, and activity logs
  • Real-time snapshot listeners — changes on one device appear live on all others
  • Deletes propagate in real time across devices
  • First sign-in pushes all existing local data to the cloud
  • Sync listeners start once per session — no repeated snapshot replays
  • Push never overwrites a remote URL already stored in Firestore

Settings & Theme

  • Theme toggle: System / Light / Dark — persisted via DataStore
  • Full dark mode support throughout the app
  • Archive stats: item count, photo count, people, storage locations
  • Account info screen with editable display name
  • Sign out with confirmation dialog
  • Monthly desert landscape login art — 12 unique color palettes

Technical Highlights

The non-obvious engineering decisions that make a multi-device family app reliable.

Race Condition: Firestore Snapshot vs. Photo Upload

When an item is saved, two async operations run concurrently: the item document is written to Firestore, and photos are uploaded to Cloud Storage. A Firestore snapshot listener on another device can receive and apply the item document before the photo upload finishes — at which point remoteUrl is still null. If the listener then overwrites the local Room record, the photo URL gets erased. The fix: the sync listener checks whether Firestore already has a remoteUrl for each photo before applying the incoming document, and preserves the existing URL if one is present. The push path mirrors this — it never writes a null over a URL that Firestore already holds.

Sync Listener Deduplication

Firestore addSnapshotListener calls accumulate if called more than once on the same collection — each registration fires independently, causing duplicate Room writes, duplicate UI updates, and subtle ordering bugs. The fix is a session-scoped flag per collection: the sync layer checks a boolean before registering a listener, sets it immediately after the first registration, and never registers again for the lifetime of the session. Listeners are also explicitly removed on sign-out so they don't fire on the next session's sign-in before auth is fully initialized.

Photo Backfill on Sign-In

When a user signs in for the first time after Cloud Storage was added (v0.3.0), they may have dozens of photos stored only locally — taken before sync existed. On sign-in the app scans every item in Room, identifies photos that have a local path but no remoteUrl, and uploads them to Cloud Storage in sequence. Each successful upload writes the returned download URL back to both Room and Firestore. This backfill is idempotent — if it's interrupted partway through, it picks up cleanly on the next sign-in by re-checking which photos still lack a remote URL.

Three-State Invite Tracking

The admin invite system needed to distinguish between three states: a user who hasn't been invited yet, a user who has been invited but hasn't signed up, and a user who has signed up. A simple boolean allowlist couldn't represent the middle state. The solution uses two Firestore fields per email: allowed: true (added when invited) and inviteSentAt: timestamp (written before the email composer opens, so the record persists even if the composer is dismissed). The admin UI shows the state as a badge — "Not Invited", "Invite Sent", or "Signed Up" — based on the presence of these fields.

putStream Upload for Local file:// URIs

Firebase Cloud Storage's putFile() method is documented for local URIs but proved unreliable with file:// paths from Android's scoped storage on some devices. The fix uses putStream() instead: open the file via ContentResolver.openInputStream() (which handles both file:// and content:// URIs uniformly), pass the stream directly to Cloud Storage, and close it in a finally block. This approach works consistently across Android versions and avoids URI scheme edge cases entirely.

Monthly Desert Landscape Color Palettes

The login screen features a desert landscape illustration that shifts color throughout the year — 12 distinct palettes, one per calendar month. Each palette defines sky, sun, mesa, ground, and shadow colors that together evoke a specific season: deep winter blues, spring greens, summer amber, autumn rust. The current palette is selected by month index at runtime. This was a deliberate design decision to give the app a sense of time passing and make the login screen feel alive rather than static — a small detail that matters for an app meant to be used across years and generations.

What This Project Demonstrates

Modern Android Architecture
MVVM + Repository, Hilt DI, Room, Jetpack Compose, DataStore — the full modern Android stack applied to a real product
Firebase Integration
Auth, Firestore, and Cloud Storage working together — with real-world race conditions identified and fixed
Real-Time Multi-Device Sync
Snapshot listeners, deduplication, backfill, and delete propagation — sync that works reliably across sessions
Access-Controlled Private App
Invite-only distribution outside the Play Store, Firestore-backed allowlist, and a full admin panel for managing family access
Local-First with Cloud Sync
Room as the source of truth, cloud as the sync layer — offline capability with automatic backfill when connectivity returns
Solving a Real Problem
Built to preserve family history — a practical tool used by real family members, not a demo app

Need a Custom Mobile App?

From architecture to Firebase backend — we build Android apps that are fast, reliable, and built to last.

Get In Touch View More Projects