RORK LABJP
BUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verifyBUILD — Rork Max runs real Macs in the cloud loaded with Xcode and the iOS SDK, writing SwiftUI, compiling, reading the errors and building again. That loop, not the code generation, is what lifts the outputNATIVE — What comes out is pure Swift and SwiftUI, not React Native. Reaching AR, Metal graphics and widgets that React Native cannot touch is the real gap between this and other buildersPLATFORMS — Coverage spans iPhone, iPad, Apple Watch, Apple TV and Vision Pro, plus iMessage. Worth a look if you want to start from a watch app or an extension rather than a phone screenCOMPANION — The Rork Companion app lets you check a generated build on a real iPhone without a paid Apple Developer account, lowering the bar for trying a first project end to endPRICING — Free to start, paid plans from $25 a month, and Rork Max on the $200 Max plan. Worth working out up front how many projects it takes to earn that backDEADLINE — From August 31, 2026, Google Play requires target API level 36 or higher for new apps and updates alike. Ten days out, and the targetSdkVersion of what you generate is yours to verify
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.

Rork540In-App ReviewSKStoreReviewControllerGoogle Play In-App ReviewInstrumentationRetention13App Store88

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-08-20
A beta-SDK build can reach TestFlight, but it can't reach review
Builds made with a beta Xcode can be distributed through TestFlight, but they cannot be submitted for App Store review. Here is how to check which SDK produced your build, and how to protect your release profile in eas.json.
Dev Tools2026-08-14
Your lockfile's dev/prod split won't tell you which licenses your app must credit
A record of classifying every dependency in a production project to decide what belongs on an app's license screen, and where the copyleft findings and the actual shipped artifact turned out to disagree.
Dev Tools2026-07-17
Shipping Notifications Without Asking First — Provisional Authorization in Rork Apps, and the Expo Snippet That Quietly Undoes It
iOS lets you start delivering notifications with no permission dialog at all, via provisional authorization. The catch: expo-notifications reports granted as false for provisional devices, so the registration snippet in Expo's own docs re-requests permission and fires the very dialog you were avoiding. Here's why granted lies, a hook that models authorization as five states, how to write notifications for quiet delivery, when to ask for the upgrade, and how to keep provisional out of your CTR.
📚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 →