Android app ratings and reviews: we love them, and we fear them. At times, it can feel that users only bother with them to complain. Sure, we like to hear about the issues in our apps so we can improve them, but a little love would be nice too!
There is a simple trick to increase your Android app ratings, but it needs to be deployed with care.
Is your app ready for higher ratings?
Overall, the foremost requirement to get higher ratings is to have a good app. If your app doesn’t do what users expect it to do, improve your app rather than obsess about your ratings.
So, before reading on, ask yourself: are you proud of your app?
Why don’t users tell you they love your app?
From your analytics (No analytics? Check out The analytics checklist: what to measure and look for ), you know your app has 1,000 weekly active users. You know you have 2,000 users who have been using the app for more than 6 months. Yet, you have a small number of ratings (eg 80 ratings for 10, 000 downloads), and the average is a bit low (eg 3.5).
Step back. When was the last time you rated an app? Was it because the app had repeatedly crashed on you? Was it because you thought “I love this app so much, I want to rate it”? Was it because the app prompted you for a rating, after a positive experience in the app?
How to increase your app ratings and reviews
Most users rate negatively when the app annoys them. A number of users rate positively when the app politely asks them for feedback at the right moment.
The right moment is very important. It needs to be after a user has completed a “positive event”. A “positive event” depends on the app of course. For example, it could be making a booking, emptying a busy inbox, completing a level in a game, or reading through a whole article.
Generally, I aim for 5 positive events, and I only ever prompt the user once. Based on several apps where I have implemented this, about 8-10% of prompted users tap on the button to get to the store, but only half of those actually leave a rating, meaning 4-5% of prompted users leave a rating (and about a fifth leave a review as well). The majority of those users leave a 5 stars rating, and the rest leave a 4 stars rating. I have seen apps with a rating of 3.2 going up a full point to 4.2 in about 5 weeks, without any other change in the app.
Show me the code
This code tutorial assumes you have an existing app project. Let’s start with creating a new subpackage for this feature, which we will call appratings.
Firstly, the app needs to track the positive events and whether or not the dialog prompt has been displayed, so Shared Preferences are perfect for this.
So let’s create a new class AddUsageHelper.java in appratings subpackage, and add the code as below.
Note: the code assumes your app is an instance of MyApp.java and you have a method getAppContext() in it. Replace as appropriate to access your SharedPrefs object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package net.cogitas.codetutorials.appratingsprompt.appratings; import android.content.Context; import android.content.SharedPreferences; import net.cogitas.codetutorials.appratingsprompt.MyApp; public class AppUsageHelper { private static final String NAME = "MySharedPrefs"; private final static String POSITIVE_EVENTS = "positive_events"; private final static int POSITIVE_EVENTS_SHOW_PROMPT = 5; private final static String APP_RATINGS_PROMPT_SHOWN = "app_ratings_prompt_shown"; public static void incrementPositiveEventsCount() { setInt(POSITIVE_EVENTS, getSharedPrefs().getInt(POSITIVE_EVENTS, 0) + 1); } public static void setAppRatingsPromptAsShown() { setBool(APP_RATINGS_PROMPT_SHOWN, true); } public static boolean showAppRatingsPrompt() { return !getBool(APP_RATINGS_PROMPT_SHOWN) && getInt(POSITIVE_EVENTS, 0) >= POSITIVE_EVENTS_SHOW_PROMPT; } /** * @param key * @return {@code defaultValue} if not found */ private static int getInt(String key,int defaultValue) { return getSharedPrefs().getInt(key, defaultValue); } private static void setInt(String key, int value) { SharedPreferences.Editor editor = getEditor(); editor.putInt(key, value); editor.commit(); } /** * @param key * @return false if not found */ private static boolean getBool(String key) { return getSharedPrefs().getBoolean(key, false); } private static void setBool(String key, boolean value) { SharedPreferences.Editor editor = getEditor(); editor.putBoolean(key, value); editor.commit(); } private static SharedPreferences.Editor getEditor() { return getSharedPrefs().edit(); } private static SharedPreferences getSharedPrefs() { return MyApp.getAppContext().getSharedPreferences(NAME, Context.MODE_PRIVATE); } } |
Secondly, we need code to show the dialog prompting the user. There are two variants for it. The simple variant asks the user to rate the app. The more complex variant gives options to email or rate the app, depending on whether the user likes the app or not.
Generally, I recommend implementing the simpler dialog, because you should only implement this when you know your app is stable and does what your users expect. If you app still gets a lot of negative reviews and you have large bugs you haven’t fixed yet, I recommend doing an update fixing the bugs first, then prompting only in the following update.
However, if your app is still expected to change, the more complex dialog is a good way to get some more personal feedback from users. Additionally, if you have a beta program you are trying to recruit for, users who email you and whose problem you can solve are generally quite up for joining your beta.
Either way, create a class AppRatingsUIHelper.java in appratings subpackage. Here is the version for the simple dialog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
package net.cogitas.codetutorials.appratingsprompt.appratings; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.support.v7.app.AlertDialog; import net.cogitas.codetutorials.appratingsprompt.R; public class AppRatingsUIHelper { public static void showPromptIfRequired(final Activity activity) { Handler h = new Handler(); Runnable r = new Runnable() { @Override public void run() { if (!activity.isDestroyed() && !activity.isFinishing() && AppUsageHelper.showAppRatingsPrompt()) { showSimpleDialog(activity); } } }; // This is delayed to make sure the user has had time to take // in that the positive event has happened eg user has had time to // view the updated UI following the positive event h.postDelayed(r, 1500); } private static void showSimpleDialog(final Activity activity) { try { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.app_ratings_prompt_title) .setMessage(R.string.app_ratings_prompt_message) .setNegativeButton(R.string.app_ratings_prompt_rate_no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // TODO report prompt dismissed to analytics } }) .setPositiveButton(R.string.app_ratings_prompt_rate, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(activity.getResources().getString(R.string.app_ratings_prompt_rate_url))); activity.startActivity(intent); // TODO report prompt tapped to analytics } catch (Exception e) { // Usually, it's fine to let the app crash, but // the app can function without proceeding to the app uri // so a crash would not help the user // TODO report crash to analytics } } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { // TODO report prompt cancelled to analytics; } } ); builder.create().show(); AppUsageHelper.setAppRatingsPromptAsShown(); // TODO report prompt shown to analytics; } catch (Exception e) { // Usually, it's fine to let the app crash, but // the app can function without asking the user for a rating // so a crash would not help the user // TODO report crash to analytics } } } |
And the related strings, defined in strings.xml.
1 2 3 4 5 6 |
<!-- App ratings prompt --> <string name="app_ratings_prompt_title">Enjoy the app?</string> <string name="app_ratings_prompt_message">We\'d love for you to leave a review!</string> <string name="app_ratings_prompt_rate">Rate it</string> <string name="app_ratings_prompt_rate_no">Not now</string> <string name="app_ratings_prompt_rate_url">https://play.google.com/store/apps/details?id=your.app.id</string> |
Here’s a screenshot of what it looks like
Here is the version for the more complex dialog.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
package net.cogitas.codetutorials.appratingsprompt.appratings; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Handler; import android.support.v7.app.AlertDialog; import android.widget.Toast; import net.cogitas.codetutorials.appratingsprompt.R; public class AppRatingsUIHelper { public static void showPromptIfRequired(final Activity activity) { Handler h = new Handler(); Runnable r = new Runnable() { @Override public void run() { if (!activity.isDestroyed() && !activity.isFinishing() && AppUsageHelper.showAppRatingsPrompt()) { showComplexDialog(activity); } } }; // This is delayed to make sure the user has had time to take // in that the positive event has happened eg user has had time to // view the updated UI following the positive event h.postDelayed(r, 1500); } private static void showComplexDialog(final Activity activity) { try { CharSequence[] items = new CharSequence[4]; items[0] = activity.getResources().getString(R.string.app_ratings_prompt_rate); items[1] = activity.getResources().getString(R.string.app_ratings_prompt_problem); items[2] = activity.getResources().getString(R.string.app_ratings_prompt_idea); items[3] = activity.getResources().getString(R.string.app_ratings_prompt_close); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(R.string.app_ratings_prompt_title) .setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); switch (which) { case 0: try { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(activity.getResources().getString(R.string.app_ratings_prompt_rate_url))); activity.startActivity(intent); // TODO report prompt tapped to analytics } catch (Exception e) { // Usually, it's fine to let the app crash, but // the app can function without proceeding to the app uri // so a crash would not help the user // TODO report crash to analytics } break; case 1: sendEmail(activity, activity.getResources() .getString(R.string.app_ratings_prompt_problem_email)); // TODO report prompt problem email to analytics; break; case 2: sendEmail(activity, activity.getResources().getString(R.string.app_ratings_prompt_idea_email)); // TODO report prompt idea email to analytics; break; case 3: // TODO report prompt closed to analytics; break; } } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { dialog.dismiss(); // TODO report prompt cancelled to analytics; } }); builder.create().show(); AppUsageHelper.setAppRatingsPromptAsShown(); // TODO report prompt shown to analytics; } catch (Exception e) { // Usually, it's fine to let the app crash, but // the app can function without asking the user for a rating // so a crash would not help the user // TODO report crash to analytics } } private static void sendEmail(Activity activity, String subject) { try { Intent emailIntent = new Intent(Intent.ACTION_SEND); emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{activity.getResources().getString(R.string.app_ratings_prompt_support_email)}); emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject); emailIntent.setType("message/rfc822"); activity.startActivity(Intent.createChooser(emailIntent, activity.getResources().getString(R.string.app_ratings_prompt_email_chooser))); } catch (Exception e) { Toast.makeText(activity, activity.getResources().getString(R.string.app_ratings_prompt_noemailclient), Toast.LENGTH_SHORT).show(); } } } |
And the related strings, defined in strings.xml.
1 2 3 4 5 6 7 8 9 10 11 12 |
<!-- App ratings prompt --> <string name="app_ratings_prompt_title">How are we doing?</string> <string name="app_ratings_prompt_rate">I love it! (Rate it on Google Play)</string> <string name="app_ratings_prompt_problem">I have a problem (Send an email)</string> <string name="app_ratings_prompt_idea">I have an idea (Send an email)</string> <string name="app_ratings_prompt_close">Close</string> <string name="app_ratings_prompt_noemailclient">No email app available</string> <string name="app_ratings_prompt_problem_email">YourAppName: I have a problem</string> <string name="app_ratings_prompt_idea_email">YourAppName: I have an idea</string> <string name="app_ratings_prompt_rate_url">https://play.google.com/store/apps/details?id=your.app.id</string> <string name="app_ratings_prompt_support_email">support@email.com</string> <string name="app_ratings_prompt_email_chooser">Send email</string> |
Here’s a screenshot of what it looks like.
Lastly, we need to wire it all up. Wherever a positive event has happened, add a call to AppUsageHelper.incrementPositiveEventsCount() followed by a call to AppUsageUIHelper.showPromptIfRequired(getActivity()) (replace getActivity() with this if calling from an Activity and not a Fragment).
What next?
Think carefully about your app’s “positive events”, and whether prompting a user for ratings will fit in the user flow without spoiling that positive event.

Mobile app developer with 12 years experience. Started with the Android SDK in 2010 and switched to Flutter in 2017. Past apps range from start ups to large tech (Google) and non tech (British Airways) companies, in various sectors (transport, commercial operations, e-commerce).