Skip to content
Blog

Asking for Ratings in Flutter with the in_app_review Package

in_app_review 2.0.12 shows the native rating dialog on Android, iOS and macOS, but strict store quotas make timing the prompt everything.

Published on • November 5, 2026

AI Assistant

A rating prompt that fires on the wrong screen costs you the review and the user’s goodwill, while one bound to a button silently does nothing once the store’s quota is spent. in_app_review version 2.0.12 wraps the native review flows — Google Play’s In-App Review API on Android and Apple’s requestReview on iOS and macOS — behind a single Dart interface.

The plugin is small on purpose: InAppReview.instance plus three methods, and a cross-platform matrix that decides which of them you can rely on.

The three calls: isAvailable, requestReview, openStoreListing

The current API (verified in the 2.0.12 class docs) has no separate requestReviewFlow() method — requestReview() is the only dialog trigger, and openStoreListing() is the store path:

import 'package:in_app_review/in_app_review.dart';

final InAppReview inAppReview = InAppReview.instance;

Future<void> maybeAskForRating() async {
  if (!await inAppReview.isAvailable()) return; // quota or store missing
  await inAppReview.requestReview();             // native dialog, may no-op
}

void openListingForPersistentButton() {
  inAppReview.openStoreListing(
    appStoreId: '1234567890',        // iOS/macOS only
    microsoftStoreId: '9NBLGGH4NNS1',  // Windows only
  );
}

isAvailable() reports whether the device could show a dialog: on Android it also checks that Google Play Services is present (added in 2.0.7), and it returns false on web. The readme warns that requestReview() should not be used frequently, because the underlying APIs enforce strict quotas.

What each store actually allows

The package’s platform table is the contract: isAvailable() and requestReview() work on Android, iOS and macOS but are not supported on Windows, while openStoreListing() works on all four — Play Store on Android, the App Store review screen on iOS and macOS (pass appStoreId, found under App Store Connect → General → App Information → Apple ID), and the Microsoft Store on Windows.

Requirements are modest: Android 5 / API 21+ with the Google Play Store installed, iOS 10.3+, macOS 10.14+.

Quotas: why a “Rate us” button fails

Both stores ration the dialog. Android’s In-App Review API quotas and eligibility mean Google decides whether the prompt appears based on quota and user eligibility, and StoreKit enforces its own limits on iOS — so a visible button wired to requestReview() will sometimes open nothing at all. The plugin’s guidance is direct: do not trigger requestReview() via a button or other call-to-action; use openStoreListing() for that, since it is not quota-limited.

When to prompt: post-success, never on launch

The readme’s Do/Avoid list reads like a checklist: ask after the user has experienced enough of the app to give useful feedback — after completing a game level or a few days in — use it sparingly, and never interrupt someone mid-task. A durable pattern is a small state machine with a cool-down:

Future<void> onLevelCompleted(int level) async {
  if (level < 3) return;                    // too early
  if (daysSinceLastPrompt < 120) return;    // cool-down, persisted locally
  if (!await inAppReview.isAvailable()) return;

  await inAppReview.requestReview();        // dialog may still be skipped
  lastPromptDate = DateTime.now();          // record regardless of outcome
}

Because the dialog can be silently suppressed, record the attempt unconditionally and fall back to a soft, dismissible in-app card that routes to openStoreListing() — that path always works and never burns quota. Apple’s Human Interface Guidelines on ratings and reviews and Google’s when-to-request and design guidance both frame the prompt as a reward for engagement, not an interruption.

Designing for the dialog that never appears

requestReview() returns normally whether or not the OS chose to show anything — there is no success callback — so keep a quota-free escape hatch in the UI:

Future<void> softRatingCta(BuildContext context) async {
  if (await inAppReview.isAvailable()) {
    await inAppReview.requestReview(); // shown, or silently skipped
  }

  if (!context.mounted) return;
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: const Text('Enjoying the app?'),
      action: SnackBarAction(
        label: 'Rate',
        onPressed: inAppReview.openStoreListing, // never quota-limited
      ),
    ),
  );
}

Testing also differs sharply per platform, which is why a prompt that “works in dev” often does not:

  • Android: dialogs only appear from an uploaded build — Google recommends internal app sharing, and the submit button is disabled on other tracks, so real reviews come only from production.
  • iOS: the simulator and a physical device both work, but requestReview() does nothing under TestFlight (per Apple’s documentation), and openStoreListing() needs a real device because the simulator has no App Store.
  • macOS: the plugin can be tested by running the app locally.
  • Windows: isAvailable() and requestReview() are unsupported — only openStoreListing() works there.

Takeaway: Gate requestReview() behind isAvailable(), trigger it only at genuine post-success moments with a long cool-down, and reserve openStoreListing() for any visible rating button so quota limits never turn your CTA into a dead end.