이 글은 누구를 위한 것인가
- 앱 평점이 3.5점대에 머물러 있는 팀
- 리뷰 요청을 언제, 어떻게 해야 하는지 모르는 개발자
- 부정적인 리뷰를 줄이고 긍정적인 리뷰를 늘리고 싶은 팀
들어가며
앱 평점 4.5+ vs 3.5의 전환율 차이는 30%에 달한다. 리뷰 요청은 아무 때나 하면 역효과다. 사용자가 가치를 경험한 직후 적절한 방식으로 요청해야 한다.
이 글은 bluebutton.kr의 앱스토어 최적화 가이드 를 참고하여 작성했습니다.
1. 리뷰 요청 전략
[리뷰 요청 타이밍 원칙]
나쁜 타이밍:
앱 첫 실행 직후
에러 발생 직후
긴 작업 도중 방해
→ 1점 리뷰 유발
좋은 타이밍:
중요한 성공 이벤트 직후
- 주문 완료
- 게임 레벨 클리어
- 파일 저장 완료
N번째 사용 (3회, 5회, 10회)
특정 기간 후 재방문 (7일 후)
[요청 전 만족도 확인]
2단계 접근:
1단계: "이 앱이 마음에 드시나요?" (예/아니오)
2단계:
예 → SKStoreReviewAPI 호출
아니오 → 피드백 폼 표시 (앱 내부)
효과:
부정적 사용자를 내부 피드백으로 유도
앱스토어에는 긍정적 사용자만 도달
→ 평균 평점 0.3-0.5점 상승
[Apple 제한 사항]
SKStoreReviewAPI: 연 3회 제한
직접 이동 금지 (앱스토어 팝업 직접 열기)
인센티브 제공 금지 (정책 위반)
타이밍은 앱이 제어하지만 UI는 시스템 제공
[Android]
In-App Review API: Google Play 공식 지원
연 횟수 제한 없음 (Google 내부 알고리즘)
사용자가 앱을 이미 평가했으면 자동 스킵
2. 리뷰 요청 구현
// iOS Swift
import StoreKit
import SwiftUI
class ReviewManager {
static let shared = ReviewManager()
private let minActionsBeforePrompt = 3
private let minDaysBetweenPrompts = 90
private var actionCount: Int {
get { UserDefaults.standard.integer(forKey: "action_count") }
set { UserDefaults.standard.set(newValue, forKey: "action_count") }
}
private var lastPromptDate: Date? {
get { UserDefaults.standard.object(forKey: "last_prompt_date") as? Date }
set { UserDefaults.standard.set(newValue, forKey: "last_prompt_date") }
}
// 중요한 사용자 액션 기록
func recordSignificantAction() {
actionCount += 1
checkAndRequestReview()
}
private func checkAndRequestReview() {
guard actionCount >= minActionsBeforePrompt else { return }
if let lastDate = lastPromptDate {
let daysSinceLastPrompt = Calendar.current.dateComponents(
[.day], from: lastDate, to: Date()
).day ?? 0
guard daysSinceLastPrompt >= minDaysBetweenPrompts else { return }
}
requestReview()
}
private func requestReview() {
DispatchQueue.main.async {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
self.lastPromptDate = Date()
self.actionCount = 0
}
}
}
// 앱스토어 직접 이동 (리뷰 쓰러 가기 버튼)
func openAppStoreForReview(appId: String) {
let url = URL(string: "https://apps.apple.com/app/id\(appId)?action=write-review")!
UIApplication.shared.open(url)
}
}
// 2단계 만족도 체크 뷰
struct SatisfactionCheckView: View {
@State private var showFeedbackForm = false
@State private var isVisible = true
var onDismiss: () -> Void
var body: some View {
if isVisible {
VStack(spacing: 16) {
Text("이 앱이 마음에 드시나요?")
.font(.headline)
HStack(spacing: 20) {
Button("😊 네, 좋아요") {
isVisible = false
ReviewManager.shared.recordSignificantAction()
onDismiss()
}
.buttonStyle(.borderedProminent)
Button("😞 아니요") {
showFeedbackForm = true
isVisible = false
}
.buttonStyle(.bordered)
}
}
.padding()
.background(.regularMaterial)
.cornerRadius(12)
.shadow(radius: 5)
.sheet(isPresented: $showFeedbackForm) {
FeedbackFormView(onSubmit: onDismiss)
}
}
}
}
struct FeedbackFormView: View {
@State private var feedback = ""
var onSubmit: () -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
Form {
Section("개선이 필요한 점을 알려주세요") {
TextEditor(text: $feedback)
.frame(height: 150)
}
}
.navigationTitle("피드백")
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("보내기") {
submitFeedback()
dismiss()
onSubmit()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("취소") { dismiss(); onSubmit() }
}
}
}
}
private func submitFeedback() {
// 피드백 API 전송
print("피드백 전송: \(feedback)")
}
}
// Android In-App Review
import com.google.android.play.core.review.ReviewManagerFactory
import com.google.android.play.core.review.ReviewInfo
class ReviewManager(private val activity: Activity) {
private val reviewManager = ReviewManagerFactory.create(activity)
fun requestInAppReview() {
val request = reviewManager.requestReviewFlow()
request.addOnCompleteListener { task ->
if (task.isSuccessful) {
val reviewInfo: ReviewInfo = task.result
val flow = reviewManager.launchReviewFlow(activity, reviewInfo)
flow.addOnCompleteListener {
// 리뷰 플로우 완료 (사용자가 평가했는지 알 수 없음 - 개인정보 보호)
// 다음 화면으로 계속 진행
}
}
}
}
fun showSatisfactionDialog(onPositive: () -> Unit, onNegative: () -> Unit) {
android.app.AlertDialog.Builder(activity)
.setTitle("이 앱이 마음에 드시나요?")
.setPositiveButton("네, 좋아요") { _, _ -> onPositive() }
.setNegativeButton("아니요") { _, _ -> onNegative() }
.show()
}
}
마무리
리뷰 요청의 핵심은 타이밍과 문맥이다. 주문 완료 직후 "앱이 마음에 드셨나요?"는 자연스럽다. 만족도 2단계 체크로 부정적 사용자를 내부 피드백으로 분리하면 평점이 평균 0.4점 이상 오른다. iOS는 연 3회 제한이 있으니 아껴서 사용하라.