Originally published on AppRanker.
You wired up the Google Play In-App Review API, the task completes without an error, and no rating card ever appears. Usually nothing is broken: the card is quota-limited, the API never tells you what happened, and only one of Google's three test methods shows the real flow. This post gives you a working setup for Kotlin, Expo and Flutter, a rule for when to call it, and a table of the usual reasons the card stays hidden.
What the API does (and won't tell you)
The flow is two calls. requestReviewFlow() fetches a ReviewInfo object from Google Play, and launchReviewFlow(activity, reviewInfo) shows a native Play card over your app where the user picks 1 to 5 stars and can add a comment. It runs on Android 5.0 (API 21) and later, including tablets and Google TV, plus ChromeOS, but only where the Google Play Store is installed.
Three facts shape everything else:
- You get no result. The completion callback fires whether the user rated, dismissed the card, or never saw it. Your code must carry on the same way in every case.
- There is a quota. Google limits how often one user sees the card. Its own example is that calling it more than once in "less than a month" may display nothing, and the exact limit can change without notice.
- The rules are strict. No "Do you like the app?" question before or during the card, no reshaping or overlaying it, and no rewards for ratings. Play's ratings policy bans incentivized feedback outright.
The quota is why Google says not to put the flow behind a button. A user who has used up their quota taps it and sees nothing.
Decide when to ask before writing the call
Since each user gets roughly one shot per period, your trigger logic matters more than the integration. Aim for a moment right after the user succeeded at something, while they are between tasks, once they have used the app enough to judge it.
| App type | Ask here | Never here |
|---|---|---|
| Productivity | After the 3rd completed export or project | First launch, mid-typing |
| Fitness | Workout summary screen | During a workout, after a missed goal |
| Game | After a level win, before the next level | Mid-game, after a loss |
| Utility | After the tool has worked several times | After an error or crash recovery |
A small eligibility gate keeps calls rare. The thresholds below are an example to tune, not a Google requirement:
private const val DAY_MS = 86_400_000L
fun reviewEligible(prefs: SharedPreferences, now: Long = System.currentTimeMillis()): Boolean =
prefs.getInt("core_action_count", 0) >= 3 &&
now - prefs.getLong("first_open_at", now) >= 3 * DAY_MS &&
now - prefs.getLong("review_last_asked_at", 0L) >= 90 * DAY_MS
Save review_last_asked_at at the moment you launch the flow. You will never learn whether a rating was left, so launch time is the only timestamp you can trust.
The integration in Kotlin, Expo and Flutter
Kotlin. Add com.google.android.play:review:2.0.2 (and review-ktx with the same version for Kotlin extensions). Fetch ReviewInfo shortly before the success moment, since it expires after a while:
class RatingAsk(private val activity: Activity) {
private val manager = ReviewManagerFactory.create(activity)
private var info: ReviewInfo? = null
fun warmUp() = manager.requestReviewFlow().addOnCompleteListener { t ->
info = if (t.isSuccessful) t.result else null
if (!t.isSuccessful) Log.w("RatingAsk", "code=${(t.exception as? ReviewException)?.errorCode}")
}
fun show(next: () -> Unit) {
val ready = info ?: return next()
manager.launchReviewFlow(activity, ready).addOnCompleteListener {
info = null
next() // runs no matter what the user did
}
}
}
Errors you may log: PLAY_STORE_NOT_FOUND (-1), INVALID_REQUEST (-2) and INTERNAL_ERROR (-100). Log them, never show them to the user, and wait for the flow to finish before moving to the next screen.
Expo. npx expo install expo-store-review, then check StoreReview.hasAction() before calling StoreReview.requestReview(). Bare React Native apps can use react-native-in-app-review.
Flutter. The in_app_review package gives you both sides:
final review = InAppReview.instance;
Future<void> askIfEligible(bool eligible) async {
if (eligible && await review.isAvailable()) await review.requestReview();
}
// Settings "Rate us" item: not quota-limited
Future<void> rateUs() => review.openStoreListing();
For a permanent "Rate us" menu entry on Android, link to https://play.google.com/store/apps/details?id=<your.app.id>&showAllReviews=true (or the market:// form) instead of calling the API.
Testing: pick the right method
Most "it's broken" reports come from testing in a setup that cannot show a working card.
| Method | Use it for | Catch |
|---|---|---|
| Internal test track | Real end-to-end card | None; quota is not applied here |
| Internal app sharing | Quick build checks | Submit button is disabled |
FakeReviewManager |
Unit and integration tests | No UI, always succeeds |
For the internal test track, all four must hold: the account is on the track, it is the primary account selected in the Play Store, it installed the app from Play, and it has not reviewed the app yet. After that first Play install, you can push new builds from Android Studio and keep testing.
Card not showing: quick diagnosis
| What you see | Likely cause | Fix |
|---|---|---|
| Completes, no card, debug build | This account never installed from Play | Install once from the internal track |
| Worked once, never again | Quota hit or already reviewed | Delete your review, or use the internal track |
| Submit greyed out | Internal app sharing install | Expected; use the internal track |
| Never shows on work phone | Enterprise-protected account | Test with a personal Gmail account |
PLAY_STORE_NOT_FOUND |
Emulator without Play | Use an image with Google Play |
| Works on one account only | Wrong primary account in Play Store | Switch accounts in the Play Store |
If none fit, check that your eligibility gate ever returns true. A gate that never opens looks exactly like a broken API.
Key takeaways
- The API reports nothing back, so always continue your flow on completion.
- A quota applies (roughly monthly in Google's example), so never trigger it from a button.
- No pre-questions, no rewards, no altering the card.
- Ask right after a success, gated by usage count, days since install and time since the last ask.
- Test on the internal test track with a personal account that has not reviewed the app.
The original covers the policy quotes, the Bare React Native setup and an FAQ in more depth: see the complete Google Play In-App Review API guide for Android, React Native and Flutter. For more on getting your app seen once the ratings come in, browse the AppRanker app leaderboard and ASO guides.
What trigger moment has worked best for your app's review prompt, and did you ever catch it silently burning the quota too early?
Top comments (0)