Setting Up In-App Review (Review Request) in Mobile Apps
SKStoreReviewRequest on iOS and In-App Review API on Android are native dialogs letting users leave a rating without leaving the app. Native dialog conversion is significantly higher than custom popover with "go to App Store" offer.
iOS: SKStoreReviewRequest
import StoreKit
// iOS 16+
func requestReviewIfAppropriate() {
guard let scene = UIApplication.shared.connectedScenes
.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene
else { return }
SKStoreReview.requestReview(in: scene)
}
Key limitation: Apple lets show dialog no more than 3 times per 365 days regardless of requestReview call frequency. Beyond quota calls — iOS simply doesn't show dialog. No callback about dialog shown — nothing.
This means: three display moments per year is your entire budget. Spending them on just-installed users wastes budget. Optimal moments:
- After completing main action (user achieved goal)
- After N sessions (5–10), not on first launch
- After positive event: first successful payment, first completed project
Testing in Xcode Simulator: dialog shows every time, ignoring quota — development only. On real device — quota works.
Android: In-App Review API
// build.gradle
implementation 'com.google.android.play:review:2.0.1'
implementation 'com.google.android.play:review-ktx:2.0.1'
class MainActivity : AppCompatActivity() {
private lateinit var reviewManager: ReviewManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
reviewManager = ReviewManagerFactory.create(this)
}
fun requestInAppReview() {
val request = reviewManager.requestReviewFlow()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
val reviewInfo = task.result
val flow = reviewManager.launchReviewFlow(this, reviewInfo)
flow.addOnCompleteListener {
// Dialog completed (or wasn't shown)
// No info if user left review
}
}
}
}
}
Similarly iOS: Google doesn't guarantee dialog shows. Google's internal quota is unknown publicly, but in practice: no more than once per 30 days per user. Calling launchReviewFlow more often — dialog won't appear without error.
Testing on Android
In DEBUG builds dialog doesn't work realistically. For testing — FakeReviewManager:
val reviewManager = if (BuildConfig.DEBUG) {
FakeReviewManager(context)
} else {
ReviewManagerFactory.create(context)
}
FakeReviewManager always shows dialog without quotas — convenient for QA.
Request Moment: Main Factor
Wrong moment is primary reason for low In-App Review conversion. Typical anti-patterns:
- First launch: user hasn't understood value yet
- On error or failed action: requesting after crash
- On app exit: dialog blocks process, annoys
- Forcefully 3 days post-install without activity check
What works: requesting after user demonstrated engagement. E.g., reading app — after third completed book. Habit tracker — after 7-day streak. E-commerce — after second successful order.
Workaround on Quota Exhaustion
When quota exhausted, native dialog won't appear. Standard practice: offer user deeplink to store.
// iOS — deeplink to App Store review page
let appID = "123456789"
let url = URL(string: "https://apps.apple.com/app/id\(appID)?action=write-review")!
UIApplication.shared.open(url)
// Android — deeplink to Google Play
val uri = Uri.parse("market://details?id=${packageName}")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
Show this deeplink only to high-NPS users or after explicit positive signal.
Process
Determine optimal moment based on user flow.
Integrate SKStoreReviewRequest (iOS) and In-App Review API (Android).
Configure FakeReviewManager for testing.
Optional fallback to deeplink for users beyond quota.
Timeline Estimates
In-App Review integration for iOS and Android — 2–4 hours. With logic for determining moment based on user events — 1 day.







