Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
FREEDOM251

Freedom251 › Blog › Android architecture

Building Hishab Ledger: Designing an Offline-First Personal Ledger with Kotlin and Jetpack Compose

By Freedom251 Team · · 6 min read

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

There is no public Hishab Ledger release, repository, or specification to point to, so this is a recommended build rather than a description of an existing app. The short answer: make a local database the read model your screens depend on, let entry and browsing work with no network connection, and have Jetpack Compose render state observed from that database. Treat server synchronization as a separate feature with its own reconciliation policy, added only once the local ledger is correct.

What offline-first has to mean for a ledger

Android Developers defines the approach in its app architecture guidance: “An offline-first app is an app that is able to perform all, or a critical subset of its core functionality without access to the internet.” For a personal ledger, the critical subset is clear. The app should let the user:

As an Amazon Associate I earn from qualifying purchases.

  • create an income or expense entry,
  • browse transaction history,
  • see account totals and balances,
  • correct or delete an existing entry,
  • export or back up data, if that is in scope for the version you ship.

Everything else, including sync, can wait for connectivity. The same guidance says data should appear promptly from the local store rather than after an initial network call, which is the behavior a ledger user notices first: opening the app on a flight should show the same balance as opening it at home.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

It helps to separate quick capture from full reporting. The hledger project’s “Mobile apps” page describes mobile ledger tools focused on entering transactions on a phone and exporting them to a computer for reporting. That split is a useful model for scope, though it says nothing about how a Hishab Ledger would be built.

Step 1: Make the local database the source of truth for reads

Room is the default persistence choice to design around for structured records. It is a Jetpack library that provides an abstraction over SQLite, and Android’s Room codelab uses expense and income records as its example of data an app must persist. Ledger entries are exactly that kind of relational, queryable data.

Keep ledger rules, such as validation and balance calculation, in the data or domain layer. A composable should display totals, not compute them, so that the same rule applies whether the entry came from the keyboard, an import, or a future sync.

A minimal entity and DAO might look like this. Storing money as integer minor units (for example, paisa or cents) rather than floating-point values is a design recommendation to avoid rounding errors in totals; it is not a feature the Android documentation prescribes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@Entity(tableName = "transactions")
data class TransactionEntity(
    @PrimaryKey val id: String,          // client-generated UUID
    val accountId: String,
    val amountMinor: Long,               // integer minor units
    val type: String,                    // "INCOME" or "EXPENSE"
    val occurredAt: Long,                // epoch millis
    val note: String,
    val updatedAt: Long,
    val syncState: String                // "LOCAL_ONLY", "PENDING", "SYNCED"
)

@Dao
interface TransactionDao {
    @Query("SELECT * FROM transactions ORDER BY occurredAt DESC")
    fun observeAll(): Flow<List<TransactionEntity>>

    @Insert(onConflict = OnConflictStrategy.ABORT)
    suspend fun insert(entry: TransactionEntity)
}

Generating the ID on the device is what makes offline creation possible: the entry exists before any server has seen it, and the same ID later lets a server recognize a retried upload.

Step 2: Route every change through the repository

Android recommends that a repository with a network component also have a local source, that reads come from the local source, and that writes update the local source first so observers see changes consistently regardless of connection state. Applied to a ledger, the flow is:

  1. The user taps Save on the add-entry screen.
  2. The composable sends an event to the ViewModel.
  3. The ViewModel calls the repository, which validates the draft and writes a row to Room.
  4. Room emits the updated list through its Flow.
  5. The repository maps rows to domain models, and the ViewModel exposes them as a StateFlow.
  6. Compose collects the StateFlow and redraws the list.

The ViewModel bridges Compose and the data layer and converts the Flow into state. Android recommends collecting it with lifecycle-aware Compose APIs.

class LedgerViewModel(private val repository: LedgerRepository) : ViewModel() {
    val entries: StateFlow<List<Entry>> = repository.observeEntries()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = emptyList()
        )

    fun addEntry(draft: EntryDraft) {
        viewModelScope.launch { repository.addEntry(draft) }
    }
}

@Composable
fun LedgerScreen(viewModel: LedgerViewModel) {
    val entries by viewModel.entries.collectAsStateWithLifecycle()
    if (entries.isEmpty()) {
        EmptyLedgerMessage()
    } else {
        LazyColumn {
            items(entries, key = { it.id }) { entry -> EntryRow(entry) }
        }
    }
}

Because the list is driven by Room rather than by the ViewModel’s own copy, a saved entry appears even if the write happened while offline. The UI never has to guess what the database contains.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Step 3: Design the states the screen must show

An offline-first screen has more states than a network-first one. These are design recommendations derived from the model above, not documented features of any existing app:

  • Initial load: the local database is being read for the first time; show a placeholder, not a spinner that waits on the network.
  • Empty ledger: no entries yet; offer the first-entry action.
  • Saved locally, pending sync: the row exists and is marked as not yet uploaded, so the user knows it is safe on the device.
  • Sync failed, local data intact: show the last successful sync time and keep all local figures visible.
  • Validation error: reject the draft in the repository and show the reason beside the field, without discarding what the user typed.

Choosing where each kind of data lives

Android identifies Room, DataStore, and plain files as local persistence options. Each suits a different shape of data:

Option Suited to Poor fit
Room (over SQLite) Structured, queryable records such as transactions, accounts, and categories; observable queries that feed the UI Large binary attachments; a single setting value
DataStore Small typed preferences such as default account, currency display, or lock settings Relational data that needs joins, totals, or history queries
Files Exports, receipts stored as images, and generated backup archives Anything the UI must query or update incrementally
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Adding sync later without corrupting balances

Offline-first does not require an account or multi-device sync. If you add it, treat it as a separate design. Android describes both pull-based and push-based synchronization and persistent queues. A practical pattern is an outbox table in Room that records each unsent change, drained by WorkManager when the network is available. Each change must survive app restarts and be safe to resend.

A financial ledger needs an explicit policy for the following decisions. Android’s guidance describes the mechanisms but does not prescribe these rules for a ledger:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Decision Options to weigh Consequence to state in the product
When to pull server changes On app open; periodic background refresh; manual refresh Freshness trades against battery and mobile data use
Duplicate submissions Client-generated UUID per entry, with the server ignoring repeated IDs Retries after a dropped connection do not create double entries
Concurrent edits to one entry Field-level merge; keep the server version and notify the user; ask the user to choose Last write wins can silently discard a corrected amount, so do not adopt it by default
Deletes Tombstone record with updatedAt, purged only after the server confirms Without a tombstone, a deleted entry can reappear from another device
Ordering and timestamps Server-assigned sequence or timestamp for ordering; device clock for display only Device clocks can drift or be set wrong, which can reorder history

Whatever policy you choose, state it plainly in the product and in the code’s documentation. Users editing money need to know which version of an entry wins.

Privacy, backup, and recovery are open decisions

Personal financial data raises questions that Android’s architecture guidance does not answer for you. Settle these before shipping any backup or sync feature:

  • Where records are stored on the device, and whether that storage is accessible to other apps or the user.
  • Whether backups are encrypted, and who holds the key.
  • What happens to data after the user loses or replaces the device, and whether restore is possible without a server.
  • Whether export formats are documented so users can move their history to another tool.

Do not promise protections in the interface or marketing until the implementation actually provides them.

Sources and currency

  • Android Developers, “Build an offline-first app” in the App architecture guide: the offline-first definition, local sources, reads and writes, synchronization, queues, and Compose integration.
  • Android Developers, “Persist data with Room”: Room as an abstraction over SQLite, and the expense and income example.
  • Android Developers, “Save data in a local database using Room”: keeping data available when the device cannot reach the network.
  • Android Developers, “State and Jetpack Compose”: Compose state concepts used in the examples above.
  • hledger.org, “Mobile apps”: examples of mobile ledger workflows that separate entry from reporting.

These sources were reviewed in October 2026. Android APIs, including Room, WorkManager, and the Compose lifecycle helpers, change between releases, so confirm the current documentation and library versions before you build.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Android architectureJetpack ComposeKotlinLedger appoffline-firstpersonal financeRoom

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.