← Back to all posts

03 Xpense

Xpense's Project Structure

Xpense Dev Retrospective


The Actual Folder Layout

Package name is com.hassanbukhari.xpense. Strip it down to what actually matters and it looks like this:

AndroidManifest.xml // declares every screen and the app's identity

java/com/hassanbukhari/xpense/
  MainActivity.kt // home screen, calendar, budget overview
  OnboardingActivity.kt // first-launch flow
  AddExpenseActivity.kt
  AddSettlementActivity.kt
  HistoryActivity.kt
  SettingsActivity.kt
  SettlementsActivity.kt
  DataActivity.kt // backup, restore, export
  AboutActivity.kt
  BaseActivity.kt // shared parent for every screen above
  Formatting.kt // currency and date formatting helpers
  data/
    AppDatabase.kt
    Expense.kt / Category.kt
    ExpenseDao.kt / CategoryDao.kt
    ExpenseRepository.kt / CategoryRepository.kt
    SettingsManager.kt
    BackupData.kt
    LegacyMigration.kt // one-time migration from the old JSON era

res/   layout/, values/, values-night/, drawable/, mipmap-anydpi-v26/

Nine activities, one shared base class, one small data package doing all the real work, and one formatting helper. That's the whole app.


The Activities

Every screen in Xpense is an Activity. Each one extends BaseActivity instead of Android's own AppCompatActivity directly, and that one decision is why the app never had to write repeated boilerplate for theming or insets in every single screen.

ActivityWhat it's for
MainActivityHome screen. Calendar view, monthly budget, day-by-day spend coloring
OnboardingActivityShown once on first launch, then never again
AddExpenseActivityAdd or edit a single expense
AddSettlementActivityAdd or edit a settlement between people
HistoryActivityFull expense history, filterable by day/week/month
SettlementsActivityList of settlements and balances
SettingsActivityApp settings, currency, categories
DataActivityBackup export and restore
AboutActivityVersion info, credits, links

BaseActivity isn't a screen a user sees. Every other Activity extends it, and it handles two things once so every screen doesn't have to: enabling edge-to-edge display with proper inset padding, and forcing the app to follow the system's light/dark setting rather than any per-app toggle.


The data Package: Where the Real Work Happens

Nine files handle almost every screen's logic. This is intentional. Activities are supposed to be dumb, they show things and react to taps, the actual decisions about what data means and how it's stored live in data/.

FileRole
AppDatabaseThe Room database itself, wires the DAOs together
Expense / CategoryThe actual data models, what a row in the database looks like
ExpenseDao / CategoryDaoThe queries Room is allowed to run against those tables
ExpenseRepository / CategoryRepositoryThe layer Activities actually talk to, sits between the UI and the DAOs
SettingsManagerSimple key-value settings, currency, onboarding flags, still on SharedPreferences
BackupDataThe shape of an exported backup file
LegacyMigrationRuns once, reads the old JSON and SharedPreferences data, writes it into Room

Why a repository layer: an Activity never talks to a DAO directly. It asks the Repository. If the Room queries ever need to change, or caching gets added later, only the Repository has to change, not every Activity that happens to need expense data. It's one extra file per feature, in exchange for not touching nine Activities every time storage logic changes.


Why Even Settings Are Still SharedPreferences

SettingsManager deliberately wasn't moved into Room during the rewrite. A handful of flags and a currency string don't need a schema, a query language, or a DAO. Room earns its place for structured, growing data like expenses. It would be overkill for four settings keys.


The res Folder

Nothing unusual here, but worth naming: layout/ holds the XML for every screen and list item, values/ and values-night/ hold light and dark color definitions respectively (no manual toggle logic, Android just picks the matching folder), drawable/ holds the calendar dot icons among other things, and mipmap-anydpi-v26/ holds the launcher icon.


Closing

Next up, the file Android actually reads before any of this code runs at all: AndroidManifest.xml.

← Previous 02 - How Android Apps Actually Work Next → 04 - AndroidManifest.xml Explained