all work
02Active2024

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.

FlutterObjectBoxBlocFirebase

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:

  1. Firebase Remote Config tells the app what languages are available.
  2. ValidatorService checks the local files against the remote definition.
  3. FileUpgradeService decides what needs updating.
  4. FallbackPolicyService handles failure paths — what to do if a download fails midway.
  5. FileDownloadService fetches the new pack.
  6. Isolate conversion (Dart's compute()) parses the heavy JSON off the main thread — the UI never freezes.
  7. SetLocalDataService writes to ObjectBox.
  8. 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.

Next

HealthPass by TruNord

Health & fitness app on the App Store.

All work