●MAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode required●APPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro●EXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English description●FUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile development●PRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developers●WWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code apps●MAX — Rork Max bills itself as the first web Swift app builder, publishing to the App Store in two clicks with no Xcode required●APPLE — It generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro●EXPO — The standard tier builds native iOS and Android apps on React Native (Expo) from a plain-English description●FUNDING — Rork raised $2.8M from a16z, strengthening its position in AI no-code mobile development●PRICE — Free to start, with paid plans from $25/month — an accessible entry point for solo developers●WWDC — WWDC 2026 pushes Apple Intelligence forward, raising the value of native features and widening AI integration options for no-code apps
Actually Delivering 'It Updates Without Opening' in Expo — A Realistic Background Task Design
Building 'content refreshes every morning' into a Rork-generated Expo app runs into iOS background execution being far less dutiful than you expect. Here is a minimal expo-background-task setup plus a design that doesn't break when the task never runs.
Running a wallpaper app as an indie developer, the single most requested feature was "I want a new wallpaper every morning without opening the app." Naively, you would just run something periodically in the background and swap the image. But build this seriously on iOS and you end up chasing irreproducible bug reports: "it updated yesterday but not today."
The cause is not a bug in your code — iOS background execution is designed around the premise that you cannot know when it will run. Rork generates an Expo app, but underneath it iOS BGTaskScheduler is at work, and if you build without understanding its capriciousness, your UX is left to chance. This article covers building a refresh experience that survives the task not running, on both the wiring and the judgment side.
Why "It Should Update Every Morning" Doesn't
iOS background tasks are not something you can pin to a clock and force to run. The system runs them only when it decides, based on the device's usage patterns, battery, and network, that "now is acceptable."
So they run fairly often for users who open the app daily, and rarely for users who almost never open it. Ironically, the dormant users who most want "updates without opening" are exactly the ones whose background tasks rarely fire. Accept this asymmetry first or you will design wrong.
A Minimal expo-background-task Setup
Current Expo uses expo-background-task (which uses BGTaskScheduler / WorkManager internally), not the deprecated expo-background-fetch. The task itself is defined with expo-task-manager.
import * as TaskManager from "expo-task-manager";import * as BackgroundTask from "expo-background-task";const REFRESH_TASK = "daily-wallpaper-refresh";TaskManager.defineTask(REFRESH_TASK, async () => { try { const updated = await fetchAndCacheTodaysWallpaper(); // Always return success/failure. Not returning makes iOS shrink your future budget return updated ? BackgroundTask.BackgroundTaskResult.Success : BackgroundTask.BackgroundTaskResult.Failed; } catch { return BackgroundTask.BackgroundTaskResult.Failed; }});export async function registerRefreshTask() { const status = await BackgroundTask.getStatusAsync(); if (status !== BackgroundTask.BackgroundTaskStatus.Available) return; await BackgroundTask.registerTaskAsync(REFRESH_TASK, { minimumInterval: 60 * 12, // minutes; 12 hours here });}
In app.json, iOS UIBackgroundModes must include processing. In Expo you write it under infoPlist.
Forget to list the task ID in BGTaskSchedulerPermittedIdentifiers and registration succeeds but the task never runs on device — a silent failure. I lost half a day to exactly this.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Get a working minimal setup of expo-background-task and expo-task-manager, including the UIBackgroundModes configuration
✦Understand that minimumInterval is a floor, not a guarantee, expressed through concrete iOS conditions: Low Power Mode, launch frequency, charging state
✦Implement a two-tier foreground fallback that keeps a wallpaper app's auto-refresh from breaking when background never fires
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Setting minimumInterval to 12 hours only specifies a lower bound: "do not run more often than every 12 hours." There is no upper bound; if conditions are not met it may not run for 24 or 48 hours.
Read it as "runs every 12 hours" and you will feel reassured in testing — where it happens to fire — only to get bitten by irreproducible reports on real user devices. You are expressing a wish; the OS holds the decision.
The Conditions Under Which iOS Skimps
Three typical conditions lower the run probability: Low Power Mode is on, the app is launched infrequently, and the device goes long stretches without charging or network.
Conversely, a device charging overnight on Wi-Fi has the conditions aligned for background execution. The reason a wallpaper app can plausibly deliver "updated by morning" is precisely that it can lean on the overnight charging window. When designing, picture concretely which time window and which device states you are betting on, and your expectations land closer to reality.
A Two-Tier Design That Survives Failure
The conclusion: treat background refresh as a lucky bonus, and make the real workhorse a foreground fallback on launch.
// Every time the app returns to foreground, check last refresh and update if staleimport { AppState } from "react-native";AppState.addEventListener("change", async (next) => { if (next !== "active") return; const last = await getLastRefreshedAt(); const stale = Date.now() - last > 1000 * 60 * 60 * 12; if (stale) await fetchAndCacheTodaysWallpaper();});
With this two-tier approach, users whose background ran are already updated before opening, and users whose background did not run get updated the moment they open. Neither path leaves them stale. After switching to this design, refresh-related inquiries to my App Store support nearly stopped.
Testing on a Real Device
The simulator does not faithfully reproduce background behavior. Test on a real device and trigger the task manually from the Xcode debugger. During a debug session, this LLDB command fires a registered task immediately.
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"daily-wallpaper-refresh"]
After confirming the work runs correctly via this manual trigger, log "how many hours later did it actually run on its own" over a few days, and your app's realistic run frequency comes into view.
Grasp the Run Frequency in Logs Before Deciding
Talking from intuition leads to design mistakes, so I recommend first grasping your app's actual run frequency in numbers. In my case, I logged the execution time to a server every time BackgroundTaskResult returned, and aggregated a week's worth.
Capture Date.now() at the top of the task body and send it with the result code.
Compute "elapsed time since last run" per device.
Average the share that ran within 24 hours across all active devices.
What this revealed was a clear gap: for users who open the app daily, the within-24-hour run rate reached roughly 70%, while for dormant users who open it once a week it fell below 20%. Background refresh runs more for high-retention users and barely reaches low-retention ones — the asymmetry, confirmed in numbers.
The production lesson is that shortening minimumInterval to lift this rate barely helps, because the OS decides on device usage, not your interval hint. Shortening it can even backfire, burning the budget before the system decides "now is fine." I keep the interval at a practical floor (12 hours for me) and lift frequency with the foreground fallback — that division of roles is where I settled.
How Far to Lean on Background, and What to Give Up
Background execution is excellent as a supplement that lifts the baseline UX, but fragile as a feature precondition. "Updates without opening" is best designed as a delight when it lands, not a promise — that is my settled compromise for running apps long-term as a solo developer.
App Store review can also flag leaving update certainty (like reflecting purchase state) to the background. Push certainty-critical work to the foreground or the server — decide that line up front and you avoid rebuilding the design later.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.