Two Play Core features quietly decide whether your app keeps improving on the store listing by itself. The in-app review API lets a user rate and review your app without ever leaving it. In-app updates let you ask an active user to move to a newer build without a trip to the Play Store. Used well, they lift ratings and shrink the share of users stuck on an old version. Used carelessly, they annoy people, burn a quota you cannot see, and hand you support tickets instead of signal.
This tutorial walks both APIs in the order you actually implement them: requirements, request timing, design limits, the code path, then testing on real hardware. Everything below follows Google's documented Play Core behaviour, and two rules frame the whole thing — you cannot force a review dialog to appear, and you cannot promise an update will install on your schedule. Play decides both. Your job is to ask at the right moment, with the right flow type, and to keep the app working when the answer is no.
1. What each API does — and what it deliberately does not
The in-app review flow lets your app present a Play Store rating card. The user can leave 1 to 5 stars and an optional comment, and the review is sent to the Play Store and eventually displayed. What you do not get is feedback about your own request: the API does not indicate whether the user reviewed, or even whether the dialog was shown. There is no value to branch on, no star count to read, and no way to re-show the card on demand.
In-app updates prompt active users to install a newer version. It is not a remote-control switch: installs are never silent, restarts are never forced, and updates only move forward to a higher version code. What you control is the flow type — flexible or immediate — and how strongly you recommend it, through the update priority attached to the release. Everything else is Play's decision.
One more detail worth knowing before you design the UI: if your app sits in a closed test track, the rating and comment collected through the review flow are shared privately with you as the developer instead of being published. Useful for rehearsal, but it is not the same data as public ratings.
2. Requirements to check before any code
- In-app reviews work on Android devices (phones, tablets and TVs with Google TV) running Android 5.0 (API level 21) or higher that have the Google Play Store installed, and on ChromeOS devices that have the Play Store installed.
- The review integration needs version 1.8.0 or higher of the Play Core library; the current integration guide uses
com.google.android.play:review:2.0.2, pluscom.google.android.play:review-ktx:2.0.2for Kotlin. - In-app updates are supported on Android 5.0 (API level 21) or higher, and only on Android phones, Android tablets and ChromeOS. They are not compatible with apps that use APK expansion files (
.obb). - The update integration uses
com.google.android.play:app-update:2.1.0, pluscom.google.android.play:app-update-ktx:2.1.0for Kotlin.
Two consequences follow. If your app ships expansion files, in-app updates are off the table and you prompt updates through the store listing instead. And on a device without the Play Store, both APIs simply do nothing — they fail, quietly. So gate your UI on the outcome of your own request call rather than assuming the feature is available.
3. In-app review: the moments you are allowed to ask
Google's guidance is short and strict. Trigger the flow after the user has experienced enough of the app or game to give useful feedback — a finished level, a completed export, a first successful session. Do not prompt excessively; every unnecessary prompt burns the quota described below and frustrates the person it was meant to please.
The hard prohibition is about pre-questioning. Your app must not ask the user anything before or while the rating button or card is presented, including opinion questions such as "Do you like the app?" and predictive ones such as "Would you rate this app 5 stars?". Filtering the unhappy users out first is exactly what the rule is written to prevent. If you want your own entry point, point it at the Play Store listing — a plain link that always works — and keep the API for moments you control.
4. In-app review: the design rules you cannot bend
The card belongs to Play, not to your design system. Surface it as-is: no changes to size, opacity, shape or any other property. Do not put an overlay on top of or around the card, and keep the card and its background on the topmost layer. Once the card has appeared, never remove it programmatically — it is dismissed either by the user's explicit action or by an internal Play Store mechanism.
In code, wait for the review flow to finish before your app continues its normal path, for example before advancing to the next level. If an error occurs during the flow, do not tell the user and do not change your flow: continue normally once the completion callback fires.
5. Quotas: why launchReviewFlow is not a button
Play enforces a time-bound quota on how often a user can be shown the review dialog, which is why calling launchReviewFlow more than once in a short period — Google's own example is less than a month — might simply display nothing at all. The exact value is an implementation detail and can change without notice.
Because the quota moves, you cannot build a UI that assumes success. A dedicated call-to-action button that fires the API is the anti-pattern Google calls out explicitly: the user may have hit their quota, the flow never appears, and your button looks broken. For a deliberate "rate us" click, redirect to the Play Store instead. Keep the API for the moments where the request is expected and the absence of a dialog is invisible.
6. In-app review: the code path, step by step
Three calls, in this order.
- Create the manager.
ReviewManagerFactory.create(context)gives you theReviewManagerthat starts the flow. - Pre-cache the info. Call
requestReviewFlow(). On success you receive theReviewInfoobject the launch needs; on failure, read theReviewErrorCodefrom theReviewExceptionand log it. TheReviewInfoobject is valid for only a limited time, so request it ahead of time but only once you are certain the flow will launch. - Launch.
launchReviewFlow(activity, reviewInfo)shows the card. The completion callback tells you the flow has finished — nothing more. Continue your app flow regardless of the result.
7. In-app updates: flexible or immediate
There are two flows and they answer different product questions. Flexible updates download in the background with graceful state monitoring, so the user keeps working while the update arrives — the right choice when the new version adds features or performance improvements that can wait a few minutes. Immediate updates are a full-screen flow that requires the user to update and restart before continuing, for updates that are critical to core functionality; after the user accepts, Play handles both the installation and the app restart.
Pick the smallest flow that solves the actual problem. Escalation is legitimate — you may notify softly first and eventually require the update — but it belongs in your priority and staleness logic, not in a decision made once at implementation time.
8. Staleness days and update priority: how "how badly" is expressed
Two signals tell your app how overdue an update is.
clientVersionStalenessDays()returns the number of days since the update became available on the Play Store. This is the natural escalation clock: wait a few days before a flexible prompt, a few more before requiring an immediate one.updatePriority()returns the priority you attached to the release — an integer between 0 (the default) and 5 (the highest). It is set through theinAppUpdatePriorityfield underEdits.tracks.releasesin the Google Play Developer API. All newly added versions in a release share the release's priority, and priority can only be set while rolling out a new release — it cannot be changed later, so the value has to be decided before you publish.
One subtlety catches people out: the priority your app sees accounts for every version code between the installed version and the newest available one, whatever track each of them was released on. Release version 1 to production with no priority, version 2 to an internal test track with priority 5, then version 3 to production with no priority — production users updating from 1 to 3 still receive priority 5, because version 2 sits in between.
Note the limit as well: inAppUpdatePriority is not supported when you upload a build to internal app sharing, so priority-dependent behaviour cannot be rehearsed that way.
9. Starting the update flow and reading the result
Check availability first. Create an AppUpdateManager with AppUpdateManagerFactory.create(context), read appUpdateInfo, confirm updateAvailability() reports UPDATE_AVAILABLE, and confirm the flow type you intend to use is allowed with isUpdateTypeAllowed(). Only then call startUpdateFlowForResult() with an activity result launcher registered for the built-in StartIntentSenderForResult contract.
Each AppUpdateInfo instance can start an update only once. If the update fails and you want to retry, request a fresh AppUpdateInfo and check availability and permission again. The callback returns RESULT_OK (accepted), RESULT_CANCELED (declined), or RESULT_IN_APP_UPDATE_FAILED (something else blocked consent or the update). With immediate updates you may never see RESULT_OK, because the update has usually finished before control comes back to your app.
AppUpdateOptions also carries an AllowAssetPackDeletion flag, false by default, which lets an update clear asset packs when device storage is tight. Set it deliberately: first check whether the update succeeds with the flag left at false, and only then opt in, because the flag exists precisely to delete assets.
10. Flexible updates: monitor, download, then restart
Register an InstallStateUpdatedListener before you start the flow and unregister it when you no longer need updates. While the state is DOWNLOADING you can read bytesDownloaded() and totalBytesToDownload() to show real progress. When the state reaches DOWNLOADED, Play does not restart the app for you — that is the point of a flexible flow. Show a notification or a snackbar, let the user confirm, and call completeUpdate() when they do.
Where that call happens changes the experience. In the foreground, the platform shows a full-screen UI and restarts the app in the background; in the background, the update installs silently. Either way, check on every entry point into the app — onResume() is the usual place — whether an update is still sitting in the DOWNLOADED state, and prompt again if it is. Otherwise the downloaded bytes keep occupying the user's storage without ever being installed.
11. Immediate updates: resume a stalled flow
Once the user accepts an immediate update, Play displays progress on top of your UI for the whole duration, and if the user closes or terminates the app the download and install continue in the background. That is convenient for the update and awkward for your code: your app can return to the foreground in a state where the flow never formally completed.
Handle it on onResume(). If updateAvailability() reports DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS, call startUpdateFlowForResult() again to resume. When the user declines or cancels, the Play UI closes and your app has to choose: if it can run without the update, let them continue and ask again later; if it genuinely cannot, say so in plain language before restarting the flow, so the restart is understood rather than experienced as a crash loop.
12. Testing both APIs on real devices
Most "broken" integrations are testing artefacts. For in-app updates, internal app sharing is the standard route: make sure the test device has a build that supports in-app updates installed from an internal app sharing URL, upload a version with a higher version code, open the sharing link for the new version on the device without installing from the Play page that appears, then launch the app from the launcher — the update should now be offered to your app.
If it still is not, check the four usual causes: the account must own the app, meaning it has downloaded your app from Play at least once; the test build must share the application ID and signing key with the Play version; the installed version code must be lower than the update; and inAppUpdatePriority is not supported on internal app sharing, so priority-driven flows look different there.
For reviews, the practical difference is what the tester sees. On a closed test track the collected rating and comment go privately to the developer rather than appearing publicly — good for verifying your integration and timing, not a proxy for real rating movement.
13. What to watch once both are live
Review prompts should move rating volume gradually and never in a spike that coincides with a release: a spike is a timing effect, not a quality change, and the usual cause is a prompt placed too early in the session. For updates, track the share of active users on the latest version code week over week — a flexible update should grow that number without touching your rating average, and it should reduce the number of bug reports you receive from old builds.
If prompts correlate with uninstalls, or with angry one-star comments, the answer is not to prompt more often or earlier in the install lifecycle. Move the trigger deeper into the user journey, where the value of the app has already been demonstrated.
Running developer accounts at scale? We handle the operational side.
We received your message and will reply within 24 hours.
Or email expert@kapps.store← Back to edit