Repository navigation
Architecture
The app follows Android's recommended architecture: a single activity, Jetpack Compose screens, ViewModels with unidirectional data flow, and an offline-first data layer. It is split into Gradle modules so each part builds and tests on its own.
flowchart TD
app[app] --> features
subgraph features[feature]
home & catalogue & book & shop & wishlist & cart & checkout & auth & settings
end
features --> ui[core:ui]
features --> data[core:data]
ui --> designsystem[core:designsystem]
data --> network[core:network]
data --> database[core:database]
data --> datastore[core:datastore]
data --> model[core:model]
network --> model
| Module | Holds |
|---|---|
app |
MainActivity, the five-tab Navigation 3 shell, Hilt setup, flavours and the updater |
feature:* |
One screen or flow each, with its ViewModel and strings |
core:ui |
Shared composables (book cards, prices, loading and error states), the routes and the website's links |
core:scanner |
The ISBN barcode scanner: CameraX for the picture, ZXing to read it, all on the phone |
core:designsystem |
The theme: the website's colours, Plus Jakarta Sans, light and dark |
core:data |
Repositories, the session manager and the network monitor |
core:network |
The Retrofit API, DTOs, mappers and OkHttp interceptors |
core:database |
Room: the offline cache, recent searches and recently viewed books |
core:datastore |
DataStore settings and the Keystore-encrypted session store |
core:model |
Plain Kotlin models used by every layer |
Features never depend on each other. They navigate through the routes in
core:ui, and the app module wires them together.
- A screen collects its ViewModel's
StateFlowof UI state and calls the ViewModel's functions for events. - The ViewModel asks a repository, which returns an
Outcome(success or a typed error) rather than throwing. - Repositories serve the saved copy from Room at once, then refresh it from the API. Account data is saved under the account's id and removed on sign-out.
Navigation 3 with @Serializable routes. Each of the five tabs has its own back
stack; Home's stack sits under the current tab's, so Back from another tab's
first screen returns to Home. Sign-in and checkout hide the tab bar.
- The access token lives in memory only.
- The refresh token is encrypted with AES-256-GCM using a key in the Android Keystore (StrongBox where the phone has one).
- Each renewal is signed by a P-256 key created on the phone, which never leaves it. The server ties the session to that key, so a copied refresh token is useless elsewhere.
- An OkHttp authenticator renews the token once when the server answers 401; concurrent requests share one renewal.
- Cloud backups and device transfers leave the session and the cache behind.
The app uses the website's API under /api/v1, defined in the website
repository's
server/shared/api.d.ts.
App-specific endpoints live under /api/v1/app: configuration (sign-in
options, delivery charges, the latest release) and sign-in.
Every request carries X-Client: android/<versionCode>. If the server ever
stops supporting a build, it answers 426 and the app shows an update screen.
The website's
docs/API-VERSIONING.md
describes the rules both sides follow.
Coil loads covers through the same OkHttp client as the API, sharing its connection pool and disk cache, and asks Cloudinary for a size that fits the screen.
BookStoreBD · Android app · MIT License · Questions: support.utsha@gmail.com
Using the app
Working on it
Links