Requesting App Store Ratings from Flutter Without Burning Quota
Apple's three-prompt limit per 365 days, Google's time-bound quota and design rules, what changed in in_app_review 2.0.12, and an eligibility strategy that survives suppression.
Published on • November 12, 2026
AI Assistant

Our in_app_review post already covered the package API and a cool-down state machine. What it only touches on is the store-side contract that makes those numbers correct: Apple publishes an exact annual quota, Google documents a quota it refuses to quantify plus a set of hard design rules for the prompt itself, and both change behavior silently. This post reads those primary sources and turns them into an eligibility function you can ship.
The two quotas, as the stores actually define them
| Apple (StoreKit) | Google Play (In-App Review) | |
|---|---|---|
| Limit | 3 prompts per app per 365 days | Time-bound quota, value undisclosed |
| Enforced by | The system, which checks prior feedback | Play Store, “implementation detail, changeable without notice” |
| Re-trigger behavior | Fourth request in the window shows nothing | launchReviewFlow again within roughly a month may show nothing |
| Escape hatch | User can opt out of all review prompts | User simply dismisses; quota still consumed |
Apple’s wording is precise: the system “automatically limits the display of the prompt to three occurrences per app within a 365-day period,” and users can opt out for every app on the device, which is why requestReview() returning normally proves nothing. Google’s is vaguer by design: calling the flow “more than once during a short period of time (for example, less than a month) might not always display a dialog,” and the quota “can be changed by Google Play without any notice.” Both facts point to the same conclusion: your own pacing logic is the only pacing you can trust.
What Google forbids in the prompt’s design
The Play guide is unusually prescriptive, and violating it is a policy problem, not a style choice:
- Surface the card exactly as provided: no changes to size, opacity, shape or any other property.
- No overlay above or around the card, and the card stays on the topmost layer.
- Never remove the card programmatically; Play dismisses it on user action or its own mechanism.
- Ask nothing before or during the prompt, explicitly including “Do you like the app?” and “Would you rate this app 5 stars.” Pre-qualifying questions are banned.
That last rule kills the common “smiley face survey that routes happy users to the store” pattern on Android: if your pre-question is about app opinion, it cannot precede the rating card. Device support is Android 5.0 (API 21) and up with the Play Store installed, plus ChromeOS with Play Store.
What Apple’s HIG asks for
Apple’s Human Interface Guidelines on ratings and reviews are behavioral rather than technical, and just as binding in practice: ask only after demonstrated engagement, never on first launch or during onboarding, find natural breaks instead of interrupting a task, and “consider allowing at least a week or two between requests.” Apple also nudges you toward the system prompt over anything custom, since the OS can check previous feedback before deciding to show anything. The HIG also warns about resetting your summary rating when releasing a new version: it reflects the current build but tends to reduce the total rating count, which can deter downloads.
in_app_review 2.0.12 today
final review = InAppReview.instance;
Future<void> requestAfterMilestone() async {
if (!await review.isAvailable()) return; // quota, missing store, or no Play Services
await review.requestReview(); // may render, may silently no-op
}
void openListingInstead() {
review.openStoreListing(appStoreId: '1234567890'); // never quota-limited
}
Recent releases explain why staying current matters: 2.0.10 updated deprecated SKStoreReviewController usage on iOS and macOS, moved Android to Play Review 2.0.2, and added Swift Package Manager and a privacy manifest; 2.0.11 migrated the Android platform code from Java to Kotlin; 2.0.12 fixed an Android NullPointerException that occurred when apps were backgrounded. The platform matrix is unchanged: requestReview() works on Android, iOS and macOS, returns false from isAvailable() on web, and is unsupported on Windows, where only openStoreListing() is available.
An eligibility function instead of a trigger
Since both dialogs can be suppressed without error, gate the call on evidence of engagement and record the attempt regardless of outcome:
Future<void> maybeRequestReview() async {
if (sessions < 5) return; // too early to have an opinion
if (daysSinceInstall < 7) return; // let value accrue
if (daysSinceLastPrompt < 14) return; // HIG's week or two, doubled
if (!await InAppReview.instance.isAvailable()) return;
await InAppReview.instance.requestReview(); // shown, or silently skipped
await prefs.setString('lastReviewPrompt', DateTime.now().toIso8601String());
}
Pair it with a quota-free path: a dismissible card whose action calls openStoreListing(), which always opens the store’s review screen and never depends on quota. That split, quota-bound system dialog for the opportunistic moment, store listing for anything the user clicked, is the only arrangement where a visible control can never dead-end.
Moments that must never trigger a prompt
Quota math is only half of timing. The negative cases are where ratings are actively lost:
- Immediately after a crash, an error state, or a failed purchase, when frustration is highest.
- During checkout or another task the user has not finished, which Apple frames as interrupting mid-task.
- Right after a support conversation or a refund request, when the relationship is already strained.
- In the same session as a declined prompt: a dismissal is a decision, so respect it for weeks.
- On first launch or during onboarding, before an opinion can exist.
- Behind a button, where a suppressed dialog turns into a broken control.
Every item above is either an Apple HIG rule or a Google rule (do not prompt excessively, ask nothing while presenting the rating), so they collapse into one blockedContext flag checked before your eligibility function runs.
The fine print: Play Core and data safety
Android integration carries paperwork. The In-App Review API requires Play Core 1.8.0 or higher (in_app_review has shipped Play Review 2.0.2 since version 2.0.10), access is governed by the Play Core SDK Terms of Service, and Play’s data safety rules classify the user-entered rating and comment as collected data: encrypted, published publicly, or shared privately with you when the review arrives from a closed test track. iOS has no equivalent disclosure because Apple renders the system UI itself, though the HIG still asks you to prefer that system prompt over anything of your own design.
Testing the prompt without lying to yourself
Neither dialog is reliably visible during development, which is why “it works in dev” is the wrong assertion. Our in_app_review post documents the matrix: Android dialogs appear only from an uploaded build (internal testing or internal app sharing), iOS does not show the dialog under TestFlight, and the simulator has no App Store for openStoreListing(). Structure QA accordingly: unit tests assert the gates (which checks ran, whether an attempt was recorded), while a human on a real device verifies the store listing path once, since that is the only branch quota can never break.
Managing the ratings you get back
The prompt is half the job. Apple’s ratings page points to the other half: respond to reviews in App Store Connect (the reviewer is notified and may update their rating), keep support contact details easy to find, treat a summary-rating reset as a rare lever, and note that review summaries on product pages are now LLM-generated across the US and eight additional storefronts. On Google’s side, remember that users can delete their reviews from their Play or Google account, and reviews submitted from a closed test track are shared privately with you instead of the public. Both stores reward prompt timing and review responses far more than prompt frequency.
Takeaway: Treat Apple’s three-per-365-days and Google’s undisclosed time-bound quota as hard ceilings you cannot observe, never attach the system dialog to a button the user clicked, obey Google’s no-tamper and no-pre-question design rules, pace requests at engagement milestones with a two-week floor, and keep openStoreListing() as the quota-free fallback while you respond to the reviews that do arrive.