← Back to blog

Google Play in-app review API: quotas, timing, and testing

If you have shipped an Android app, you know the rating problem. The people who love your app rarely think to rate it. The people who hit a bug are the ones who rush to leave one star. Your store rating ends up lower than the experience most of your users are actually having.

There is a built-in fix that a surprising number of apps never wire up: the Google Play in-app review API. The code is about fifteen lines. What trips people up is everything around it: a quota that decides whether the card appears at all, rules about when and how you are allowed to ask, and the fact that it looks broken the first time you test it. This guide covers all three, checked against Google's own documentation.

What is the Google Play in-app review API?

The Google Play in-app review API lets your app ask Google Play to show its native rating card on top of your current screen. The user picks 1 to 5 stars, can add an optional comment, and submits without leaving the app. Google Play, not your app, decides whether the card actually appears.

Google's in-app reviews overview lists the supported devices: Android phones, tablets, and Google TV devices running Android 5.0 (API level 21) or higher with the Play Store installed, plus ChromeOS devices with the Play Store. Once the user submits, the review is sent to the Play Store and eventually displayed there.

The alternative is a link that kicks the user out to your store listing, where they have to find the rating row, tap, write, and then find their way back. Less friction means more of your happy users actually finish the review.

Two properties of the API shape everything else in this guide. The card belongs to Google, so you cannot restyle it or wrap your own UI around it. And the API tells you nothing afterwards: not the star rating, not whether the user submitted, not even whether the card was displayed.

Why does the in-app review dialog not show up?

The dialog usually does not show because Google Play enforces a time-bound quota on how often each user can see it. Call the API again within a short period (Google's example is less than a month) and nothing may appear. The API reports no error when this happens. The flow just completes.

Google does not publish the number. The documentation calls the specific value an implementation detail that can change without notice. So you cannot test the integration by spamming a button. In development you tap, nothing happens, you assume the code is wrong, and you rip it out. It is not broken. It is rate limited by design so apps cannot nag users.

Quota is the usual cause, but Google's testing page lists several others that produce the same silent result:

  • The app is not on any Play track. It does not have to be published, but its application ID must exist on at least the internal testing track.
  • The app is not in the account's Play library. The account has to have downloaded the app from the Play Store at least once.
  • The wrong account is active. With several accounts on the device, the primary account must be the one selected in the Play Store.
  • The account already reviewed the app. Delete the existing review in the Play Store first.
  • The account is protected, for example an enterprise account. Use a regular Gmail account.
  • The Play Store was sideloaded or Google Play Services is not valid on the device. Use a different device.

The request and launch flow in Kotlin

Add the review library. These are the versions Google's Kotlin and Java integration guide lists as I write this:

dependencies {
    implementation("com.google.android.play:review:2.0.2")
    implementation("com.google.android.play:review-ktx:2.0.2")
}

The flow has two steps. You request a ReviewInfo object, then you hand it to launchReviewFlow:

import android.app.Activity
import android.util.Log
import com.google.android.play.core.review.ReviewException
import com.google.android.play.core.review.ReviewManagerFactory

fun askForReview(activity: Activity) {
    val manager = ReviewManagerFactory.create(activity)

    manager.requestReviewFlow().addOnCompleteListener { task ->
        if (task.isSuccessful) {
            val reviewInfo = task.result
            manager.launchReviewFlow(activity, reviewInfo)
                .addOnCompleteListener { _ ->
                    // The flow has finished. The API does not say whether
                    // the card was shown or a review was left. Carry on.
                }
        } else {
            // Log it and move on. Never show the user an error for this.
            val code = (task.exception as? ReviewException)?.errorCode
            Log.w("Review", "requestReviewFlow failed: $code")
        }
    }
}

Three details from the guide are easy to miss:

  • ReviewInfo expires. The object is only valid for a limited time. Google suggests requesting it ahead of time, but only once you are certain your app will launch the flow.
  • Errors stay invisible. If something fails, do not tell the user and do not change your normal flow. Continue as if nothing happened.
  • Completion means nothing. The completion listener fires whether or not the card appeared. Do not thank the user for a review there, because you do not know that one exists.

If you use coroutines, the review-ktx artifact adds suspending versions of both calls: requestReview() and launchReview().

When should you ask for a review?

Ask after the user has experienced enough of your app to give useful feedback, and right after a moment that went well: a finished task, a completed level, a saved export. Do not prompt often, never ask after a crash or an error, and never put a question in front of the card.

That last point is Google's rule, not just taste. The guidelines say your app should not ask the user any questions before or while presenting the rating card, including opinion questions ("Do you like the app?") and predictive ones ("Would you rate this app 5 stars?"). That rules out the common pattern of a pre-prompt that sends happy users to the card and unhappy users to a feedback form. The design rules are just as strict: show the card as-is with no changes to size, opacity, or shape, add no overlay on or around it, keep it on the topmost layer, and do not remove it programmatically.

Inside those rules, the single biggest lever is when you ask. A prompt after a frustrating moment gets you a bad rating. The same prompt shortly after the user does something satisfying gets you a good one. The API is easy. The judgment about the moment is the real work.

Google tells you to apply your own logic because the quota can change. Here is a reasonable starting gate. These are rules of thumb, not Google's numbers:

  • The user has opened the app on at least three separate days.
  • They have just completed the app's core action successfully.
  • Nothing has crashed or thrown an error in this session.
  • You have not called the API for this user in the last few months. Store that date yourself, because the API will not tell you.

If your app is unstable, fix that before you ask anyone for stars. A review prompt cannot outrun a crash rate above the Android vitals bad behavior thresholds. And the in-app card is one tactic among several: the rest are in my guide to getting more app reviews on Google Play.

What about a "Rate this app" button?

Do not connect a button to the in-app review API. Google's documentation is explicit: you should not have a call-to-action option that triggers the API, because the user may already have hit their quota. The card will not show, and your button looks dead. For a settings-screen button, send the user to your store listing instead.

The intent for that is short:

val intent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse(
        "https://play.google.com/store/apps/details?id=$packageName")
    setPackage("com.android.vending")
}
startActivity(intent)

Setting the package to com.android.vending opens the listing in the Play Store app instead of showing a chooser. So the split is simple: the API is for moments your code picks, and the store link is for moments the user picks.

How do you test the in-app review API?

Test it by uploading the app to the internal testing track and installing it from the Play Store with a tester account that has no existing review. Quota limits are not enforced for installs from the internal test track. For faster iteration use internal app sharing, and for unit tests use FakeReviewManager.

Internal testing track

Google's testing page lists four conditions. The account must be on the internal testers list, it must be the primary account selected in the Play Store, it must have downloaded the app from the Play Store, and it must not currently have a review for the app. Once that account has downloaded the app from the track one time, you can deploy new builds to that device straight from Android Studio. Installing from a track is worth doing anyway, for the reasons in why a release build works locally but fails on Google Play.

Internal app sharing

Internal app sharing skips the track. You upload a build, get a link, and install from it. Play Console's help page says these uploads can be signed with any key and can reuse version codes, which makes it the fastest loop. One difference: a review cannot be submitted from an internal app sharing install, and the submit button is disabled to make that obvious. You are testing that the card appears, not that the review lands.

FakeReviewManager

For automated tests, the library ships a fake. Take the manager as a constructor parameter so you can swap it:

import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.testing.FakeReviewManager

class ReviewPrompter(private val manager: ReviewManager) {
    // requestReviewFlow() and launchReviewFlow() calls live here
}

// In the app
val prompter = ReviewPrompter(ReviewManagerFactory.create(context))

// In a unit or integration test
val testPrompter = ReviewPrompter(FakeReviewManager(context))

FakeReviewManager does not simulate the UI. It always returns a fake ReviewInfo and a success status when you launch the flow, so it is only useful for checking what your app does after the flow completes.

Where IOn Emit fits

The in-app review API lives in your app's code, so no publishing tool can add it for you. What IOn Emit covers is the step after: it is a Windows desktop app that publishes and updates your Google Play listing through the Google Play Developer API, including release tracks and staged rollout control, with a free tier. Pair a review prompt with a staged rollout so a bad build never reaches the users you are about to ask.

In-app review checklist

  1. Add the library. com.google.android.play:review, plus review-ktx if you use coroutines.
  2. Pick the moment. One clear success event, after the user has seen enough of the app to have an opinion.
  3. Gate it yourself. Track sessions, errors, and the date you last asked. Do not rely on the quota to pace you.
  4. Ask nothing first. No "Enjoying the app?" pre-prompt, no overlay, no changes to the card.
  5. Ignore the result. Continue your normal flow on success, failure, and completion alike.
  6. Link the button to the store. A "Rate this app" button opens your listing, never the API.
  7. Test on the internal track. Use a tester account with no existing review, and FakeReviewManager in automated tests.

This post is part of our app publishing guides for indie Android developers.

Try it free
IOn Emit - Free to start

Publish to Google Play and the App Store from your desktop, then grow with keyword gap analysis, competitor intel, rank tracking, A/B testing, and a screenshot studio. Pro is $19/mo.

→
Keep reading
IOn Emit How to Get More App Reviews on Google Play Read → IOn Emit Google Play Staged Rollouts: Which Percentage to Use Read → IOn Emit Android Vitals Bad Behavior Thresholds, Explained (2026) Read →
Reading us on Google?
Add The IOn Project as a preferred source

One click on Google’s preferences page, and our articles show up more often in your Top Stories, AI Overviews, and AI Mode.

→