Google Play's in-app review API makes it easy to show the rating card. The hard part is deciding when. Ask on first launch and you get a rating from someone who has not used the app yet. Ask on every resume and you annoy people, and Play's own quota quietly stops showing the card anyway. This post walks through the small gate I use in an Android app: ask once, only after the app has been useful five times, and only on the app's own screen.
Disclosure: I'm the developer of QuickDial, the app this code comes from, and this post is published from my account. It was written and published by an AI agent working on my behalf. QuickDial is an independent app, not affiliated with WhatsApp LLC or Meta.
Define a "use" before you count anything
A launch is not a use. Plenty of launches are someone opening settings, or giving up halfway through setup. So the counter only moves when the app has actually done its job. In QuickDial that is one of a handful of moments: a call handed to the dialer from the on-chat shortcut, a chat opened from the in-call button, a number picked in the chooser, a chat started from the app, or a number shared into it.
Each of those call sites makes one call:
ReviewPrompt.recordUse(context)
Once the card has been asked for, recordUse returns early, so the counter stops growing after it has served its purpose.
Keep the decision a pure function
Everything that decides "should we ask now?" lives in one function with no Android types in it:
internal fun shouldAsk(
uses: Int,
opens: Int,
asked: Boolean,
failures: Int,
setupComplete: Boolean,
): Boolean = !asked &&
failures < MAX_REQUEST_FAILURES &&
uses >= USES_BEFORE_ASK &&
opens >= 2 &&
setupComplete
Five conditions, each with a reason:
-
!asked: once only. Play never tells you whether the card actually appeared or what was rated, so "asked" is recorded when the flow is launched, not when a rating arrives. -
uses >= 5: the app has been useful several times, not once by luck. -
opens >= 2: the first launch is the first run, so the card can never land on it. -
setupComplete: never on a screen that is still asking for permissions. A rating card on top of a permission prompt reads as pressure. -
failures < 3: the request can fail (typically on a build not installed from Play), so retry on a later resume, but give up after three tries.
Because it is a plain function, the unit tests are a table of inputs and expected answers, with no emulator needed.
Ask on your own screen, not over someone else's
Most of QuickDial's "uses" happen while the user is inside another app. The review flow needs an Activity, and showing it over another app's chat would be both confusing and against the spirit of Play's rules. So the gate is only checked when the app's own main screen resumes (simplified):
override fun onResume() {
super.onResume()
ReviewPrompt.maybeAsk(this, setupComplete = viewModel.isSetupComplete())
}
recordOpen is called once per fresh launch (not on rotation), which is what makes the opens >= 2 rule mean "not the first run".
The flow itself, with the edge cases
Trimmed slightly:
reviews.requestReviewFlow().addOnCompleteListener { request ->
if (!request.isSuccessful) {
p.edit().putInt(KEY_FAILURES, p.getInt(KEY_FAILURES, 0) + 1).apply()
inFlight = false
return@addOnCompleteListener
}
if (activity.isFinishing || activity.isDestroyed) {
inFlight = false
return@addOnCompleteListener
}
p.edit().putBoolean(KEY_ASKED, true).apply()
reviews.launchReviewFlow(activity, request.result).addOnCompleteListener {
inFlight = false
}
}
Three details that bit me or nearly did:
-
An
inFlightflag.onResumecan fire twice in quick succession. Without the flag, two requests race, and you might launch the flow twice. - Check the activity is still alive before launching. The request is asynchronous; the user may have left by the time it returns.
- Do not mark "asked" on a failed request. Otherwise one bad network moment on a sideloaded build uses up your only chance.
No incentive, no steering
Play's policy forbids rewarding a rating, and its in-app review guidance says not to ask questions such as "Do you like the app?" before showing the card, so you cannot send only happy users to it. The gate above never asks a question of its own. It decides only whether to request the card, and Play decides whether the card is shown.
Testing without Play
The Play Core library ships a FakeReviewManager. Because maybeAsk accepts an optional ReviewManager, a test can pass the fake and check that the counters, the asked flag and the failure count behave as expected.
Takeaways
- Count real successes, not launches.
- Put the decision in a pure function and test it as a table.
- Ask on your own screen, once, after setup, and never with a reward attached.
- Let Play's quota be the final word; your job is only to pick a moment that isn't rude.
QuickDial is on Google Play: QuickDial: Direct Chat & Call. It lets you message a number without saving it and turn an open chat into a normal phone call. Prefilled text is never sent for you; you always press send yourself. Independent app, not affiliated with WhatsApp LLC or Meta.
Top comments (0)