●SDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks●11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days out●EASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much later●NEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation list●UISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58●CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than once●SDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks●11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days out●EASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much later●NEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation list●UISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58●CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than once
Review Count Is Decided by When You Ask, Not the Wording — Rating Design for Rork Apps
When a Rork-built app's review count stalls, the cause is usually not the request wording but the moment you choose to ask. Here is the expo-store-review frequency limit, how iOS and Android differ, and working implementation code.
Shipping apps as an indie developer, there are stretches where downloads grow but the review count refuses to budge past two digits. I struggled with exactly this on a wallpaper app for a long time. The common response is to polish the "please review us" copy, but what actually moved the number was not the wording — it was changing the moment I asked.
A Rork-generated Expo app can add a rating request in a few lines. The question is when you call those lines. The native iOS review prompt has a hard cap on how often it appears, and asking carelessly wastes that precious opportunity. This article covers timing design for growing review count, including how it feeds ASO.
Why Review Count Feeds ASO
On an App Store search result or product page, a user has limited material to decide on installing: screenshots, the star rating, and the count.
The role of "count" is easily overlooked. At 4.8 stars with 5 ratings, a user suspects "maybe friends and family left those." At the same 4.8 with 500 ratings, it becomes proof of trust. Reviews drive conversion only when star height and count work together. Treat "raising stars" and "increasing count" as separate efforts — that is the starting point.
And count indirectly affects ranking itself. When post-install conversion and retention rise, ASO signals improve and exposure for the same keywords grows. Review count is the entrance to that loop.
Three Moments to Never Ask
First, eliminate the moments where you must not request a rating.
Right after launch. Ask a user who has felt no value yet and you get either a low rating or a dismissal.
Right after a crash or error. The worst timing, and it mass-produces one-star reviews.
Right after declining a purchase or right after showing an ad. Asking in a low mood backfires.
My first mistake was number one. While I prompted on launch, the average rating slowly declined. Simply changing the timing recovered the same app's average — that was what made me take this design seriously.
Number two is the sneaky one. Even when you think you have avoided it, a delight condition can fire through a different code path three minutes after a failed save. A passive guard that records errors and stays quiet for a while afterward is cheap insurance. In the implementation below, that guard is noteError.
✦
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
✦Understand concretely how review count drives ASO conversion along two axes: star rating and number of ratings
✦Given expo-store-review's frequency limit, concentrate the scarce display opportunity on the 'moment of delight' using code you can paste in as-is
✦Distinguish where iOS and Google Play behave differently, and where Apple's line falls between forbidden custom rating dialogs and the allowed satisfaction check
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.
You should ask right after a user has a good experience in the app. For a wallpaper app, the moment they "saved a wallpaper they liked" or "added a third favorite." For a task app, the moment they "completed a task."
The key is that the moment is the user's own achievement — not right after the app pushed something, but right after the user felt satisfied. Layer on "after a few uses, once they have come to like the app" rather than first use, and rating quality rises. My condition is "right after 3+ sessions and 2+ favorite saves."
If nothing obvious comes to mind for your own app, the table below is a starting point. What these share is that none of them is a screen transition — each is the instant right after the user did something with their own hands and finished it.
App type
Moment of delight
Example condition
Wallpaper / imagery
Right after finishing a save of an image they liked
saves ≥ 2 and sessions ≥ 3
Tasks / habits
Right after a streak passes a meaningful length
streak days ≥ 5 and completions ≥ 10
Tools / converters
Right after a successful run exports a result
successes ≥ 3 and no recent failure
Games
Right after clearing a stage or setting a personal best
clears ≥ 5 and not on a retry streak
Social / sharing
The first time their own post gets a reaction
reactions received ≥ 1 and sessions ≥ 4
One more thing about placement. The native prompt cannot appear on top of a modal. Call it before the save-complete dialog has fully dismissed and the system quietly ignores you. I call it one beat after the completion toast disappears.
expo-store-review and the Frequency Limit
Implementation uses expo-store-review. Because the native prompt is limited by iOS to three displays per 365 days, always gate it with isAvailableAsync and your own conditions.
Pulling the gate logic into its own file means you can tune the numbers later without touching any screen. What follows is the shape I actually use, tidied up.
// lib/review.tsimport AsyncStorage from "@react-native-async-storage/async-storage";import * as StoreReview from "expo-store-review";import * as Application from "expo-application";const K_SESSIONS = "review.sessions";const K_DELIGHT = "review.delight";const K_ASKED_VERSION = "review.askedVersion";const K_ASKED_AT = "review.askedAt";const K_ERROR_AT = "review.errorAt";const MIN_SESSIONS = 3; // how many launches before a user qualifiesconst MIN_DELIGHT = 2; // how many delight moments they hitconst COOLDOWN_DAYS = 120; // minimum gap since the last askconst ERROR_QUIET_HOURS = 24; // stay silent after a recent errorconst num = async (k: string) => Number((await AsyncStorage.getItem(k)) ?? 0);const bump = async (k: string) => AsyncStorage.setItem(k, String((await num(k)) + 1));/** Call once when the app returns to the foreground */export const noteSession = () => bump(K_SESSIONS);/** Call right after a save, a completion, any achievement */export const noteDelight = () => bump(K_DELIGHT);/** Call from your error boundary or failed API handlers */export const noteError = () => AsyncStorage.setItem(K_ERROR_AT, String(Date.now()));export async function maybeAskForReview(): Promise<"asked" | "skipped"> { const version = Application.nativeApplicationVersion ?? "0"; const [sessions, delight, askedVersion, askedAt, errorAt] = await Promise.all([ num(K_SESSIONS), num(K_DELIGHT), AsyncStorage.getItem(K_ASKED_VERSION), num(K_ASKED_AT), num(K_ERROR_AT), ]); const now = Date.now(); const day = 24 * 60 * 60 * 1000; if (sessions < MIN_SESSIONS) return "skipped"; if (delight < MIN_DELIGHT) return "skipped"; if (askedVersion === version) return "skipped"; if (askedAt && now - askedAt < COOLDOWN_DAYS * day) return "skipped"; if (errorAt && now - errorAt < ERROR_QUIET_HOURS * 60 * 60 * 1000) return "skipped"; if (!(await StoreReview.isAvailableAsync())) return "skipped"; if (!(await StoreReview.hasAction())) return "skipped"; await StoreReview.requestReview(); await AsyncStorage.multiSet([ [K_ASKED_VERSION, version], [K_ASKED_AT, String(now)], ]); return "asked";}
The call site just marks the achievement, then asks once the UI has settled.
async function onWallpaperSaved() { await noteDelight(); showToast("Saved"); setTimeout(() => { void maybeAskForReview(); }, 1200); // ask after the toast is gone}
Each gate is there for a reason. askedVersion limits you to once per version, but for an indie developer who ships often, that alone effectively means asking every release. COOLDOWN_DAYS layers a time-based limit on top. ERROR_QUIET_HOURS is the guard for the leak described earlier.
The return type is "asked" | "skipped" for measurement, which I will come back to. Note that "asked" means "we attempted to ask," not "the prompt appeared." Blur that distinction and you will misread your own numbers later.
Since the iOS three-per-year cap is outside developer control, you cannot know whether the system will actually show the prompt. That is exactly why you narrow to "users who meet the conditions worth asking" and aim the scarce display slots at those most likely to rate well. Calling it carelessly on every action burns the annual budget without ever displaying.
There is also a system setting that lets users turn off in-app rating requests entirely. When that is on, no amount of careful gating will show a prompt on that device. Read your numbers assuming some share of users is simply out of reach, regardless of your implementation.
iOS and Google Play Do Not Behave the Same
Rork generates a cross-platform Expo app, so the same code ships to Android. But what expo-store-review calls underneath is a different mechanism on each platform, and the behavior does not line up. Carry your iOS intuitions over unexamined and the Android numbers stop making sense.
Aspect
iOS
Android (Google Play)
Underlying mechanism
StoreKit review request
Play In-App Review API
Display cap
Documented as three per 365 days
A quota exists, exact numbers undisclosed
When the cap is hit
The call shows nothing
The flow completes with nothing shown
Detecting whether it showed
Not possible
Not possible
User-side opt-out
A system setting disables in-app rating requests
Depends on Play Store account state
Checking during development
Always shows in builds run from Xcode. Does not show in TestFlight builds
Verify via internal app sharing or an internal test track
In practice the last row matters most. The prompt not appearing in a TestFlight build is expected behavior, not a bug in your code. I once lost half a day suspecting my condition logic over exactly this. Verify visually with a build run directly from Xcode, and verify the branching itself through logs — that split saves a lot of time.
On Android, the flow only works for builds installed through Google Play. An APK sideloaded onto your dev device stays silent. Internal app sharing gives you the store-installed state, so that is where I do Android verification.
What both platforms share is that neither tells you whether the prompt was displayed. You will want to branch on success or failure. You cannot. All you get back is the fact that the call completed.
The Satisfaction Check and Apple's Line
Asking "Do you like the app?" first, sending only the "yes" users to the native prompt and routing "no" users to a feedback form — this pre-check is widely used.
But the line matters. What Apple forbids is a homemade dialog that captures star input, or custom UI that substitutes for the native prompt. Asking satisfaction as an entrance to feedback is acceptable. I use "ask satisfaction → call requestReview only for satisfied users → route the dissatisfied to support." This lets me receive low-rating sentiment as improvement requests before it flows into public reviews.
What you must never do is an overt design that fully shuts dissatisfied users out of rating. Blatant funneling gets flagged in review, and more importantly, it is not honest. The satisfaction check is only for guiding users to the right exit.
When I am unsure, my test is whether the custom UI contains any element that captures the rating itself — stars, a slider, a numeric scale. If it does, I rebuild it. A plain yes/no that only chooses an exit is routing, not rating input. Putting that line into words for yourself makes the wait for review a lot calmer.
Sizing the Denominator Before You Tighten Conditions
Take timing design far enough and you will want to keep tightening the conditions. But every condition also shrinks the pool of people you can ask. Run the arithmetic on your own numbers before you tighten.
Say a 3,000 monthly-active app where 25% reach three or more sessions, and 40% of those meet the delight condition. That is 3,000 × 0.25 × 0.40 = 300 people. From there the iOS annual cap and the user-side opt-out shave it down, and only a portion of those who see the prompt actually leave a star. Assume a few percent convert, and monthly new reviews land in the single digits.
This is a frame for plugging in your own numbers, not a benchmark. What matters is not the absolute value but the structure: every condition you add multiplies the denominator down. Moving "3+ sessions" to "5+ sessions" may raise rating quality, but the count will visibly drop.
I start loose enough to keep the denominator alive, then tighten once the average rating shows signs of slipping. Doing it in the opposite order — strict from day one — leaves you unable to tell whether the count is flat because the conditions are too strict or because the delight moment itself was defined wrong.
Measuring the Effect
As covered above, you cannot detect whether the prompt was displayed. So record attempts instead. Send the return value of maybeAskForReview as an event, and when it is "skipped", record which gate stopped it. That log is what you tune the conditions against.
Then look at daily counts in App Store Connect under Ratings and Reviews, and lay them alongside the attempt trend. Compare week over week with matching weekday composition — daily comparisons drown the signal in weekday effects.
One more caution: separate condition changes from version releases. Ship them on the same day and you cannot tell whether the movement came from the new feature or the timing design. I deliberately ship one release that changes nothing but the timing.
Average rating is dragged by the existing total, so it barely moves in the short term. With several hundred ratings already, dozens of new ones shift the average almost imperceptibly. Watch the pace of count growth first, and follow the average over months. That ordering keeps you from judging a change too early.
App Store Connect also lets you reset ratings when publishing a version, but the count goes to zero. When count itself is doing the trust-building work, weigh that carefully.
On my wallpaper app, simply moving from a launch prompt to the "moment of delight" recovered the average by about 0.3 and clearly increased monthly new reviews. A number that would not move no matter how many times I rewrote the copy moved on timing design alone — still a telling lesson in hindsight.
You cannot buy reviews, but you can design the moment you ask. Start by writing down one moment where a user in your app finishes something, and putting a single noteDelight() line there. I hope this helps anyone else stuck on review count.
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.