Inspiration
A three-year-old cannot read. That is the whole brief.
Every Bible app for children I found assumed one of two things: that the child could read, or that an adult would sit there and read it to them. Both are fine until 6pm on a Tuesday. What I wanted was an app a small child can finish alone — and that an adult can hand over without watching for what it will ask of them next.
The second half of the idea was a bet: that a single person could produce a Bible for pre-readers — 162 illustrated chapters, in three languages, fully narrated — if the content were treated as a build artifact instead of as handmade work. Ten books of art and voice is a studio's output. I wanted to see whether it could be a pipeline.
What it does
Bible Kids: Listen and Play tells the Bible in 162 short chapters, from Genesis to Acts, to children aged three to six who cannot read yet.
Each chapter is one illustrated scene, narrated out loud, followed by three small games built from the pieces of that same scene:
- Find the pairs — memory, with the animals and people from the story
- Which came first — put the moments in order; the order is the one in the picture, so the scene stays on screen as the prompt
- Find it in the picture — the voice asks for something, the child taps it
One star per game, three per chapter. That is the entire progression system.
What matters is what it refuses to do:
- Nothing depends on reading. Every command is voice plus icon. The text on screen exists for the adult nearby, never to give the instruction.
- Getting it wrong does not hurt. No lives, no timer, no error sound, no defeat screen. There is no red in the palette — the wrong piece bounces back and the narrator repeats the request. "Turn over another one," not "wrong."
- Nothing pulls the child out of the game. No ads, ever. No external links. No prices, no countdowns, no "today only." Buying, rating and support all sit behind a parental gate.
- No account, no network. It works entirely offline. We ask for no name, no email, nothing about the child. Progress stays on the device.
Book 1 is free and complete — 16 chapters. Books 2 and 3 unlock with a one-time purchase through RevenueCat. There is no subscription and no currency.
Portuguese, English and Spanish — interface, story text and recorded voice, all three.
How we built it
Flutter, as a Dart workspace with 14 packages in clean architecture: Page → Cubit → UseCase → Repository → DataSource. Errors travel as (Success?, Failure?) tuples rather than exceptions.
The game engine is pure Dart and has never imported Flutter. Which cards match, whether the order is right, how many targets remain — that is deterministic rule, 1,363 lines of it, tested in milliseconds without a widget tree.
The content is generated, not written. tools/ is a Python pipeline that lives outside the app bundle: 54 art pieces are parameterized functions, a scene is a list of pieces, and chapter 100 costs the same to produce as chapter 1. It emitted 162 scenes and 398 icons — 31 MB of SVG. A validator refuses to write anything if a chapter asks for a piece that does not exist on disk.
Narration is a service, not a UI detail — it is the primary channel, so it gets a serial queue, interruption, and an await the screen uses to know when touch may be released. Two voices at once, to a four-year-old, is noise. Across 162 chapters there are 1,620 spoken lines but only 564 distinct ones — "Isso!" appears 160 times — so the pipeline records the distinct set once and a manifest maps text to file. Speed is tied to intent rather than set globally: 0.80× for a story sentence, 1.00× for a cheer, because a drawn-out "Yes!" does not sound warm, it sounds disappointed.
RevenueCat drives the store, not the code. The parents' area builds its buttons from live offerings crossed with book state, so a button never appears for a product the store cannot sell, and releasing a book means registering a product — not shipping a release. One product per book plus an all-books entitlement. With no API key configured the app degrades to "nothing purchased" and book 1 stays fully playable.
Three languages are three layers, and one shared list of supported locales that the MaterialApp, the content data source and the speech data source all read — so the app can never end up with its interface in one language and its voice in another.
Shipped to both stores with fastlane: signed AAB with obfuscation and symbols, IPA, and store metadata plus screenshots generated in all three languages from the same source.
Challenges we ran into
648 pieces that did not exist. The ordering game's sequence was written by hand as story beats — door, rain, water_rises — and none of them had art. A piece name is a string, and a string matching no drawing complains nowhere: the screen renders an empty square and the test passes. It was wrong in 162 chapters and only surfaced when someone opened the app and looked. Two rules came out of it, and they shaped everything after: the order is derived from the scene, never authored, so it is impossible to ask for art that does not exist; and tests assert against files on disk, not against counts. Counting four names proves nothing.
Characters with no bodies. Icons have their own background and scenes have sky — two backgrounds, and the validator was only checking the second. A batch of characters ended up in sky500 tunics on sky500 backgrounds: a head and two hands, floating. The fix picks the icon background by contrast and prefers the usual blue, changing it only when the piece would vanish.
A silent RevenueCat failure. The API key is per platform, appl_… and goog_…, and swapping them raises nothing: configure accepts it, getOfferings comes back empty, and the app behaves exactly like "nothing purchased." The adult simply cannot buy, with no message. Choosing the key by defaultTargetPlatform at startup was two lines; finding out it was needed cost an afternoon.
Ten thousand TTS credits, spent on the wrong books. The narration run started at book 4 — books with no planned voice — and the quota ran out before reaching the two missing lines in the launch scope. Now the generator takes an explicit --books scope, and API keys are a comma-separated list so a quota-exhausted 401 mid-list rotates to the next key and redoes the line that failed instead of leaving a language half-recorded.
The theme was that all of these fail quietly. A missing audio asset plays nothing. A missing translation falls back. A wrong store key sells nothing. None of them throws. So each one got a guardian that shouts at build time: the speech tool rejects orphaned and colliding translations, the translation tool refuses to write a half-translated book, and one test proves all three language layers agree.
Store submission is its own project. The IPA carried an Objective-C simulator slice, review screenshots went to Apple with an alpha channel and at device sizes instead of slot sizes, the iPad slot was missing entirely, and the parental gate's 1–9 keypad allowed a sum that reached 10.
Accomplishments that we're proud of
It went from nothing to both stores in three days — 38 commits between August 25 and 27 — and what shipped is a complete product, not a demo: 162 chapters, ~10,800 lines of Dart and ~7,250 of Python, 22 test files.
A child who cannot read can finish a chapter alone. That is a test we actually run: play through all three games using only what the narration says, touching no text.
Three languages, complete to the voice. Not a translated interface over Portuguese audio — 214 distinct lines recorded in each of Portuguese, English and Spanish for the launch scope, same narrator, same rhythm rules, zero silent lines.
The principles are enforced by the code, not by discipline. There is no red token to reach for. Touch targets below 72dp are a bug rather than a style choice. Disabled is a state, never a null callback. It is hard to accidentally build something hostile to a four-year-old here.
No ads, no account, no analytics on the child's side, fully offline. In a category where the opposite is the default, that was the point.
What we learned
In a content pipeline, the dangerous failures are the quiet ones. A crash gets fixed in ten minutes. A missing asset that plays silence, a translation that falls back to Portuguese, a store key that returns an empty catalog — those ship. Every guardrail in this project exists because something rendered an empty square and no one noticed for days.
Derive, don't author. Every hand-written list that pointed at generated content eventually pointed at nothing. The ordering game got fixed not by correcting 162 sequences but by making the sequence impossible to write by hand.
Slow is not universally kind. The instinct with small children is to slow everything down, and it is wrong for half the content. Tying speech rate to the line's intent rather than to a global setting is the single change that most improved how the app feels.
Constraints for a three-year-old are constraints for the architecture. "No one reads" is why narration is a queued service with an awaitable state instead of a play() call. "Getting it wrong does not hurt" is why there is no error path in the game engine at all. Design principles that cannot be violated in code are the only kind that survive a deadline.
The store listing is part of the product. Screenshots, category, parental gate and the wording of the description all had to be built and reviewed, and they took roughly as long as a feature.
What's next for Bible Kids: Listen and Play
nlock the other seven books. All ten are written, drawn and playable — 110 chapters are sitting there marked "coming soon" for exactly one reason: their narration is not recorded. Releasing a book is recording the voice, flipping a boolean and running the generator. Not a line of Dart.
Raise the art. The pipeline is solid and the line is not yet at production quality — pieces are drawn blind, by writing coordinates and rendering. The next step is bringing in an illustrator for the pieces that carry a scene while keeping the generator for composition.
More languages. Adding one is creating a translation file; the tooling discovers it by listing the directory. The demand is obvious for a Bible app and the marginal cost is now mostly the voice recording.
Adults' area, deepened. A simple picture of what the child has played, still without an account and still on-device.
Then test the purchase ladder for real. The model — one book free and complete, one-time purchase per book, no subscription — is a deliberate bet that a parent who watches their child use book 1 will unlock book 2. Now there are RevenueCat charts to find out whether that is true.
Log in or sign up for Devpost to join the conversation.