Horeb.
Offline-first Bible app for Flutter.
Rebuilt a state-heavy Flutter app to be data-driven and offline-first. Cut memory use by 64% and made the app 3x faster — without changing what users see.
The story
Horeb is an offline-first Bible app for Flutter — built to be reliable on a phone that may never have a stable internet connection.
When I joined, the app worked, but it was slow. The reason: Bloc (the state manager) was being used like a giant in-memory database. Every screen eagerly loaded everything it might need. Memory ballooned. Frames dropped. The UI was tightly coupled to the data, so even small changes risked breaking unrelated screens.
I rewrote the foundation.
What I built
- A strictly layered architecture where UI never touches the database directly. The path is: Bloc → Service Layer → Repository → ObjectBox.
- A MetaData Module that downloads, validates, and swaps Bible language packs at runtime — no app update needed to add a new language.
- A lazy-loading data system where ObjectBox is the single source of truth, and Bloc only holds what the current screen needs.
- A Firebase Remote Config integration so the team can ship a new Bible translation just by updating a config — no code, no release.
How it works
The metadata flow looks like this:
- Firebase Remote Config tells the app what languages are available.
- ValidatorService checks the local files against the remote definition.
- FileUpgradeService decides what needs updating.
- FallbackPolicyService handles failure paths — what to do if a download fails midway.
- FileDownloadService fetches the new pack.
- Isolate conversion (Dart's
compute()) parses the heavy JSON off the main thread — the UI never freezes. - SetLocalDataService writes to ObjectBox.
- MetaDataBloc notifies the UI.
I kept the concurrency control in exactly one place — there's a single pool with a max of three downloads, no double-pooling, no race conditions.
The big architectural decision: don't pick the trendy database, pick the one that solves the actual problem. I evaluated Hive (outdated), Isar (outdated), and Drift (overkill for a key-value-ish workload), and chose ObjectBox.
Stack
Mobile — Flutter, Dart, ObjectBox, Bloc/Cubit, AutoRoute, Dio, RxDart.
Firebase — Auth, FCM, Crashlytics, Analytics, Performance, Remote Config, Realtime DB, Firebase AI.
Tooling — Patrol (E2E tests), Fastlane, build_runner, flutter_gen, derry.
Modules — Reading, Search (fuzzy + JSON fallback), Cross References, Topical Bible (recursive, unlimited depth), Dictionary, AI Chat, Daily Verse, Subscriptions (RevenueCat), Notifications.
Outcomes
- 64% reduction in Dart heap memory.
- 3x faster data rendering.
- Noticeably smoother UI — fewer jank frames during scroll and search.
- A modular architecture that the team can extend without breaking unrelated areas.
The lesson I took from it: if you do something architecturally right, performance pays you back for free. I wrote about the full migration on Medium.