How to ask users nicely to review your Android app using Kotlin

How to ask users nicely to review your Android app using Kotlin

A very usual method that developers use to ask their users for an app review is to show a pop-up dialog after a couple of starts of their app.

The problem with that is users will ignore it easily because you interrupt their experience.

Not the best method to ask for a review…

To fix this problem, we will implement this thing inside in a RecyclerView.

In this tutorial, we are using the library Amplify, which is heavily inspired by Circa’s article The right way to ask users to review your app.

First, we have to add the library to our dependencies inside the build.gradle file

dependencies {
//Other dependencies...
implementation 'com.github.stkent:amplify:2.2.1'
}
view raw build.gradle hosted with ❤ by GitHub

In colors.xml file, inside the folder values, we’re adding the background and text color we want our rate view to have.

<color name="promptBackgroundColor">#bc214b</color>
<color name="promptTextColor">#FFFFFF</color>
view raw colors.xml hosted with ❤ by GitHub

Now, we need to add our RecyclerView row layout which contains the view, by making a new XML file under the layout folder named review_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.github.stkent.amplify.prompt.DefaultLayoutPromptView
android:id="@+id/prompt_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
view raw review_row.xml hosted with ❤ by GitHub

Let’s go now and set up how frequent we’re going to ask our users to review our app by adding in our Application() file (or MultiDexApplication()) the following code:

class Application: Application() {
override fun onCreate() {
super.onCreate()
Amplify.initSharedInstance(this)
.setPositiveFeedbackCollectors(GooglePlayStoreFeedbackCollector())
.setCriticalFeedbackCollectors(object : BaseEmailFeedbackCollector("feedback@mydomain.com") {
override fun getSubjectLine(iApp: IApp, iEnvironment: IEnvironment, iDevice: IDevice): String =
"Feedback for the Social App"
override fun getBody(iApp: IApp, iEnvironment: IEnvironment, iDevice: IDevice): String =
""
})
.addEnvironmentBasedRule(GooglePlayStoreRule()) // Prompt never shown if Google Play Store not installed.
.setInstallTimeCooldownDays(5) // Prompt not shown within 5 days of initial install.
.setLastUpdateTimeCooldownDays(3) // Prompt not shown within 3 days of most recent update.
.setLastCrashTimeCooldownDays(7) // Prompt not shown within one week of most recent crash.
.addTotalEventCountRule(
PromptInteractionEvent.USER_GAVE_POSITIVE_FEEDBACK,
MaximumCountRule(1)
) // Never ask the user for feedback again if they already responded positively.
.addTotalEventCountRule(
PromptInteractionEvent.USER_GAVE_CRITICAL_FEEDBACK,
MaximumCountRule(1)
) // Never ask the user for feedback again if they already responded critically.
.addTotalEventCountRule(
PromptInteractionEvent.USER_DECLINED_FEEDBACK,
MaximumCountRule(1)
).setAlwaysShow(BuildConfig.DEBUG)
}
}
view raw Application.kt hosted with ❤ by GitHub

If you don’t have any Application() file on your project, create a new Kotlin file and paste the code above. After that, go your AndroidManifest.xml and add android:name=".Application" inside the tag <application> like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.johncodeos.asktorate">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:name=".Application"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
view raw AndroidManifest.xml hosted with ❤ by GitHub

After we finished with all that, let’s add it in our RecyclerView.

Go to your RecyclerView Adapter, in our example it’s called Social_RVAdapter, and add the following code:

class Social_RVAdapter(private val socialCells: List<SocialModel>): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class PostViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
class RateViewHolder(itemView: View): RecyclerView.ViewHolder(itemView)
private lateinit var mContext: Context
private var RATE_VIEW = 0
private var POST_VIEW = 1
private var RATE_VIEW_POSITION = 1
lateinit var promptView: DefaultLayoutPromptView
lateinit var basePromptViewConfig: BasePromptViewConfig
lateinit var defaultLayoutPromptViewConfig: DefaultLayoutPromptViewConfig
override fun getItemViewType(position: Int): Int {
return if (position == RATE_VIEW_POSITION) {
RATE_VIEW
}else{
POST_VIEW
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
mContext = parent.context
if (viewType == RATE_VIEW) {
val rateView = LayoutInflater.from(parent.context).inflate(R.layout.review_row, parent, false)
val rateViewHolder = RateViewHolder(rateView)
return rateViewHolder
}else if (viewType == POST_VIEW) {
val postView = LayoutInflater.from(parent.context).inflate(R.layout.post_row, parent, false)
val postViewHolder = PostViewHolder(postView)
return postViewHolder
}
return null!!
}
override fun getItemCount(): Int {
//We adding 1 more row in the size of the array for our Rate View
return socialCells.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder.itemViewType == RATE_VIEW) {
val rateViewHolder = holder as RateViewHolder
promptView = rateViewHolder.itemView.prompt_view
setupPromptViewConfig()
setupPromptViewColors(R.color.promptBackgroundColor, R.color.promptTextColor)
promptView.applyBaseConfig(basePromptViewConfig)
promptView.applyConfig(defaultLayoutPromptViewConfig)
Amplify.getSharedInstance().promptIfReady(promptView)
}else if (holder.itemViewType == POST_VIEW) {
val currentPosition: Int
val postViewHolder = holder as PostViewHolder
if (position > RATE_VIEW_POSITION) {
currentPosition = position - 1
}else {
currentPosition = position
}
postViewHolder.itemView.userProfilePic_imageview.setImageResource(socialCells[currentPosition].userProfilePic)
postViewHolder.itemView.userName_label.text = socialCells[currentPosition].userName
postViewHolder.itemView.userDate_label.text = socialCells[currentPosition].userDate
postViewHolder.itemView.userDesc_textview.text = socialCells[currentPosition].userDesc
postViewHolder.itemView.userPhoto_imageview.setImageResource(socialCells[currentPosition].userPhoto)
}
}
private fun setupPromptViewConfig() {
basePromptViewConfig = BasePromptViewConfig.Builder()
.setUserOpinionQuestionTitle("Do you like Social App?")
.setUserOpinionQuestionPositiveButtonLabel("Yeah!")
.setUserOpinionQuestionNegativeButtonLabel("Nah")
.setPositiveFeedbackQuestionTitle("Awesome! Would you like to rate it?")
.setPositiveFeedbackQuestionSubtitle("It helps a lot 🙂")
.setPositiveFeedbackQuestionPositiveButtonLabel("Sure!")
.setPositiveFeedbackQuestionNegativeButtonLabel("Not right now")
.setCriticalFeedbackQuestionTitle("🙁 Would you like to send feedback?")
.setCriticalFeedbackQuestionPositiveButtonLabel("Sure!")
.setCriticalFeedbackQuestionNegativeButtonLabel("Not right now")
.setThanksTitle("Thank you!!!")
.setThanksDisplayTimeMs(2000)
.build()
}
private fun setupPromptViewColors(backgroundColor: Int, textcolor: Int) {
defaultLayoutPromptViewConfig = DefaultLayoutPromptViewConfig.Builder()
.setForegroundColor(ContextCompat.getColor(mContext, textcolor))
.setBackgroundColor(ContextCompat.getColor(mContext, backgroundColor))
.setTitleTextColor(ContextCompat.getColor(mContext, textcolor))
.setSubtitleTextColor(ContextCompat.getColor(mContext, textcolor))
.setPositiveButtonTextColor(ContextCompat.getColor(mContext, textcolor))
.setPositiveButtonBackgroundColor(ContextCompat.getColor(mContext, backgroundColor))
.setPositiveButtonBorderColor(ContextCompat.getColor(mContext, textcolor))
.setNegativeButtonTextColor(ContextCompat.getColor(mContext, textcolor))
.setNegativeButtonBackgroundColor(ContextCompat.getColor(mContext, backgroundColor))
.setNegativeButtonBorderColor(ContextCompat.getColor(mContext, textcolor))
.build()
}
}
view raw Social_RVAdapter.kt hosted with ❤ by GitHub

Here’s what we’re doing:

  • Create a ViewHolder for our RateView as we have for our Posts (called PostViewHolder) (Line 4 and 6)
  • Give a number that we’ll use in the getItemViewType later to know in which row will return our Rate or Post view (Line 9 and 10)
  • Give the position we wanna show the RateView in the RecyclerView (Line 12)
  • The getItemViewType function will return the RateView in our position that we put in RATE_VIEW_POSITION before (Lines 19-25)
  • Set up the layouts for the Rate View and Post View (Lines 30-38)
  • Add one more row to include our RateView in our array with our posts (Lines 47)
  • Set up our RateView with our preferable prompt text and the colors (Lines 51-58)
  • Calculate the position so we don’t skip any Post because from the row that RateView is using (Lines 60-66)

That’s it!! 🎉🎉

You can find the final project here

If you have any questions feel free to DM me on Twitter @johncodeos or leave a comment below!

Leave a Reply

avatar
  Subscribe  
Notify of

Stay Connected

Newsletter

Subcribe for weekly emails! Get my posts of the week right in your inbox!