RORK LABJP
PUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicksSIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlightMAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core MLFUNDING — Rork raised $15M to power the next generation of App Store entrepreneursPRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/monthPUBLISH — Rork Max handles code signing and provisioning so you can submit to the App Store in two clicksSIMULATOR — A browser streaming simulator feels close to real hardware, and a QR code installs to your device without TestFlightMAX — Rork Max generates native Swift apps, powered by Claude Code and Claude Opus 4.6NATIVE — Rork Max reaches Xcode-class territory: AR/LiDAR, Metal 3D, widgets, Live Activities, HealthKit, and Core MLFUNDING — Rork raised $15M to power the next generation of App Store entrepreneursPRICE — Plans start free, paid tiers from $25/month, and Rork Max at $200/month
Articles/Dev Tools
Dev Tools/2026-07-07Intermediate

When In-App Review Prompts Fire but Your Ratings Never Move — Field Notes on Measuring Display Opportunities and Timing

You wired expo-store-review into your Rork app, yet the star count won't grow. The OS silently suppresses the dialog, so calling it doesn't mean it shows. These are field notes on measuring display opportunities and redesigning timing.

Rork490In-App ReviewSKStoreReviewControllerGoogle Play In-App ReviewInstrumentationRetention12App Store74

Premium Article

My star count didn't budge for two weeks.

expo-store-review was in place. The logs showed requestReview() being called dozens of times a day. Yet the rating count in App Store Connect was almost identical to the week before. Calling it but seeing no growth is the worst kind of stuck: there's no error, so there's no thread to pull.

The cause was simple. Calling the review dialog doesn't guarantee the OS shows it, and the OS never tells you whether it did. I had been happily counting requests that never arrived.

These are field notes on making that silent suppression visible for a Rork (React Native / Expo) app, using a metric I could actually control — display opportunities. I'll keep it to the steps that genuinely moved the star count on an app I run as a solo developer.

The call succeeds, but nothing is shown

Let's be precise about the constraints. On iOS, SKStoreReviewController.requestReview() (StoreReview.requestReview() under Expo) behaves as follows.

ConstraintDetailVisible to you?
Annual display capRoughly 3 times per year per userNo
Final say on showingThe OS may choose not to display itNo
CallbackNo return value for shown or submittedUnavailable
Dev buildAlways shows in the simulator (unlike production)Misleading

Google Play's In-App Review API follows the same philosophy. There's a daily quota, and repeating within a short window silently does nothing. It, too, returns no confirmation that anything was shown.

So the number of requestReview() calls is useless as a signal for the review experience. The more you call it, the more of the internal quota you burn — leaving nothing for the moment that matters. That waste was exactly what trapped me for two weeks.

Count display opportunities, not calls

With no return value, you can't capture actual displays. Stop chasing what you can't measure and place a metric one step earlier — one you fully control: the moment you passed your own gate and requested the dialog. Call that a display opportunity, and record it with your own counter.

The reasoning: you can't touch the OS quota, so instead manage the app-side gate (when, for whom, and how often you ask) strictly, and treat only passages through that gate as a trustworthy signal. Narrow the opportunities, and you concentrate the scarce OS quota on high-value moments.

// src/lib/reviewGate.ts
import * as StoreReview from 'expo-store-review';
import AsyncStorage from '@react-native-async-storage/async-storage';
 
const KEYS = {
  opportunities: 'review_opportunities',    // times a display opportunity was reached
  lastPromptAt: 'review_last_prompt_at',     // last prompt time (ISO)
  positiveSignals: 'review_positive_signals', // accumulated good experiences
  version: 'review_prompted_version',         // app version already prompted
};
 
// These two are your own anti-waste rules, separate from the OS limits.
const MIN_DAYS_BETWEEN = 60;      // minimum days since the last prompt
const REQUIRED_SIGNALS = 3;        // good experiences required before prompting
 
async function getNumber(key: string): Promise<number> {
  const raw = await AsyncStorage.getItem(key);
  return raw ? Number(raw) : 0;
}
 
// Add a point on each good experience (task done, save succeeded, etc.)
export async function recordPositiveSignal(): Promise<void> {
  const current = await getNumber(KEYS.positiveSignals);
  await AsyncStorage.setItem(KEYS.positiveSignals, String(current + 1));
}
 
// Decide whether to ask; if allowed, record the opportunity and request.
export async function maybeRequestReview(appVersion: string): Promise<boolean> {
  const isAvailable = await StoreReview.isAvailableAsync();
  if (!isAvailable) return false;
 
  const signals = await getNumber(KEYS.positiveSignals);
  if (signals < REQUIRED_SIGNALS) return false;
 
  const promptedVersion = await AsyncStorage.getItem(KEYS.version);
  if (promptedVersion === appVersion) return false; // never twice on one version
 
  const lastAtRaw = await AsyncStorage.getItem(KEYS.lastPromptAt);
  if (lastAtRaw) {
    const days = (Date.now() - new Date(lastAtRaw).getTime()) / 86400000;
    if (days < MIN_DAYS_BETWEEN) return false;
  }
 
  // Count only this passage as a display opportunity.
  const opp = await getNumber(KEYS.opportunities);
  await AsyncStorage.setItem(KEYS.opportunities, String(opp + 1));
  await AsyncStorage.setItem(KEYS.lastPromptAt, new Date().toISOString());
  await AsyncStorage.setItem(KEYS.version, appVersion);
 
  await StoreReview.requestReview(); // the OS decides whether it shows
  return true;
}

The crucial part is sending the count of times maybeRequestReview() returned true to your analytics. Line up that total of display opportunities against the rating deltas in App Store Connect and Play Console, and you can estimate the otherwise invisible reality of displays and submissions.

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
A concrete hook that counts times you reached a display opportunity, not raw requestReview calls
A weekly method to estimate the invisible iOS 3-per-year and Android quota limits against your rating deltas
How to read the log that moved my ratings once I shifted prompts from cold launch to a moment of accomplishment
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.

or
Unlock all articles with Membership →
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.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-26
Keep Your Rork App's Review From Stalling on a Privacy Manifest Gap
Handle PrivacyInfo.xcprivacy and Required Reason APIs in a Rork Expo app — a common cause of App Store review stalls — with the app.config.ts setup and a step-by-step check of third-party SDKs.
Dev Tools2026-06-23
DAU Went Up but Retention Didn't — Rebuilding Gamification That Actually Sticks in Rork Apps
Points, badges, and leaderboards lift DAU, but retention is a different story. Field notes on a server-authoritative point ledger, streaks that forgive, and leaderboards that don't crush newcomers — with working code for Rork apps.
Dev Tools2026-06-14
Stop Burning Your One Push-Permission Shot on App Launch — Pre-Prompt Priming for Rork Apps
If your Rork (Expo) app fires the OS push-permission dialog at launch, every 'Don't Allow' tap closes that channel forever — iOS won't let you ask again. Here's how a self-built pre-permission screen lifts your opt-in rate, with the Expo code to do it.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →