LogiTrack
Backend and architecture of a warehouse Android app
The server side reached a working state and was verified live; the Android client stayed at the scaffold and architecture stage. The project was archived in September 2026.
Verifiable decisions
The server verifies device authenticity with Keystore key attestation instead of Play Integrity. The client architecture is designed for MDM distribution without Google services.
code
When the server rejects a request over the rate limit, the device's packet counter does not advance. Otherwise the server would wait for the missing packet forever.
code
A stocktake discrepancy never changes stock automatically. It is parked for a person to review.
code
In detail
The job
An app for warehouse staff: receiving, put-away, picking and stocktaking. The device fleet is mixed: rugged terminals with hardware scanners and ordinary phones.
What was built
The server side, in Kotlin and Ktor: device enrolment, mutual TLS, sync and stocktaking. It was deployed and verified live. The Android client stayed at the scaffold stage: build setup, module graph and architecture docs, with no feature code.
No Google services
The client architecture targets MDM distribution rather than a store, so it has no dependency on Google services. The server verifies device authenticity with Keystore key attestation instead of Play Integrity, and traffic uses mutual TLS.
Shared contract
A contract module compiles into both the client and the server. Kotlin and serialization versions change only in both repositories at once: a mismatch does not break the build, it breaks deserialization at run time.
Sync
The device numbers its packets. If the server rejects a request over the rate limit, the response carries no results and the counter must not advance, otherwise the server waits for the missing packet forever. A stocktake with a discrepancy does not change the stock; the discrepancy is parked for a person to review.
Status
The project was archived in September 2026. Everything needed to bring it back is kept.