Skip to content

davideagostini/dunio

Repository files navigation

Track net worth in one place
    Dunio Logo

    Dunio

    License Kotlin

Dunio is a mobile-first Android app for shared household finance tracking.

It is designed for small shared workspaces such as couples or families who want a simple way to track:

  • net worth
  • assets and liabilities
  • income and expenses
  • recurring transactions
  • monthly household progress

The app is intentionally not an accounting suite. The focus is fast daily use, clear numbers, and a simple shared-household model backed by Firebase.

Screenshots

Track net worth in one placeTrack every transactionManage assets and liabilitiesMade for shared financesAdd transactions fasterWidgets for quick accessFaster than opening the appBuilt for day and night

What this repository contains

  • Android app source code
  • Gradle project files
  • GitHub CI workflow
  • contribution and release documentation
  • dedicated Wear OS quick-entry architecture notes in docs/wear-os-quick-entry.md

Core product model

All financial data belongs to a household.

Supported roles:

  • owner
  • member

Both users share the same data inside a household:

  • dashboard
  • assets
  • transactions
  • categories
  • recurring transactions
  • month close state

Each household uses one shared currency configured from Settings. The app does not support per-asset currencies or mixed-currency totals.

Current feature set

Authentication and household

  • Google sign-in with Firebase Authentication
  • persistent session restore
  • create household
  • join household with invite + household ID
  • owner/member household model
  • household onboarding keeps the user behind a loading state until session access is fully resolved

Dashboard

  • net worth
  • total assets
  • total liabilities
  • monthly cash flow
  • savings rate
  • financial runway
  • 3 / 6 / 12 month net worth chart
  • month selector
  • full-screen get-started flow for households missing entries or assets

The dashboard now renders a lightweight skeleton state while its monthly summaries load, so the screen structure appears immediately instead of blocking behind a generic full-screen spinner.

Entries

  • monthly transaction list
  • Firestore reads scoped to the selected month instead of the full household history
  • grouped by day
  • search
  • entries / reports toggle in the toolbar
  • All / Expenses / Income filter
  • unusual spending insights
  • category spending report with a horizontal bar chart plus totals, averages, percentages, and transaction counts
  • entry edit and delete flow
  • quick-entry flow

The entries screen now keeps the list month-scoped, loads category reports only when the user opens Reports, and limits unusual-spending insights to a small rolling window around the selected month instead of the full transaction archive. It also renders a local shimmer skeleton during cold loads and uses a lightweight route shell plus progressive body mount so the first tab switch feels more immediate on slower devices. To reduce ANR risk on older phones, Firestore transaction listeners now dispatch off the main thread and the visible ledger list is flattened into truly lazy day headers plus entry rows instead of rendering whole day groups in one composed block. Entry category pickers also keep a tiny local Room usage index so the most-used categories for the current household and entry type can be shown first without querying Firestore. The index is keyed by householdId + type + categoryStableId, grows only by category used, and is treated as best-effort so entry saves never depend on local ranking updates.

Assets

  • monthly asset snapshots
  • Firestore reads scoped to the selected month plus the previous month instead of the full asset history
  • assets and liabilities in one feature
  • search
  • edit and delete flow
  • copy previous month snapshot
  • monthly net worth summary
  • household currency applied automatically to new asset snapshots

The assets screen now reads only the selected month and previous month snapshots so list rendering, month-over-month change, and Copy previous month stay responsive without downloading the full asset archive. Like Entries, it now uses a local shimmer skeleton during cold loads plus a lightweight route shell/progressive body mount instead of blocking the whole app shell.

Settings

  • household currency
  • app language selection
  • app theme selection
  • household data export to CSV or JSON
  • category management
  • recurring transactions
  • month close
  • household members
  • invite management
  • household details

The household currency is changed from a dedicated searchable settings screen, and the selected currency is surfaced at the top of the list.

The app also includes a dedicated language selection screen in Settings. It uses the same list style as the currency screen, keeps a stable alphabetical order, and highlights the currently selected language.

The app theme is also changed from a dedicated Settings screen using the same list treatment as language selection. Users can choose Light, Dark, or System, and the selected theme is persisted across launches.

The app also includes a dedicated export screen in Settings. It can export the current household as a full JSON backup, plus entries and assets as CSV files through the Android system document picker.

Recurring transactions can also be auto-applied on app startup. Once per day, after the session is ready, the app checks whether the current household has due recurring items for the current month and materializes them into the entries list without requiring a manual Apply due tap.

The month-close screen now reads only the selected month's entries and asset snapshots instead of loading the full transaction and asset history.

Bottom navigation now restores state across the primary tabs (Dashboard, Entries, Assets, Settings) so rapid tab switching reuses warm screens more reliably.

Realtime Firestore DAO listeners now use a shared background executor for snapshot callbacks, which keeps document deserialization and mapping away from the UI thread across dashboard, entries, assets, members, invites, recurring, categories, and month close.

Local-only convenience data is stored in Room. The first local table tracks category usage counts for the entry category picker; source-of-truth finance data remains in Firestore under the current household.

Current app-language support includes:

  • English
  • Italian
  • French
  • German
  • Dutch
  • Spanish
  • Portuguese (Brazil)
  • Russian
  • Arabic
  • Chinese (Simplified)
  • Japanese

Quick access

  • Quick Settings tile
  • home-screen quick-entry widget
  • home-screen spending summary widget
  • home-screen top categories widget

Wear OS

  • dedicated Wear OS app module
  • non-standalone watch quick entry
  • watch-to-phone RPC over Wear Data Layer
  • local queue on the watch when the phone is temporarily unavailable
  • package layout split into data, model, navigation, presentation, protocol, sync, theme, and ui

Wear OS implementation details and architecture notes live in docs/wear-os-quick-entry.md.

The spending widget now renders from a tiny local cache first and refreshes remotely afterward. This avoids the launcher getting stuck on the static preview while Firebase work is still in flight.

The top-categories widget follows the same cache-first pattern. It shows the top three expense categories for the selected month and renders each share as a horizontal meter that uses the same percentage semantics as the in-app category spending report.

Tech stack

  • Kotlin
  • Jetpack Compose
  • Material 3
  • Hilt
  • Coroutines + Flow
  • Firebase Authentication
  • Cloud Firestore
  • Navigation Compose
  • Glance widgets

Project structure

Main source root:

app/src/main/java/com/davideagostini/summ

Top-level packages:

summ/
├── data/       # Firebase access, repositories, entities, session state, DI
├── domain/     # Shared models and lightweight use cases
├── tile/       # Quick Settings tile integration
├── ui/         # Compose screens, ViewModels, state, feature components
└── widget/     # Home-screen widgets and widget data sources

The current app pattern is:

Screen composable
  -> collects immutable state with collectAsStateWithLifecycle()
ViewModel
  -> exposes uiState + renderState
Repository / Use Case
  -> reads and writes Firebase-backed data

App text is resource-based and ready for Android per-app language switching through the system locale picker and the in-app language screen.

Composable screens should stay focused on rendering and screen orchestration. Derived business data such as totals, grouped lists, chart models, and filtered lists should be prepared in ViewModel or shared model/use-case code.

Requirements

  • Android Studio
  • JDK 17
  • Android SDK
  • Firebase project

Backend setup required

To actually run the app, you need your own Firebase project.

Minimum backend setup:

  • Firebase Authentication with Google sign-in enabled
  • Cloud Firestore created
  • app/google-services.json added locally
  • firebase/firestore.rules deployed
  • firebase/firestore.indexes.json deployed

The provided Firestore rules support the initial household bootstrap flow used by the Android app:

  • create household document
  • create the owner membership
  • seed default categories

This repository is app-first, but the Android client depends on that Firebase setup to work correctly.

Local setup

1. Create a Firebase project

2. Register the Android app

Use this package name:

com.davideagostini.summ

3. Enable Google sign-in

  • Open Authentication
  • Enable Google
  • Set the support email if requested

4. Create Firestore

  • Open Firestore Database
  • Create a database
  • Choose the mode you prefer for local development

5. Add Android SHA fingerprints

From the repository root:

cd mobile-app
./gradlew signingReport

Add the debug SHA-1 and SHA-256 fingerprints to the Android app in Firebase.

6. Add google-services.json

Download the file from Firebase and place it here:

app/google-services.json

This file is intentionally ignored by Git.

7. Deploy Firestore rules and indexes

From the mobile-app/firebase folder:

cd mobile-app/firebase
firebase use --add
firebase deploy --only firestore:rules
firebase deploy --only firestore:indexes

If you prefer, you can deploy with --project <your-project-id> instead of setting an active project first.

8. Build and install

cd mobile-app
./gradlew assembleDebug
./gradlew installDebug

Firestore data model

Expected structure:

users/{uid}
households/{householdId}
households/{householdId}/members/{userId}
households/{householdId}/categories/{categoryId}
households/{householdId}/transactions/{transactionId}
households/{householdId}/assets/{assetId}
households/{householdId}/assets/{assetId}/history/{entryId}
households/{householdId}/recurringTransactions/{recurringTransactionId}
households/{householdId}/monthCloses/{period}
households/{householdId}/invites/{inviteId}

Release signing

Create a local keystore.properties file by copying keystore.properties.example and filling in your local values.

Example:

storeFile=/absolute/path/to/your-upload-key.jks
storePassword=change-me
keyAlias=upload
keyPassword=change-me

Build a release bundle with:

cd mobile-app
./gradlew bundleRelease

The output bundle will be generated under:

app/build/outputs/bundle/release/app-release.aab

GitHub release automation

This repository is set up so that pushing a tag like v0.0.5 can:

  • build a signed release APK
  • build a signed release AAB
  • generate a release mapping file for deobfuscation
  • keep optional native debug metadata and symbol tables as workflow artifacts when available
  • publish both files to a GitHub Release

Required GitHub Actions secrets:

  • ANDROID_KEYSTORE_BASE64
  • ANDROID_KEYSTORE_PASSWORD
  • ANDROID_KEY_ALIAS
  • ANDROID_KEY_PASSWORD
  • GOOGLE_SERVICES_JSON_BASE64

Release notes:

  • CI validates that the decoded google-services.json matches com.davideagostini.summ and generates default_web_client_id
  • release builds explicitly keep @string/default_web_client_id so shrinkResources does not remove Google Sign-In configuration

How it works:

  1. Convert your keystore to base64 locally
  2. Store the value in ANDROID_KEYSTORE_BASE64
  3. Add the other signing values as repository secrets
  4. Push a tag such as:
git tag v0.0.5
git push origin v0.0.5

The workflow will decode the keystore, sign the release build, generate both APK and AAB, and attach them to the Dunio GitHub Release.

Release builds use minification and resource shrinking. The generated mapping.txt is uploaded as a GitHub Actions artifact so you can keep the deobfuscation file for Play Console or post-release analysis without exposing it in the public GitHub Release. Native debug metadata and symbol tables are also uploaded as optional workflow artifacts when AGP produces them.

Security and publishing notes

Do not commit:

  • app/google-services.json
  • local.properties
  • keystore.properties
  • .jks / .keystore files
  • service account JSON files

If a local Firebase file was added to Git by mistake:

git rm --cached app/google-services.json

Documentation

Roadmap ideas

High Priority · Lower Effort

  • income vs expense analysis with category breakdown
  • user feedback mechanism

High Priority · Medium Effort

  • budgets with weekly, monthly, and yearly spending limits by category
  • reports and summaries for monthly review
  • notifications and reminders
  • on-device AI category suggestion during quick entry

Future Additions · Higher Effort

  • import transactions from CSV with validation
  • bank CSV import support
  • better backup and migration flows
  • on-device monthly financial summaries
  • Analyze habits with on-device AI insights
  • receipt or invoice scan to draft an entry with description, amount, date, and category

Support the project

Dunio is free to use and open source. If you find it useful and want to support ongoing development, you can do it here:

Star History

If you find Dunio useful, consider giving the repository a star.

Star History Chart

License

This repository uses the MIT license. See LICENSE.