Share Pray

A quiet place for families and small churches to carry each other's prayers.

What inspired it

Distance is the ordinary condition now. Family in one country, friends in another, a church you visit twice a year. Staying in each other's lives takes more deliberate effort than it used to, and the thing that goes first is the small stuff — not the news you announce, but the things you are quietly carrying. Those get lost in a group chat, scrolled away under photos and links by the evening.

I wanted somewhere those could sit still. You write what you would like prayer for; the people who care about you see it and can say they are praying. That is the whole app.

I first tried to build it in 2017 — Kotlin app, Kotlin backend — and never had the evenings for it. It sat unfinished for years. What changed was not the idea. It was that I finally had the time.

How I built it

Kotlin Multiplatform, with Compose Multiplatform for the interface and my own Ktor backend. SQLDelight for storage on both ends, Koin for wiring.

The split is the point:

files
shared, platform-agnostic 109
Android-specific 18
iOS-specific 15 Kotlin + 3 Swift

Nearly everything — the feed rules, the session handling, the offline cache, the validation — is written once. What stays platform-specific is what genuinely differs: how a text field looks, how a link arrives from the system, how the database driver opens, and how a native SDK like Google Sign-In is reached.

The backend is mine too, not a BaaS: accounts and email confirmation, prayers and their visibility, communities with invite codes, a moderation panel with an audit trail. Around 390 unit and integration tests, plus 81 instrumented tests that drive the real app against a real server.

Paying for it without charging for it

Charging someone to ask for prayer was never going to happen. Nothing in the app is behind a paywall and nothing ever will be.

So RevenueCat runs a tip jar instead of a gate. The Settings screen offers a coffee, a coffee and a snack, lunch, dinner, or a standing subscription for anyone who wants to keep it running. Everything about the offer — how many tiers, what they cost, what the paywall says — comes from the offering RevenueCat serves, not from constants in my source, because prices differ by country and change without an app release.

Purchases are tied to the account rather than the device, so someone who supports on their phone is still a supporter on their tablet. Restore is there for the people who reinstall. And when no API key is built in — the ordinary state of the test flavour — the app says support is unavailable and everything else carries on. That had to be a state the app could be in, not a crash.

It is an unusual use of a purchases SDK: no entitlement gates anything. The customer info exists only so the app can say thank you.

What I learned

The bugs that matter are the ones that fail silently. Not crashes — those announce themselves. The ones that cost weeks are the ones where everything looks fine.

A test asserted the very typo it existed to catch. The Digital Asset Links file named kg.nongrate.share_pray where the app is kg.nongrate.sharepray, and Android's response to a mismatch is not an error — it simply never offers to open the app. The test had encoded the wrong spelling, so it went green every time.

Google Sign-In failed for a week because I had registered the upload key's fingerprint rather than the one Play re-signs with. The device log said NoCredentialException, and my own error handler translated that to "no Google account available" on a phone with two.

Ktor 3.2.0 compiled, passed all 390 tests, and then failed to build a release: Space characters in SimpleName 'use streaming syntax' are not allowed prior to DEX version 040. It needs minSdk 30; mine is 24. Only the release build caught it.

And a feature can fail without anything being broken. I curated 98 prayer tags and put a search box over them. Production ran for weeks with nine tags ever used. Someone typed trevoga — Russian, transliterated — rather than find anxiety, which was right there. You cannot search a set you have never seen. A tag nobody can find is a tag that does not exist.

Cutting the list would have decided that most of what people pray about does not deserve a word. Grouping was the third answer: six groups, ten tags in each, and the picker shows six things at a time.

Challenges

The one I spent longest on was a faint ring around the launcher icon. Eight diagnoses, each plausible, each wrong. It turned out to be an emulator rendering artefact — invisible on a physical Pixel. I had been correcting artwork that was already correct. Since then I check on real hardware before I believe anything.

Then there is measurement instead of taste. When a card needed lifting off dead centre, the honest version is not a number I liked the look of but a bias over the space actually left over, so a short prayer rises and a full one does not move. Same for placing the logo: its weight sits above its bounding-box centre, so the mark is positioned by a measured centroid,

$$\bar{y} = \frac{\sum_{y} y \cdot w_y}{\sum_{y} w_y}$$

where each row's weight $w_y$ is how far its pixels depart from the paper, in the proportions the eye uses — a broad pale wash counting for less than a thin dark stroke of the same area. That number is measured at build time, so it stays true if the artwork is ever redrawn.

Where it is

Live on Google Play and Apple Store in English and Russian — the Russian reviewed by a native speaker, because a devotional word and an everyday word are not the same word.

Built With

Share this project:

Updates

Submission history