13.
Getting Top Ratings & Avoiding Negative Reviews: Gathering In-App Feedback
Written by Fuad Kamal
So now your app is live on the Play Store, after months of internal, alpha and beta testing. Your app is hardened and secure, and you’ve built automated toolchains to rapidly test and update your app. What happens once your software is in the customers’ hands?
How do you assess how they feel about the app? How do you collect and respond to feedback? How can you avoid most one-star ratings and ensure your users are happy and have the best possible experience, both with the app and your company? In this chapter, you’ll get an inside look at how two large companies accomplished just that and how with a little effort, you can, too.
Responding to Google Play comments in the dashboard
When your app is on Google Play in any form, your users can provide a rating and feedback. This feedback is visible to you in the Play Console under the Quality section.
Go to the Play Console and open Quality -> Ratings and reviews -> Ratings. Here you’ll find various statistics and data visualizations for your app’s ratings over time. Note that in this section, ratings from both beta and release versions of your app are combined.
Next, check the Reviews section. Here you’ll find any reviews for the release version of your app. You may also see a notice reminding you to use the Google Play In-App Review API. You’ll implement this in Podplay later in this chapter.
Feedback from beta versions of your app appears in the Testing Feedback section. You’ll find the star rating and user’s comments for each review, along with a text box where you can directly reply to the feedback. Your replies will then appear in the Play Store along with the user’s review. It’s a handy way for developers to provide some sort of response to reviews received in the Play Store.
Next, check out the Notifications page under the Play Console’s Setup section. Here you’ll find various options to get email notifications, such as a notification when a user updates their review after you’ve replied to it.
Ratings and reviews can significantly impact the perception of your app and the willingness of new users to download it. Most app developers seem to publish their app and then wait for reviews, good or bad. However, the typical user doesn’t make an effort to leave feedback or rate your app on the App Store unless they have a very negative experience. Then, they take the extra effort to go to the Play Store, leave a one-star rating and rant about your app.
Typically these users are frustrated after having a negative experience, and they don’t know a more effective way to get the developer’s attention to resolve the issue. For example, maybe 70% of your users are happy with your app, but 30% of users have some sort of frustrating experience. Most users with a good experience might not bother to leave a rating or review, while users with a negative experience will make an extra effort to leave a review. The typical net result can be a low one or two-star rating overall on the App Store and a long list of negative reviews, despite most users having a good experience.
However, you can be proactive and guide users to provide feedback directly to you from within your app when they have a negative experience, rather than leaving negative feedback publicly on the Play Store. Likewise, you can encourage users who had a good experience with your app to leave a high star rating and positive review on Google Play.
Case studies: in-app feedback
Earlier in this chapter, you learned the strategy for optimizing your app’s ratings and reviews in your favor. That strategy isn’t untested theory. More than one company has quickly changed overwhelmingly negative reviews and one-star ratings to positive reviews and four and five-star ratings.
Note that while this book is specific to Android and mainly focused on the Google Play Store experience, the case studies that follow cover both iOS and Android apps from these companies. The strategies in this chapter are universally applicable for all platforms. Keep in mind, the app user bases numbered in the millions for these companies, but only a small percentage of users left ratings and feedback. Revising their strategies and becoming proactive in how they collected user feedback had a phenomenal impact on both the users’ experience and the public perception of the apps.
Target
Target, the eighth largest American retail chain, has been in business since 1962. It has close to 2,000 stores spread across the country. However, their public-facing apps were plagued with one-star reviews and negative ratings. Erik Kerber, the principal iOS developer at Target, posted the following in the raywenderlich.com slack in July 2017:
Funny little milestone - 15 months from a stupid line of code to 1 million reviews.
Previously we would typically get 5-20 (typically negative) reviews for every 2-week release. Rewarded for being pests.
They deployed a simple strategy that completely reversed the trend of negative reviews and vastly increased the number of reviews they got. While the code example above is Swift for requesting an App Store review from within an iOS app, the same feature is now available in Android. The iOS framework for this has built-in rules for when and how often the user sees the in-app prompt. Basically, it prevents the user from getting spammed with review requests.
You’ll implement this same functionality later in the Podplay Android app. If you want more information on the iOS functionality, you can find the documentation here: https://developer.apple.com/documentation/storekit/skstorereviewcontroller/requesting_app_store_reviews.
Taking a proactive approach of asking users for feedback, rather than waiting for frustrated users to reach out, completely flips the outcome.
Octo Telematics
Octo Telematics, based in Rome, Italy, is the world’s leading telematics provider. They produce hardware and apps used by auto insurance companies worldwide to monitor their customers’ driving behavior, enabling them to reward good driving behavior with incentives such as discounts on insurance premiums. One of their clients had an app with an overwhelming number of one-star reviews on both the iOS and Android app stores.
Here are some examples of the negative reviews users were leaving, which, if taken in context, amount to bugs that were getting lost in the call centers and never even reaching the developer team:
This chapter’s author is both a colleague of Erik from Target, via the raywenderlich.com team, and was newly hired as the director of mobile at Octo. So, using a strategy similar to Target’s, the mobile team at Octo was able to reverse this negative review trend almost overnight. They took it a bit further, however.
Rather than prompting users to leave ratings and feedback, they additionally directed the users with a negative experience to provide direct feedback to the development team, rather than publicly leaving comments on the App Store. Here’s the overall strategy:
- Prompt the user, in-app, for feedback: How do they like the app?
- If they love the app, prompt them to leave a rating and review on the respective app store.
- If they don’t love the app, prompt them for direct feedback, sent to the developer team.
The direct feedback mechanism uses the JIRA API to create tickets in the development team’s JIRA board directly. These tickets further ensure issues are brought front and center and prioritized. When the new version of the app launched with this feature enabled, the iOS App Store ratings went from one star to four stars overall in just a couple of weeks. At the time of writing, it currently sits at 4.7 stars!
This is because Apple implemented a change in their App Store, where new versions of an app get their own rating, rather than the rating being an average of all the versions of the app since its first launch. On the Google Play Store, the app rating is still an average of all versions of the app. While the process took longer, the Android app still increased from 1.5 stars to 3.1 stars overall, and it’s still climbing.
Building an in-app feedback mechanism
Now that you’ve seen how important it is to properly channel reviews and feedback for your app, you’ll learn how to implement it in Podplay. Sending your user out of the app to leave a review in the Play Store can be a disruptive experience. Thankfully, you don’t have to do that anymore because Google, and Apple for iOS, now provides in-app review APIs which let users leave feedback and reviews directly from within your app interface.
Creating an in-app review module
To start implementing the in-app review feature, you’ll create a new feature module. This module will let you decouple the relevant code from the rest of the code. Decoupling features helps you reuse them across your app or even between multiple projects. In fact, you can even publish the code as a library for other people or organizations to use.
Open the starter project for this chapter. From the menu, choose File -> New -> New Module.
From the wizard that appears, choose Android Library for the module type:
In the next screen, change the name of the module from the default to inappreview. Leave the language as Kotlin and the Bytecode level at 8. Change the minimum SDK to match the minimum SDK of the project, which is 26 as of this writing. Click Finish, and you’ll see a new inappreview module in the project.
In your app module’s build.gradle, add the following to the dependencies section:
implementation project(":inappreview")
This dependency lets you use the inappreview code within the app module. It makes the objects, functions and types you define within the module accessible from your main app code, just as you can with other libraries you add as dependencies to your app Gradle file.
Next, make sure the version numbers for compileSdkVersion, minSdkVersion and targetSdkVersion match in both the app build.gradle and module build.gradle to prevent any dependency versioning issues. If you want, you can extract the version numbers into variables, so you only need to update them in one place in the future.
Finally, you need to add some dependencies to build this feature.
First, you need the Kotlin dependency and other things, such as styles. Replace the dependencies block in the innappreview build.gradle with:
// 1
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:$coreKtx_version"
//2 - UI
implementation "androidx.appcompat:appcompat:$appCompat_version"
implementation "androidx.constraintlayout:constraintlayout:$constraintLayout_version"
implementation "androidx.cardview:cardview:$cardView_version"
// 3 - Play Core
api "com.google.android.play:core:$playCore_version"
api "com.google.android.play:core-ktx:$playCoreKtx_version"
Here’s a code breakdown:
-
Note the use throughout of variables for library version numbers. You can define these variables in the
buildscriptblock of your project build.gradle. That way, you only need to update version numbers in one place rather than remembering to keep them in sync across multiple Gradle files. The starter project already has version number variables defined. Keep in mind these numbers change frequently: It’s usually best to keep your library versions up to date. Also, note the use of double quotes everywhere rather than single quotes. While either notation might be legal in Groovy, using double quotes makes things easier if you ever want to convert your Gradle files to Kotlin instead. -
You need these to set up the general UI code.
-
The In-App Review API is a part of the Play Core SDK. The Play Core KTX library is optional. It provides Kotlin coroutines versions for the asynchronous method calls in the regular Play Core Library and other useful extensions that use the APIs more idiomatic from Kotlin.
Click Sync Now. Build and run to check that everything works OK.
Review prompt UI
Next, you need to create the UI for your review prompt.
Start by creating new resource directories within the inappreview module for drawable, layout and values. For each, right-click the module package and select New -> Android Resource Directory. Then choose the appropriate directory type. Android Studio will then create each of those directories for you under res.
You’ll use Databinding to tie your UI code to your XML layouts. Make sure you enable Databinding:
In the app build.gradle, add the following code in the android{} block after compileSdkVersion:
dataBinding {
enabled = true
}
In the inappreview build.gradle, add the following code in the android{} block:
buildFeatures {
viewBinding true
}
The code above enables View Binding in the inappreview module. Click Sync Now.
Under res -> layout for the inappreview module, create a new layout file named fragment_in_app_review_prompt. Then replace the contents of the file with:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/inAppReviewPromptRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:orientation="vertical"
app:cardCornerRadius="4dp"
app:cardElevation="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="@dimen/largeMargin">
<ImageView
android:id="@+id/sadFace"
android:layout_width="@dimen/ratePromptFaceSize"
android:layout_height="@dimen/ratePromptFaceSize"
android:layout_marginStart="@dimen/largeMargin"
android:contentDescription="@string/sad_face"
android:src="@drawable/sad_face" />
<ImageView
android:id="@+id/progressBar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/smallMargin"
android:layout_marginStart="@dimen/promptProgressMargin"
android:layout_marginEnd="@dimen/promptProgressMargin"
android:layout_weight="1"
android:contentDescription="@string/progress_bar"
android:src="@drawable/progress_black" />
<ImageView
android:id="@+id/happyFace"
android:layout_width="@dimen/ratePromptFaceSize"
android:layout_height="@dimen/ratePromptFaceSize"
android:layout_marginEnd="@dimen/largeMargin"
android:contentDescription="@string/happy_face"
android:src="@drawable/happy_face" />
</LinearLayout>
<TextView
android:id="@+id/reviewPromptTitle"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/defaultMargin"
android:gravity="center_horizontal"
android:text="@string/inAppReviewTitle"
android:textColor="@color/charcoal_black"
android:textSize="@dimen/ratePromptTitleTextSize" />
<TextView
android:id="@+id/reviewPromptText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/defaultMargin"
android:layout_marginEnd="@dimen/defaultMargin"
android:gravity="center"
android:text="@string/inAppReviewMessage"
android:textSize="@dimen/ratePromptMessageTextSize" />
<TextView
android:id="@+id/leaveReview"
android:layout_width="match_parent"
android:layout_height="@dimen/buttonSize"
android:layout_marginHorizontal="@dimen/defaultMargin"
android:layout_marginTop="@dimen/largeMargin"
android:background="@color/dark_sea_green"
android:gravity="center"
android:text="@string/inAppReviewRateNow"
android:textColor="@color/white"
android:textSize="@dimen/promptButtonTextSize" />
<TextView
android:id="@+id/reviewLater"
android:layout_width="match_parent"
android:layout_height="@dimen/buttonSize"
android:layout_marginHorizontal="@dimen/defaultMargin"
android:layout_marginTop="@dimen/buttonMarginTop"
android:layout_marginBottom="@dimen/defaultMargin"
android:background="?attr/selectableItemBackground"
android:gravity="center"
android:text="@string/inAppReviewRateLater"
android:textSize="@dimen/promptButtonTextSize" />
</LinearLayout>
</androidx.cardview.widget.CardView>
Then copy drawable and values from the final project into your current project. If you’re following along with the provided XML, the UI will look like this:
In the inappreview module, create a new package for the Kotlin class corresponding to your layout file and name it dialog. Create a new Kotlin class named InAppReviewPromptDialog in this module and add:
import androidx.fragment.app.DialogFragment
class InAppReviewPromptDialog: DialogFragment() {
private var binding: FragmentInAppReviewPromptBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentInAppReviewPromptBinding.inflate(inflater, container, false)
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initListeners()
dialog?.setCanceledOnTouchOutside(false)
}
private fun initListeners() {
val binding = binding ?: return
binding.leaveReview.setOnClickListener { onLeaveReviewTapped() }
binding.reviewLater.setOnClickListener { onRateLaterTapped() }
}
private fun onLeaveReviewTapped() {
// TODO
dismissAllowingStateLoss()
}
private fun onRateLaterTapped() {
// TODO
dismissAllowingStateLoss()
}
/**
* Styles the dialog to have a transparent background and window insets.
*/
override fun onStart() {
super.onStart()
initStyle()
}
private fun initStyle() {
val back = ColorDrawable(Color.TRANSPARENT)
dialog?.window?.setBackgroundDrawable(back)
dialog?.window?.setLayout(
resources.getDimensionPixelSize(R.dimen.ratePromptWidth),
resources.getDimensionPixelSize(R.dimen.ratePromptHeight))
}
}
In the code above, you create a DialogFragment and set its style. You also set the click listeners for the Leave a review and Not now buttons.
Build and run again to make sure everything still works fine.
In-app review feature
Now that you’ve set up the UI, it’s time to build out the in-app review feature. You want to show the UI from the previous section after using Podplay for some pre-defined amount of time. The dialog will offer two options: one to rate the app and one to rate it later.
The first option will start the review flow and save the user’s selection to preferences, so you don’t ask them again. The second option will also save the user’s selection to preferences, along with a timestamp for when the event occurred. Then you can again prompt the user after some pre-defined amount of time has passed. In this case, you’ll ask them again after a month.
Note: In this exercise you are going to be using a pattern of creating interfaces in implementing them. This is being done to make it easy to integrate this example with dagger in the event that you decide to use this in your own application. If you are not using dagger you may want to refactor this o get rid of those interfaces.
Create a new package named preferences in the module. Then, add a new interface named InAppReviewPreferences in this package. Define the following functions for the interface:
// 1
fun hasUserRatedApp(): Boolean
fun setUserRatedApp(hasRated: Boolean)
// 2
fun hasUserChosenRateLater(): Boolean
fun setUserChosenRateLater(hasUserChosenRateLater: Boolean)
// 3
fun getRateLaterTime(): Long
fun setRateLater(time: Long)
This interface serves as an abstraction for the logic to store and fetch the data regarding the user’s rating choices:
- Know if the user already rated the app.
- Know if they instead chose to rate later.
- Know what “later” means as you’ll define a time when to remind them again.
Next, in the same package, add a class named InAppReviewPreferencesImpl and replace the contents with:
class InAppReviewPreferencesImpl(
private val sharedPreferences: SharedPreferences
// 1
) : InAppReviewPreferences {
// 2
companion object {
const val KEY_IN_APP_REVIEW_PREFERENCES = "inAppReviewPreferences"
private const val KEY_HAS_RATED_APP = "hasRatedApp"
private const val KEY_CHOSEN_RATE_LATER = "chosenRateLater"
private const val KEY_RATE_LATER_TIME = "rateLaterTime"
}
}
In the code above:
-
InAppReviewPreferencesImplimplements the interface you defined earlier. - You define some constants for storing the user’s choices in
SharedPreferences.
In Android Studio, select Code -> Implement Methods to fill in the missing interface methods. Then update those methods as follows:
override fun hasUserRatedApp(): Boolean =
sharedPreferences.getBoolean(KEY_HAS_RATED_APP, false)
override fun setUserRatedApp(hasRated: Boolean) =
sharedPreferences.edit { putBoolean(KEY_HAS_RATED_APP, hasRated) }
override fun hasUserChosenRateLater(): Boolean =
sharedPreferences.getBoolean(KEY_CHOSEN_RATE_LATER, false)
override fun setUserChosenRateLater(hasUserChosenRateLater: Boolean) =
sharedPreferences.edit { putBoolean(KEY_CHOSEN_RATE_LATER, hasUserChosenRateLater) }
override fun getRateLaterTime(): Long =
sharedPreferences.getLong(KEY_RATE_LATER_TIME, System.currentTimeMillis())
override fun setRateLater(time: Long) =
sharedPreferences.edit { putLong(KEY_RATE_LATER_TIME, time) }
These functions store and read data to and from shared preferences. Notice you use edit, which takes in a lambda function. This extension function is a part of the Core KTX dependency, and you can use it by adding:
import androidx.core.content.edit
Now that you’ve implemented the preferences and the date part of the feature, it’s time to connect this to your review dialog. Open InAppReviewPromptDialog and add:
private val preferences: InAppReviewPreferences
Instead of initializing an instance of InAppReviewPreferences inside InAppReviewPromptDialog, you provide it via the constructor.
Next, add the following line to the beginning of onLeaveReviewTapped():
preferences.setUserRatedApp(true)
When the user taps Leave Review, you’ll store their choice.
Similiarly, add the following lines to beginning of onRateLaterTapped:
preferences.setUserChosenRateLater(true)
preferences.setRateLater(getLaterTime())
Note the second line will throw an error as you haven’t defined getLaterTime() yet. This code specifies that the user chose to rate the app later and stores when to prompt them again.
Next, implement cancel and getRateLaterTime() as follows:
private fun getLaterTime(): Long {
return System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14)
}
override fun onCancel(dialog: DialogInterface) {
preferences.setUserChosenRateLater(true)
preferences.setRateLater(getLaterTime())
super.onCancel(dialog)
}
Note: When importing the package for
TimeUnit, choosejava.util.concurrent.
In the code above, you use getLaterTime() to show the ratings dialog after fourteen days. If the user cancels the dialog, you treat it the same as if they chose to “rate later”. Alternatively, you could alter the logic of the cancel case or extract these two scenarios into a single function.
Build and run to make sure everything still works as before.
Implementing the in-app review manager
Now that you have almost everything in place regarding project setup and data, it’s time to build the in-app review manager. Create a new interface in the inappreview module called InAppReviewManager and add the following to the body:
fun startReview(activity: Activity)
fun isEligibleForReview(): Boolean
The code above abstracts two functions:
-
isEligibleForReviewwill check if the app state is ready to show the user an in-app review dialog, and -
startReviewwill start the review.
Next, add the implementation class for this interface, InAppReviewManagerImpl, and add:
class InAppReviewManagerImpl(
private val reviewManager: ReviewManager,
private val inAppReviewPreferences: InAppReviewPreferences
): InAppReviewManager {
companion object {
private const val KEY_REVIEW = "reviewFlow"
}
private var reviewInfo: ReviewInfo? = null
init {
if (isEligibleForReview()) {
reviewManager.requestReviewFlow().addOnCompleteListener {
if (it.isComplete && it.isSuccessful) {
this.reviewInfo = it.result
}
}
}
}
}
In the code above, you add a constructor to InAppReviewManagerImpl that takes three parameters that are instances of Context, ReviewManager and InAppReviewPreferences. You also add a variable to store an instance of ReviewInfo. You first check if it’s OK to ask for a review, and if it is, you store information about the review in reviewInfo.
Next, add the following code to the same class:
override fun isEligibleForReview(): Boolean {
// 1
return (!inAppReviewPreferences.hasUserRatedApp() &&
!inAppReviewPreferences.hasUserChosenRateLater()
// 2
|| (inAppReviewPreferences.hasUserChosenRateLater() && enoughTimePassed()))
}
// 3
private fun enoughTimePassed(): Boolean {
val rateLaterTimeStamp = inAppReviewPreferences.getRateLaterTime()
return abs(rateLaterTimeStamp - System.currentTimeMillis()) >= TimeUnit.DAYS.toMillis(14)
}
Note: When resolving the import for
abs, choosekotlin.math.
In the code above, you check if:
- The user hasn’t given a rating yet and they haven’t chosen to rate later.
- They’ve previously chosen to rate later, and enough time has passed since then to prompt them again.
If these conditions are met, your app can ask the user for a review.
- To check if enough time has passed, you calculate if the difference between the rate later timestamp and the current day is at least two weeks.
Finally, add the following methods to start the review flow:
override fun startReview(activity: Activity) {
val myReviewInfo = reviewInfo
if (myReviewInfo != null) {
reviewManager.launchReviewFlow(activity, myReviewInfo)
.addOnCompleteListener { reviewFlow ->
onReviewFlowLaunchCompleted(reviewFlow)
}
}
}
private fun onReviewFlowLaunchCompleted(reviewFlow: Task<Void>) {
if (reviewFlow.isSuccessful) {
logSuccess()
}
}
private fun logSuccess() {
if (BuildConfig.DEBUG) {
Log.d(KEY_REVIEW, "Review complete!")
}
}
To start the review, you call reviewManager to launch reviewFlow and pass in which activity you want the flow to be bound to and which information you want to start the flow with. Then you add CompleteListener and pass the reviewFlow task to onReviewFlowLaunchCompleted. If the flow was successful, and you’re in a debug build, you log the successful status.
Note that you don’t need to do anything with the task or the review info. The manager takes care of everything for you and will launch the flow and show the dialog. The last step is to connect the manager to your UI and the rest of the app.
Connecting the manager to the UI
Finally, you need to connect the manager to the UI. Create an interface named InAppReviewView in the inappreview module. You’ll implement this interface in places you want to expose and use the in-app review feature. Add the following function to this interface:
fun showReviewFlow()
Next, open InAppReviewPromptDialog.kt and add the following code to the constructor:
private val inAppReviewManager: InAppReviewManager
Then replace the TODO comment in onLeaveReviewTapped() with the following to start the on review flow when the user taps the review option in the dialog:
inAppReviewManager.startReview(requireActivity())
Open PodcastActivity.kt and add the manager by injection, just as you did above. The manager holds the information to check if the app is eligible to show your review dialog, which is why you need it there. Add the following code to the end of onCreate() to set up the view in the ViewModel:
podcastViewModel.setInAppReviewView(this)
You’ll get a compilation error since setInAppReviewView() doesn’t exist yet in the podcast ViewModel. Click the error prompt in Android Studio and choose create member function to automatically create the corresponding function in the ViewModel.
Next, in PodcastActivity.kt, implement the InAppReviewView interface, so the class signature now looks like this:
class PodcastActivity :
AppCompatActivity(),
PodcastListAdapterListener,
OnPodcastDetailsListener,
InAppReviewView {
Then implement showReviewFlow():
override fun showReviewFlow() {
}
Now, add the following code to the PodcastViewModel to add InAppReviewView:
private lateinit var inAppReview: InAppReviewView
And update setInAppReviewView():
fun setInAppReviewView(podcastActivity: PodcastActivity) {
this.inAppReview = podcastActivity
}
This sets up the InAppReviewView, letting you tell PodcastActivity to show your review prompt dialog. Add the following function to PocastActivity:
private fun checkIfNeedsReviewPrompt() {
val value = podcastListAdapter.itemCount
if (value > 3) {
showReviewFlow()
}
}
The code above checks if there are three or more subscribed podcasts in the podcast list on the screen. You could choose some other criteria for when to show a review prompt, but for this example, assume that if the user has subscribed to three podcasts, they’ve been using the app enough to have some impression of it.
Next, you need access to instances of InAppReviewManager and InAppReviewPreferences. First, add the following field declarations in PodcastActivity as follows:
private lateinit var preferences: InAppReviewPreferences
private lateinit var inAppReviewManager: InAppReviewManager
You also need to initialize the field declarations above. Add:
private fun setupReviewManager() {
val sharedPreferences = getSharedPreferences(InAppReviewPreferencesImpl.KEY_IN_APP_REVIEW_PREFERENCES, Context.MODE_PRIVATE)
preferences = InAppReviewPreferencesImpl(sharedPreferences)
inAppReviewManager = InAppReviewManagerImpl(
ReviewManagerFactory.create(this),
preferences
)
}
In the code above, you create new instances of SharedPreferences and ReviewManager and then use them to create an instance of InAppReviewPreferencesImpl.
Inside onCreate() right below updateControls, add an invocation to setupReviewManager.
Now that you have instances of InAppReviewPreferences and InAppReviewManager, you can create an instance of InAppReviewPromptDialog. Add the following code to showReviewFlow():
if(inAppReviewManager.isEligibleForReview()) {
val dialog = InAppReviewPromptDialog(preferences, inAppReviewManager)
dialog.show(this.supportFragmentManager, null)
}
In the code above, you check if the app is eligible for review. If it is, you create a dialog and show it.
Call setupReviewManager inside onCreate between updateControls() and setupPodcastListView().
Finally, add a call to checkIfNeedsReviewPrompt() inside showSubscribedPodcasts right after setting the adapter data.
Build and run. Subscribe to four of your favorite podcasts. As soon as you add the fourth podcast, you’ll see a review dialog as shown below:
Testing the in-app review feature
In-App Review is tied to the Play Core API environment, so you need to do a few things to make it testable and make sure it works. It requires live data from the Play Store or the app you want to publish.
To test In-App Review, you’ll need a few different devices or emulators and some fresh Google Play accounts that haven’t rated the app yet. Upload your APK for your app with the feature to one of the testing tracks. Fortunately, if you’ve been following along with this book so far, you’re already set up with this!
Testing limitations
Currently, you can only test In-App Review with builds from the Play Store. However, you can test your in-app review integration using an internal testing track or internal app sharing. For rapid iteration, internal app sharing is the way to go. However, note that you can’t submit the reviews to the Play Store when using when this method. For that, you need to use the internal test track instead.
With the internal test track, you need to use Google Play accounts that haven’t rated the app yet, and you can’t request the IAR dialog more than a certain number of times. The actual number depends on some quota set by Google, and it may vary. This quota doesn’t apply to the internal app sharing, however.
Also, even if your code works and you get a successful review flow, this isn’t a guarantee the user will see the dialog since you may have already hit the quota for the number of allowed requests.
Once you’ve connected your devices and users as internal testers and download the app through the testing track, you can build and run the app in Android Studio to deploy new versions of the app to the device. However, you need to set up the testing initially.
Finally, keep in mind the points highlighted earlier in the chapter. It’s important to know when and how to ask users for a review. If you can use moments of accomplishment or some other milestone that catches the user at a positive time, they may be more likely to take the time to review your app.
Once you have your app and fresh test user accounts set up, test the in-app review feature. Congratulations, and good luck!
Key points
As Rob Napier (@cocoaphony) famously said,
When thinking about “good enough to ship: “Works 80%” generally doesn’t mean works 80% for everyone, so they review you with four stars instead of five. Works 80% usually means “works fine for 80% and is completely broken for 20%, and that 20% write all the reviews.”
Remember, users generally don’t bother to rate your app until they have a problem. At that point, they’ll likely give you a lower rating. Don’t just chase after ratings, though. Address the actual problem.
- It’s incredibly helpful to engage your users directly to provide positive reviews and intercept users with broken experiences with support.
- You can prompt your users for positive feedback in-app and intercept negative feedback by directly opening a JIRA ticket, bringing your user’s frustrations to the developers.
- Google Play provides a dashboard where you can not only monitor your reviews but also respond to them directly.
- Android provides an in-app review API to automate the process of prompting users for reviews.
- Be prudent with the strategies for when to ask for a review.
- Testing the in-app review feature can be hard to set up and a bit tedious as you’ll need fresh user accounts, and the process is tied to Google Play.
Where to go from here?
Check out the Android In-App Review video course on raywenderlich.com at https://www.raywenderlich.com/20065814-android-in-app-review/lessons/1
Additionally, suppose you want to take things a step further, like the author did for Octo, and you want to integrate feedback from your users directly into your ticketing system. In that case, you’ll want to reference the API for whatever system you use. For JIRA and other Atlassian apps, you can find the API documentation here: https://developer.atlassian.com
To learn more about testing in-app reviews, see the official documentation here: https://developer.android.com/guide/playcore/in-app-review/test
To learn more about creating Android library modules, see the official documentation here: https://developer.android.com/studio/projects/android-library