Add in-app reviews for play flavor
Signed-off-by: William Brawner <me@wbrawner.com>
This commit is contained in:
parent
76c45bb914
commit
c6728f1afa
4 changed files with 140 additions and 0 deletions
|
@ -125,6 +125,7 @@ dependencies {
|
||||||
implementation 'com.commonsware.cwac:anddown:0.3.0'
|
implementation 'com.commonsware.cwac:anddown:0.3.0'
|
||||||
playImplementation 'com.android.billingclient:billing:3.0.0'
|
playImplementation 'com.android.billingclient:billing:3.0.0'
|
||||||
playImplementation 'com.google.firebase:firebase-core:17.5.0'
|
playImplementation 'com.google.firebase:firebase-core:17.5.0'
|
||||||
|
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||||
implementation "androidx.core:core-ktx:1.3.1"
|
implementation "androidx.core:core-ktx:1.3.1"
|
||||||
implementation 'androidx.browser:browser:1.2.0'
|
implementation 'androidx.browser:browser:1.2.0'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.wbrawner.simplemarkdown.utility
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.play.core.review.ReviewManagerFactory
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
object ReviewHelper {
|
||||||
|
// No review library for F-droid, so this is a no-op
|
||||||
|
fun init(application: Application, errorHandler: ErrorHandler) {
|
||||||
|
Log.w("ReviewHelper", "ReviewHelper not enabled for free builds")
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
import com.wbrawner.simplemarkdown.utility.AcraErrorHandler
|
import com.wbrawner.simplemarkdown.utility.AcraErrorHandler
|
||||||
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
import com.wbrawner.simplemarkdown.utility.ErrorHandler
|
||||||
|
import com.wbrawner.simplemarkdown.utility.ReviewHelper
|
||||||
|
|
||||||
class MarkdownApplication : Application() {
|
class MarkdownApplication : Application() {
|
||||||
val errorHandler: ErrorHandler by lazy {
|
val errorHandler: ErrorHandler by lazy {
|
||||||
|
@ -23,6 +24,7 @@ class MarkdownApplication : Application() {
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
ReviewHelper.init(this, errorHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context?) {
|
override fun attachBaseContext(base: Context?) {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package com.wbrawner.simplemarkdown.utility
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.SystemClock
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.content.edit
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.android.play.core.review.ReviewManager
|
||||||
|
import com.google.android.play.core.review.ReviewManagerFactory
|
||||||
|
import com.wbrawner.simplemarkdown.view.activity.MainActivity
|
||||||
|
|
||||||
|
private const val KEY_TIME_IN_APP = "timeInApp"
|
||||||
|
|
||||||
|
// Prompt user to review after they've used the app for 30 minutes
|
||||||
|
private const val TIME_TO_PROMPT = 1000L * 60 * 30
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple attempt at a non-intrusive way of prompting long-time users of the app to leave a
|
||||||
|
* review. It works by registering itself as one of the [Application.ActivityLifecycleCallbacks] to
|
||||||
|
* keep track of the current activity, which is needed to launch the review flow, along with the
|
||||||
|
* time the activity is started to estimate how long the user has been active.
|
||||||
|
*/
|
||||||
|
object ReviewHelper : Application.ActivityLifecycleCallbacks {
|
||||||
|
private lateinit var application: Application
|
||||||
|
private lateinit var reviewManager: ReviewManager
|
||||||
|
private lateinit var sharedPreferences: SharedPreferences
|
||||||
|
private lateinit var errorHandler: ErrorHandler
|
||||||
|
private var currentActivity: Activity? = null
|
||||||
|
private var activityCount = 0
|
||||||
|
set(value) {
|
||||||
|
field = if (value < 0) {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var activeTime = 0L
|
||||||
|
|
||||||
|
fun init(
|
||||||
|
application: Application,
|
||||||
|
errorHandler: ErrorHandler,
|
||||||
|
sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(application),
|
||||||
|
reviewManager: ReviewManager = ReviewManagerFactory.create(application)
|
||||||
|
) {
|
||||||
|
this.application = application
|
||||||
|
this.errorHandler = errorHandler
|
||||||
|
this.sharedPreferences = sharedPreferences
|
||||||
|
this.reviewManager = reviewManager
|
||||||
|
if (sharedPreferences.getLong(KEY_TIME_IN_APP, 0L) == -1L) {
|
||||||
|
// We've already prompted the user for the review so let's not be annoying about it
|
||||||
|
Log.i("ReviewHelper", "User already prompted for review, not configuring ReviewHelper")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
application.registerActivityLifecycleCallbacks(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStarted(activity: Activity) {
|
||||||
|
currentActivity = activity
|
||||||
|
if (activityCount++ == 0) {
|
||||||
|
activeTime = SystemClock.elapsedRealtime()
|
||||||
|
}
|
||||||
|
if (activity !is MainActivity || sharedPreferences.getLong(KEY_TIME_IN_APP, 0L) < TIME_TO_PROMPT) {
|
||||||
|
// Not ready to prompt just yet
|
||||||
|
Log.v("ReviewHelper", "Not ready to prompt user for review yet")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.v("ReviewHelper", "Prompting user for review")
|
||||||
|
reviewManager.requestReviewFlow().addOnCompleteListener { request ->
|
||||||
|
if (!request.isSuccessful) {
|
||||||
|
val exception = request.exception
|
||||||
|
?: RuntimeException("Failed to request review")
|
||||||
|
Log.e("ReviewHelper", "Failed to prompt user for review", exception)
|
||||||
|
errorHandler.reportException(exception)
|
||||||
|
return@addOnCompleteListener
|
||||||
|
}
|
||||||
|
|
||||||
|
reviewManager.launchReviewFlow(activity, request.result).addOnCompleteListener { _ ->
|
||||||
|
// According to the docs, this may or may not have actually been shown. Either
|
||||||
|
// way, it's not a critical piece of functionality for the app so I'm not
|
||||||
|
// worried about it failing silently. Link for reference:
|
||||||
|
// https://developer.android.com/guide/playcore/in-app-review/kotlin-java#launch-review-flow
|
||||||
|
Log.v("ReviewHelper", "User finished review, ending activity watch")
|
||||||
|
application.unregisterActivityLifecycleCallbacks(this)
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putLong(KEY_TIME_IN_APP, -1L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityPaused(activity: Activity) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityStopped(activity: Activity) {
|
||||||
|
currentActivity = null
|
||||||
|
if (--activityCount == 0) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putLong(KEY_TIME_IN_APP, SystemClock.elapsedRealtime() - activeTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityDestroyed(activity: Activity) {
|
||||||
|
// No op
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue