
For every problem there is always, at least, a solution which seems quite plausible. It is simple and clean, direct, neat and nice, and yet very wrong, #Anawim, justtothepoint.com
There are two ways to do great mathematics. The first is to be smarter than everybody else. The second way is to be stupider than everybody else — but persistent, Raoul Bott

This guide walks you through combining offline and online Bible sources to build a getDailyPlanVerse() service, while teaching the waterfall fallback pattern, how to test date‑dependent logic, and why we don’t fire all requests in parallel.
Promise chaining with ?? differs from sequential await callsawait all sources in parallel for this use caseOne function appended to src/services/bibleService.ts: - getDailyPlanVerse(date?) — the top-level verse-of-the-day function.
And a test that simulates three different dates to verify that the correct references and sources are returned.
The Verse tab adds a final panel that calls getDailyPlanVerse() with three simulated dates, printing the reference and source for each.
When you have multiple data sources, you can choose between two strategies:
Parallel race.
// ❌ Try all sources at once, use whichever resolves first
const verse = await Promise.race([
fetchOurManna(),
fetchYouVersion(key),
fetchBibleApi(ref),
]);
Problem: We launch three network calls simultaneously even if the first one succeeds. This wastes data, battery, and often the user’s bandwidth – especially problematic on mobile.
Waterfall (what we use)
// ✅ Try sources one at a time, stop at first success
const verse =
lookupKJVByRef(ref) // 1. Offline — instant, always try first
?? await fetchBibleApi(ref); // 2. Online fallback — only if offline fails
The ?? (nullish coalescing) short-circuits: if the left side is non-null or undefined, the right side is never evaluated. fetchBibleApi() is only called if the KJV lookup returned null.
This matches the Python bible_verse.py cascade:
# Python waterfall — same logic
result = _from_daily_plan() or _from_youversion() or _from_ourmanna_api()
getDailyPlanVerse to src/services/bibleService.tsWe’ll add our new function to the end of src/services/bibleService.ts. It will use these pieces already built in earlier posts:
readLines() from assetReader.ts to load a bundled .txt filefetchBibleApi() to call the online Bible APIlookupKJVByRef() to search the offline KJV database// ──────────────────────────────────────────────────────────────────────────────
// DAILY PLAN VERSE SERVICE
// Appended to bibleService.ts
// ──────────────────────────────────────────────────────────────────────────────
// ══════════════════════════════════════════════════════════════════════════════
// PLAN LOADER — reads and caches daily_plan.txt
//
// The plan is a simple text file (data/daily_plan.txt) where each line is a Bible reference like Genesis 1:1.
// We read it once and cache the result so repeated calls to getDailyPlanVerse() don’t re‑read the file.
// ══════════════════════════════════════════════════════════════════════════════
/** Cached lines from daily_plan.txt.
* `null` means “not yet loaded”.
*/
let _planCache: string[] | null = null;
/**
* Load and cache the daily reading plan from data/daily_plan.txt.
*
* Uses readLines() from assetReader.ts which:
* 1. Locates the bundled .txt asset via expo-asset
* 2. Extracts it to a local file URI
* 3. Reads it as UTF-8 text
* 4. Strips comment lines (#) and blank lines
* 5. Returns an array of reference strings
*
* @returns Array of reference strings, e.g. ["Genesis 1:1", "John 3:16", ...]
*/
async function loadPlan(): Promise<string[]> {
if (_planCache !== null) {
return _planCache; // already loaded
}
try {
// readLines reads a bundled .txt asset
// We import it lazily here (inside the function) to avoid a circular
// import if assetReader ever imports from bibleService.
const { readLines } = await import("../utils/assetReader");
// require() works with Expo’s bundler to point to the asset
// eslint-disable-next-line @typescript-eslint/no-var-requires
const lines = await readLines(require("../../data/daily_plan.txt"));
_planCache = lines;
console.log(`[Bible] Daily plan loaded — ${lines.length} references`);
return lines;
} catch (e) {
console.error("[Bible] Failed to load daily_plan.txt:", e);
// Return an empty array → getDailyPlanVerse will fall back to the API
return [];
}
}
/**
* Reset the plan cache.
* Used in tests to force a reload, and by the Settings screen when the user updates the plan file.
*/
export function clearPlanCache(): void {
_planCache = null;
console.log("[Bible] Plan cache cleared.");
}
// ══════════════════════════════════════════════════════════════════════════════
// MAIN FUNCTION: getDailyPlanVerse
// ══════════════════════════════════════════════════════════════════════════════
/**
* Get the verse of the day from the personal reading plan.
*
* ALGORITHM:
* Load the plan, determine the day index, get the reference at that index, e.g. "John 3:16".
* Look up the verse text:
* a. Offline KJV (lookupKJVByRef) — instant, no network.
* b. bible-api.com (fetchBibleApi) — online fallback.
* c. Hardcoded fallback — if everything else fails.
*
* WHY offline KJV first:
* The offline KJV is always available and instant (~0ms after the first
* load). The online APIs add 200-2000ms latency and fail when offline.
* For a prayer app used every morning, reliability matters more than
* having the "official" translation from a specific API.
*
* @param date The date to calculate the verse for (defaults to today).
* Pass a specific Date to test or simulate a different day.
* @returns VerseResult with ref, text, and source — NEVER null.
* Falls back to a hardcoded verse if all sources fail.
*
* @example
* // Today's verse
* const verse = await getDailyPlanVerse();
*
* // Simulate a specific date for testing
* const verse = await getDailyPlanVerse(new Date(2026, 0, 1)); // Jan 1
*/
export async function getDailyPlanVerse(date?: Date): Promise<VerseResult> {
// ── Step 1: Load the plan ────────────────────────────────────────────────
// loadPlan() is an async function that reads daily_plan.txt and caches the result.
// The plan is an array of strings – Bible references.
const plan = await loadPlan();
// ── Step 2: Calculate today's index ──────────────────────────────────────
// Calculate today's index: (dayOfYear - 1) % planLength.
//
// (dayOfYear(date) - 1) % planLength
// dayOfYear is 1-based, we subtract 1 to make it 0-based
//
// We duplicate the calculation inline so bibleService.ts does not depend
// on dateUtils.ts at the module level (avoids potential circular imports).
// This is a pragmatic trade‑off – the logic is simple enough to keep the dependency graph clean.
const target = date ?? new Date();
const jan1 = new Date(target.getFullYear(), 0, 1);
const doy = Math.floor((target.getTime() - jan1.getTime()) / 86_400_000) + 1;
// Difference in milliseconds divided by milliseconds per day.
// + 1 converts the 0‑based day count into a 1‑based day‑of‑year
const idx = plan.length > 0 ? (doy - 1) % plan.length : 0;
// It wraps around if the plan is shorter than the year (e.g., a 30‑day cycle) and makes the index 0‑based.
const ref = plan.length > 0 ? plan[idx] : "John 3:16";
// It guards against an empty plan.
console.log(
`[Bible] Daily plan: day ${doy}, index ${idx}/${plan.length}, ref="${ref}"`
);
// ── Step 3: Look up the verse text ────────────────────────────────────────
//
// Waterfall: try each source in priority order.
// The code implements a priority cascade:
// Offline KJV (synchronous) → bible‑api.com (async) → Hardcoded fallback
// Offline is instant, works without internet, and never fails.
// Each ?? only evaluates its right side if the left side is null.
//
// Source A: Offline KJV (synchronous — no await needed)
// It searches a preloaded JSON object (kjv.json). It returns either a VerseResult-like object or null/undefined.
const offlineResult = lookupKJVByRef(ref);
if (offlineResult) {
// If found, the function immediately returns, enriched with a source label that includes the plan position
// (Day 5 of 365). This feedback helps the user understand where they are in the plan.
// Every returned VerseResult carries a source string that tells exactly where the text came from.
// This is invaluable for debugging, support, and transparency.
// Users can be shown the source, e.g., “KJV (offline) · Day 47 of 365”.
return {
...offlineResult,
source: `KJV (offline) · Day ${idx + 1} of ${plan.length}`,
};
}
// The console.warn calls serve as a non‑intrusive log when fallback paths activate.
console.warn(`[Bible] KJV lookup failed for "${ref}", trying online...`);
// Source B: bible-api.com (online fallback for the specific reference)
// Only reached if lookupKJVByRef returned a falsy value.
const apiText = await fetchBibleApi(ref);
if (apiText) {
// If successful, a VerseResult is constructed with the API text and a similar source label.
return {
ref,
text: apiText,
source: `bible-api.com · Day ${idx + 1} of ${plan.length}`,
};
}
console.warn(`[Bible] bible-api.com also failed for "${ref}".`);
// Source C: Hardcoded fallback — always works, always returns something
// This runs only when both offline and online lookups failed. The most common scenario:
// offline KJV database wasn’t generated/bundled, and the device has no internet connection.
// The fallback verse is a well‑known passage (John 3:16), hardcoded to ensure the user always sees something.
// This eliminates the need for null checks throughout the UI.
return {
ref: "John 3:16",
text:
"For God so loved the world, that he gave his only begotten Son, " +
"that whosoever believeth in him should not perish, but have everlasting life.",
source: "KJV (hardcoded fallback)",
};
}
// ══════════════════════════════════════════════════════════════════════════════
// HELPER: get reference for a specific date (without fetching text)
// Used by the Verse tab to show "Today's reference: John 3:16" in the UI
// before the async text lookup completes.
// ══════════════════════════════════════════════════════════════════════════════
/**
* Return only the plan reference for a given date without fetching the text.
* Useful in the UI for showing a placeholder ("Loading John 3:16...") before the
* full verse result arrives.
*
* @param date The date (defaults to today)
* @returns Reference string, or null if the plan has not loaded yet
*/
export async function getTodayReference(date?: Date): Promise<string | null> {
const plan = await loadPlan();
if (plan.length === 0) return null; // Plan unavailable
// The day‑of‑year calculation is identical to getDailyPlanVerse, but without the fallback reference.
// If the plan is empty, it returns null to signal "not ready."
const target = date ?? new Date();
const jan1 = new Date(target.getFullYear(), 0, 1);
const doy = Math.floor((target.getTime() - jan1.getTime()) / 86_400_000) + 1;
const idx = (doy - 1) % plan.length;
return plan[idx];
}
DailyPlanPanel is a diagnostic/test component or widget designed to verify that the daily reading plan logic (getDailyPlanVerse) works correctly. It renders a small card inside the Verse tab and performs three independent checks:
Add this component to app/(tabs)/verse.tsx after KJVPanel:
// ── DailyPlanPanel: test display ────────────────────────────────
function DailyPlanPanel() {
/**
* We test three dates:
* - Today
* - January 1 (start of year)
* - July 4 (mid‑year)
*
* This gives us three well‑separated indices into daily_plan.txt,
* making it easy to confirm that getDailyPlanVerse(date) returns
* distinct verses for distinct dates.
*/
const testDates = [
{ label: "Today", date: new Date() },
{ label: "Jan 1", date: new Date(new Date().getFullYear(), 0, 1) },
{ label: "Jul 4", date: new Date(new Date().getFullYear(), 6, 4) },
];
/**
* Local type describing one row in the UI.
*
* result can be:
* - "loading": fetch in progress
* - VerseResult: successful lookup
* - null: defensive fallback (getDailyPlanVerse normally never returns null)
*/
type PlanResult = {
label: string; // – the human-readable name of the test date (e.g., "Today").
result: VerseResult | "loading" | null; // one of three possible states:
// "loading" – the verse is still being fetched; a VerseResult object – the successful result;
// null – an explicit fallback if the lookup returned null (though getDailyPlanVerse guarantees non‑null,
// so this branch is for defensive programming).
};
/**
* Initial state: all rows start in "loading" mode.
* Once the Promise.all completes, the entire array is replaced.
*/
const [results, setResults] = useState<PlanResult[]>(
testDates.map(({ label }) => ({ label, result: "loading" }))
);
// Data Fetching
useEffect(() => {
// Run all three lookups in parallel
// It’s the simplest way to fetch three independent resources in parallel.
Promise.all(
// Maps over the three test dates, creating an async function for each.
// Promise.all always resolves to an array containing the resolved values of each promise inside it.
// It waits until all three lookups are complete,
// then calls setResults with an array of { label, result } objects.
/* [
{ label: "Today", result: VerseResult },
{ label: "Jan 1", result: VerseResult },
{ label: "Jul 4", result: VerseResult }
] */
testDates.map(async ({ label, date }, i) => {
// getDailyPlanVerse(date) is called with the specific Date object.
// This triggers the full waterfall (offline KJV → API → hardcoded) for that date.
// Each callback is async, so it returns a Promise<{ label, result }>.
// Promise.all resolves with an array of these values.
const result = await getDailyPlanVerse(date);
return { label, result };
})
// Promise.all calls setResults with an array of { label, result } objects.
// That array becomes the argument to .then(...)
// Because setResults replaces the whole array, the UI re-renders with the final data.
).then(setResults);
// Runs once when the component mounts ([] dependency).
}, []);
return (
<View style={apiStyles.card}>
<Text style={apiStyles.title}>📅 Daily Plan</Text>
<Text style={[apiStyles.note, { marginBottom: 12 }]}>
Each date maps to a different verse from daily_plan.txt.
Three different dates should give three different references.
</Text>
{results.map(({ label, result }) => (
<View key={label} style={apiStyles.resultBlock}>
<Text style={apiStyles.resultLabel}>{label}</Text>
{result === "loading" ? (
<ActivityIndicator color="#c9a84c" size="small" />
) : result === null ? (
<Text style={apiStyles.resultError}>❌ null</Text>
) : (
<>
<Text style={apiStyles.resultRef}>{result.ref}</Text>
<Text style={apiStyles.resultText}>
{result.text.slice(0, 90)}
{result.text.length > 90 ? "…" : ""}
</Text>
<Text style={apiStyles.resultSource}>{result.source}</Text>
</>
)}
</View>
))}
</View>
);
}
/* Add the import at the top of `verse.tsx`: */
import { getDailyPlanVerse } from "../../src/services/bibleService";
Add <DailyPlanPanel /> inside the <ScrollView>, after <KJVPanel />.
npx expo start --clear.📅 Daily Plan — Chunk 16
Each date maps to a different verse...
Today John 3:16
For God so loved the world, that he gave his only...
KJV (offline) · Day 26 of 63
Jan 1 Genesis 1:1
In the beginning God created the heaven and the earth.
KJV (offline) · Day 1 of 63
Jul 4 Psalm 1:1
Blessed is the man that walketh not in the counsel of the ungodly...
KJV (offline) · Day 50 of 63