I've always found reading hard.
Long documents slide off my attention, and I lose my place constantly.
What I really wanted was something that would read to me and show me the words as it went — so my eyes and ears stayed in sync.
Nothing did exactly that, so I built it.
It's called ReadAloud, it's on Google Play, and this post is the "why" and the interesting bits of the "how." The moment it became real The first person I showed a rough build to was my Sister, Praise.
She'd come to town to officiate a Women's Premier League match at Auntie Aku Astro Turf Park, and I pulled out my phone between everything else.
She watched a paragraph read itself aloud with each word lighting up and got genuinely excited — that was the push I needed.
She became tester #1.
My colleague Reggie became tester #2.
Between them they found the rough edges I'd stopped seeing, and the app settled into something stable.
What it is A text-to-speech reader for PDFs, EPUB, DOCX, plain text and web articles.
It reads aloud in natural voices, highlights each word as it speaks, and auto-scrolls to follow along.
There's offline listening, English/French/Spanish, speed-reading (RSVP), a vocabulary builder, and reading stats.
The stack: Kotlin, Jetpack Compose + Material 3, MVVM + Clean Architecture, Hilt, Room, DataStore, WorkManager, .
Now the parts that were actually interesting to build.
1.
Word-by-word highlighting This is the whole product, so it had to be right.
On-device voices are easy — Android's gives you (API 26+), which fires per spoken range: The catch: the natural cloud voices people actually want don't emit .
So for cloud synthesis I wrap each word in an SSML and ask Google Cloud TTS to return timepoints: Each timepoint maps a mark → a timestamp; I pair that back to the word's character range and drive the highlight from .
Some families (Studio, Chirp) reject SSML outright, so those fall back to a syllable-weighted time estimate.
Two code paths, one visible behaviour — I call it "dual highlighting." The lesson learned the hard way: highlighting regressed silently more than once as I refactored the reader.
It never crashed — it just quietly stopped tracking.
Now any change to the reading pipeline runs a highlight check before it ships.
2.
Keeping API keys off the device Cloud TTS costs money per character, so the API key is a liability if it ships in the APK (anyone can unzip a and grep for ).
My rule: no third-party keys in the binary.
Instead the app fetches config from a small server.
Every request is HMAC-signed, and every response is encrypted: The server verifies the signature, then returns the keys as an AES-256-GCM blob whose key is — so every device gets a distinct derived key.
The app decrypts, caches, and uses them.
Rotating a key, blocking a device, or flipping maintenance mode is a server change, not an app release.
A debugging war story from this exact system: a debug build once compiled without the shared secret and silently baked in a placeholder.
Every signed request came back , cloud voices vanished, and it looked like a server outage.
It wasn't — the fix was a rebuild.
The takeaway I now live by: fail loudly when a required secret is missing, especially in debug.
3.
PDFs that don't scramble PDF text extraction is famously messy.
Two-column layouts interleave line-by-line, and running heads/footers leak into the middle of sentences.
I run a column-aware reordering pass (cluster text runs by x-position, read each column top-to-bottom) and strip repeated page furniture.
The subtle part is resume + highlights.
If I anchored them to page/character offsets, they'd break the moment extraction changed.
Instead they're content-anchored — tied to the surrounding text — so your place and your highlights survive a re-extraction or a settings change that reflows the page.
4.
Offline listening A commute shouldn't need signal.
A job synthesizes the whole book once in your chosen voice, storing the MP3 chunks plus a sidecar of word timings: Playback then runs fully offline, with the same word highlighting as the live path, because the timings travel with the audio.
What I'd tell my past self The core feature is the product.
Highlighting is 5% of the code and 95% of the value — treat it like a first-class citizen, with tests.
Accessibility helps everyone.
Bionic reading, adjustable spacing, colour overlays, a sleep timer — I added them for myself and they made the app better for everyone.
Ship the boring safety.
Server-held keys, "fail loud" on missing secrets, content-anchored state.
Each one saved me later.
Real testers beat assumptions.
Praise and Reggie caught things I was blind to.
Try it ReadAloud is on Google Play: https://play.google.com/store/apps/details?id=com.bless.readaloud.app It's free; premium unlocks the most natural voices and offline audio.
If reading is ever a chore for you the way it is for me, I hope it helps — and I'd love your feedback.
Built with Kotlin + Compose.
Happy to go deeper on any of the above in the comments.