How to implement Rate It feature in Android App
Asked Answered
C

18

107

I am developing an Android App. In which everything is working right. My app is ready to launch. But there I need to implement one more feature. I need to display a popup that contains

Rate It and Remind me later

Here if any user rates the app in the market then the popup won't be disappeared. I have searched in Google and found one link. With this, I understand that it's not possible to know. So I need a suggestion for this.

Has anybody faced this situation before? If so, is there any solution or any alternative for this?

Crumhorn answered 25/1, 2013 at 2:51 Comment(6)
So are you asking for just the Rate it/remind me later or are you asking for how to know if a specific user has rated a Android App?Martinmartina
i have implemented the popup. but how to know if a user rate the app or notCrumhorn
-1 I do not see the difference between this question and the one in the link.Martinmartina
@wtsang02, May be its true. But see the question. its asked on Mar 15 2011. so almost 20 months over. I think some one has solution or alternative for my requirement. that's y i posted here.Crumhorn
You can use library github.com/Vorlonsoft/AndroidRate (implementation 'com.vorlonsoft:androidrate:1.0.3')Rohde
Best way is google's in-app review - https://mcmap.net/q/203024/-how-to-implement-rate-it-feature-in-android-appOosperm
T
194

I implemented this a while back, to some extent. It is impossible to know whether or not a user has rated an app, to prevent ratings from becoming a currency (some developers might add an option like "Rate this app and get so and so in the app for free").

The class I wrote provides three buttons, and configures the dialog so that it is only shown after the app has been launched n times (users have a higher chance of rating the app if they've used it a bit before. Most of them are unlikely to even know what it does on the first run):

public class AppRater {
    private final static String APP_TITLE = "App Name";// App Name
    private final static String APP_PNAME = "com.example.name";// Package Name

    private final static int DAYS_UNTIL_PROMPT = 3;//Min number of days
    private final static int LAUNCHES_UNTIL_PROMPT = 3;//Min number of launches

    public static void app_launched(Context mContext) {
        SharedPreferences prefs = mContext.getSharedPreferences("apprater", 0);
        if (prefs.getBoolean("dontshowagain", false)) { return ; }

        SharedPreferences.Editor editor = prefs.edit();

        // Increment launch counter
        long launch_count = prefs.getLong("launch_count", 0) + 1;
        editor.putLong("launch_count", launch_count);

        // Get date of first launch
        Long date_firstLaunch = prefs.getLong("date_firstlaunch", 0);
        if (date_firstLaunch == 0) {
            date_firstLaunch = System.currentTimeMillis();
            editor.putLong("date_firstlaunch", date_firstLaunch);
        }

        // Wait at least n days before opening
        if (launch_count >= LAUNCHES_UNTIL_PROMPT) {
            if (System.currentTimeMillis() >= date_firstLaunch + 
                    (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)) {
                showRateDialog(mContext, editor);
            }
        }

        editor.commit();
    }   

    public static void showRateDialog(final Context mContext, final SharedPreferences.Editor editor) {
        final Dialog dialog = new Dialog(mContext);
        dialog.setTitle("Rate " + APP_TITLE);

        LinearLayout ll = new LinearLayout(mContext);
        ll.setOrientation(LinearLayout.VERTICAL);

        TextView tv = new TextView(mContext);
        tv.setText("If you enjoy using " + APP_TITLE + ", please take a moment to rate it. Thanks for your support!");
        tv.setWidth(240);
        tv.setPadding(4, 0, 4, 10);
        ll.addView(tv);

        Button b1 = new Button(mContext);
        b1.setText("Rate " + APP_TITLE);
        b1.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                mContext.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + APP_PNAME)));
                dialog.dismiss();
            }
        });        
        ll.addView(b1);

        Button b2 = new Button(mContext);
        b2.setText("Remind me later");
        b2.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
        ll.addView(b2);

        Button b3 = new Button(mContext);
        b3.setText("No, thanks");
        b3.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                if (editor != null) {
                    editor.putBoolean("dontshowagain", true);
                    editor.commit();
                }
                dialog.dismiss();
            }
        });
        ll.addView(b3);

        dialog.setContentView(ll);        
        dialog.show();        
    }
}

Integrating the class is as simple as adding:

AppRater.app_launched(this);

To your Activity. It only needs to be added to one Activity in the entire app.

Taler answered 25/1, 2013 at 3:6 Comment(13)
This doesnt support multiple users using the same device.Farland
@Farland Yes, but multiple users using the same device can be handled by only showing the apprater dialog after authentication and changing shared preference to include the google email address in the key.M16
Hi, I have just one question. Why did you make everything static? Thanks Raghav!Luing
Great class, really helpful!! you could set the ShowRateDialog() method to private to improve performance!Concert
@RuchirBaronia, Look at this AppRater.app_launched(this); the function app_launched(this) is only called by writing the name of the class and just call the public static functions and field members ... it is the easiest way and also better in performance than normal class, otherwise you can do it in a simple class, and then create Object of that class and then call it via that Object ... thats it. :)Incorporate
Hi, I am trying your above code. I have put AppRater.app_launched(this); inside my onCreate() of MainActivity. I have also changed the minimum number launches required to 2. But, I am not seeing the dialog after 2 app launch. Can You help me out? Thanks!Sundaysundberg
@Raghav Sood Does it show the dialog each time the user open the app after 3 days of the launch??Consolata
need to add this line editor.putLong("launch_count", 0); for restart the counter again other wise user dialog will appears again . For prevent this issue add this in "Remind me later" option.Norse
Better use the enum Context.MODE_PRIVATE - context.getSharedPreferences("apprater", Context.MODE_PRIVATE);Slambang
When calling mContext.startActivity, please catch ActivityNotFoundException and fall back to an URL like Uri.parse("play.google.com/store/apps/details?id=" + ctx.getPackageName()) for devices that don't have the Goolge Play Store installed (e.g. Chinese)Cointreau
Best way is now Google's In-app review - https://mcmap.net/q/203024/-how-to-implement-rate-it-feature-in-android-appOosperm
Rather use BuildConfig.APPLICATION_ID instead of the field APP_PNAME Palomino
I would also include in the button 1 listener the lines: if (editor != null) { editor.putBoolean("dontshowagain", true); editor.commit(); }Rosaceous
S
19

My one using DialogFragment:

public class RateItDialogFragment extends DialogFragment {
    private static final int LAUNCHES_UNTIL_PROMPT = 10;
    private static final int DAYS_UNTIL_PROMPT = 3;
    private static final int MILLIS_UNTIL_PROMPT = DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000;
    private static final String PREF_NAME = "APP_RATER";
    private static final String LAST_PROMPT = "LAST_PROMPT";
    private static final String LAUNCHES = "LAUNCHES";
    private static final String DISABLED = "DISABLED";

    public static void show(Context context, FragmentManager fragmentManager) {
        boolean shouldShow = false;
        SharedPreferences sharedPreferences = getSharedPreferences(context);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        long currentTime = System.currentTimeMillis();
        long lastPromptTime = sharedPreferences.getLong(LAST_PROMPT, 0);
        if (lastPromptTime == 0) {
            lastPromptTime = currentTime;
            editor.putLong(LAST_PROMPT, lastPromptTime);
        }

        if (!sharedPreferences.getBoolean(DISABLED, false)) {
            int launches = sharedPreferences.getInt(LAUNCHES, 0) + 1;
            if (launches > LAUNCHES_UNTIL_PROMPT) {
                if (currentTime > lastPromptTime + MILLIS_UNTIL_PROMPT) {
                    shouldShow = true;
                }
            }
            editor.putInt(LAUNCHES, launches);
        }

        if (shouldShow) {
            editor.putInt(LAUNCHES, 0).putLong(LAST_PROMPT, System.currentTimeMillis()).commit();
            new RateItDialogFragment().show(fragmentManager, null);
        } else {
            editor.commit();
        }
    }

    private static SharedPreferences getSharedPreferences(Context context) {
        return context.getSharedPreferences(PREF_NAME, 0);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.rate_title)
                .setMessage(R.string.rate_message)
                .setPositiveButton(R.string.rate_positive, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getActivity().getPackageName())));
                        getSharedPreferences(getActivity()).edit().putBoolean(DISABLED, true).commit();
                        dismiss();
                    }
                })
                .setNeutralButton(R.string.rate_remind_later, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dismiss();
                    }
                })
                .setNegativeButton(R.string.rate_never, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        getSharedPreferences(getActivity()).edit().putBoolean(DISABLED, true).commit();
                        dismiss();
                    }
                }).create();
    }
}

Then use it in onCreate() of your main FragmentActivity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...

    RateItDialogFragment.show(this, getFragmentManager());

}
Sesame answered 29/5, 2014 at 10:12 Comment(3)
Good one! I would just put the editor.commit() before showing the DialogFragment just in case something goes wrong when loading the Dialog.Quanta
Note:It can lead to a memory leak if you use to apply to save shared preference. If you notice carefully in setPositiveButton and setNegativeButton, it is writing to shared preferences using commit but if you use apply which is async and will keep the reference to the activity until it compleats and right after that it is calling dismiss. Dismiss will try to destroy the fragment, but it can't because the activity is held/used by the shared preference apply process.(I wore this because AndroidStudio will prompt the user to change commit to apply, don't do it unless you use some other logic)Suttles
@Sesame How to modify code to be able to use in Activity and without fragment?Boogie
A
18

Java & Kotlin solution (In-app review API by Google in 2020):

enter image description here

First, in your build.gradle(app) file, add following dependencies (full setup here)

dependencies {
    // This dependency is downloaded from the Google’s Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:core:1.8.0'
}

Add this method to your Activity:

void askRatings() {
    ReviewManager manager = ReviewManagerFactory.create(this);
    Task<ReviewInfo> request = manager.requestReviewFlow();
    request.addOnCompleteListener(task -> {
        if (task.isSuccessful()) {
            // We can get the ReviewInfo object
            ReviewInfo reviewInfo = task.getResult();
            Task<Void> flow = manager.launchReviewFlow(this, reviewInfo);
            flow.addOnCompleteListener(task2 -> {
                // The flow has finished. The API does not indicate whether the user
                // reviewed or not, or even whether the review dialog was shown. Thus, no
                // matter the result, we continue our app flow.
            });
        } else {
            // There was some problem, continue regardless of the result.
        }
    });
}

Call it like any other method:

askRatings();

Kotlin code can be found here

Aldose answered 13/8, 2020 at 22:29 Comment(7)
But this review dialog showing only once.How we can show this in-app review dialog more than once in app?Pirali
@Pirali Haven't tested that yet, but I think it must be done on purpose by Google to prevent the dialog from opening more than once otherwise it will be overwhelming for the users if developers keep on asking for review.Aldose
Hi I have found the solution, we can test app by using internal App Sharing of google, where we can upload an apk to test In-app rating dialog. After uploading we can test app it will show In-app rating dialog every time according to your condition which you have done in app.Pirali
@AG-Developer Didn't check it yet, but I'm sure it must be API >= 21Aldose
One drawback of this dialog is that it is modal less dialog, if user unfortunately clicks on other area of screen it will leave the universe and entire efforts will be in waterOvercloud
@ShriramPanchal You can create a feature request for it.Aldose
As always google takes something and over complicates it and documentation isn't complete. Also the use case has only one use but others may want to know why request failed and act upon it. API is worthless junk.Enlistment
S
8

I think what you are trying to do is probably counter-productive.

Making it easy for people to rate apps is generally a good idea, as most people who bother do so because they like the app. It is rumoured that the number of ratings affects your market rating (although I see little evidence of this). Hassling users into rating - through nag screens - is likely to cause people to clear the nag through leaving a bad rating.

Adding the capability to directly rate an app has caused a slight decrease in the numerical ratings for my free version, and a slight increase in my paid app. For the free app, my 4 star ratings increased more than my 5 star ratings, as people who thought my app was good but not great started to rate it as well. Change was about -0.2. For the paid, change was about +0.1. I should remove it from the free version, except I like getting lots of comments.

I put my rating button into a settings (preference) screen, where it does not affect normal operation. It still increased my rating rate by a factor of 4 or 5. I have no doubt that if I tried nagging my users into making a rating, I would get lots of users giving me bad ratings as a protest.

Stir answered 25/1, 2013 at 3:57 Comment(1)
100% true. The same thing happened with my free app too.Fourflusher
R
7

AndroidRate is a library to help you promote your android app by prompting users to rate the app after using it for a few days.

Module Gradle:

dependencies {
  implementation 'com.vorlonsoft:androidrate:1.0.8'
}

MainActivity.java:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  AppRate.with(this)
      .setStoreType(StoreType.GOOGLEPLAY) //default is GOOGLEPLAY (Google Play), other options are
                                          //           AMAZON (Amazon Appstore) and
                                          //           SAMSUNG (Samsung Galaxy Apps)
      .setInstallDays((byte) 0) // default 10, 0 means install day
      .setLaunchTimes((byte) 3) // default 10
      .setRemindInterval((byte) 2) // default 1
      .setRemindLaunchTimes((byte) 2) // default 1 (each launch)
      .setShowLaterButton(true) // default true
      .setDebug(false) // default false
      //Java 8+: .setOnClickButtonListener(which -> Log.d(MainActivity.class.getName(), Byte.toString(which)))
      .setOnClickButtonListener(new OnClickButtonListener() { // callback listener.
          @Override
          public void onClickButton(byte which) {
              Log.d(MainActivity.class.getName(), Byte.toString(which));
          }
      })
      .monitor();

  if (AppRate.with(this).getStoreType() == StoreType.GOOGLEPLAY) {
      //Check that Google Play is available
      if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) != ConnectionResult.SERVICE_MISSING) {
          // Show a dialog if meets conditions
          AppRate.showRateDialogIfMeetsConditions(this);
      }
  } else {
      // Show a dialog if meets conditions
      AppRate.showRateDialogIfMeetsConditions(this);
  }
}

The default conditions to show rate dialog is as below:

  1. App is launched more than 10 days later than installation. Change via AppRate#setInstallDays(byte).
  2. App is launched more than 10 times. Change via AppRate#setLaunchTimes(byte).
  3. App is launched more than 1 days after neutral button clicked. Change via AppRate#setRemindInterval(byte).
  4. App is launched X times and X % 1 = 0. Change via AppRate#setRemindLaunchTimes(byte).
  5. App shows neutral dialog (Remind me later) by default. Change via setShowLaterButton(boolean).
  6. To specify the callback when the button is pressed. The same value as the second argument of DialogInterface.OnClickListener#onClick will be passed in the argument of onClickButton.
  7. Setting AppRate#setDebug(boolean) will ensure that the rating request is shown each time the app is launched. This feature is only for development!.

Optional custom event requirements for showing dialog

You can add additional optional requirements for showing dialog. Each requirement can be added/referenced as a unique string. You can set a minimum count for each such event (for e.g. "action_performed" 3 times, "button_clicked" 5 times, etc.)

AppRate.with(this).setMinimumEventCount(String, short);
AppRate.with(this).incrementEventCount(String);
AppRate.with(this).setEventCountValue(String, short);

Clear show dialog flag

When you want to show the dialog again, call AppRate#clearAgreeShowDialog().

AppRate.with(this).clearAgreeShowDialog();

When the button presses on

call AppRate#showRateDialog(Activity).

AppRate.with(this).showRateDialog(this);

Set custom view

call AppRate#setView(View).

LayoutInflater inflater = (LayoutInflater)this.getSystemService(LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.custom_dialog, (ViewGroup)findViewById(R.id.layout_root));
AppRate.with(this).setView(view).monitor();

Specific theme

You can use a specific theme to inflate the dialog.

AppRate.with(this).setThemeResId(int);

Custom dialog

If you want to use your own dialog labels, override string xml resources on your application.

<resources>
    <string name="rate_dialog_title">Rate this app</string>
    <string name="rate_dialog_message">If you enjoy playing this app, would you mind taking a moment to rate it? It won\'t take more than a minute. Thanks for your support!</string>
    <string name="rate_dialog_ok">Rate It Now</string>
    <string name="rate_dialog_cancel">Remind Me Later</string>
    <string name="rate_dialog_no">No, Thanks</string>
</resources>

Check that Google Play is available

if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this) != ConnectionResult.SERVICE_MISSING) {

}
Rohde answered 25/11, 2017 at 13:56 Comment(0)
I
5

Make sure the below is implemented For in-app reviews:

implementation 'com.google.android.play:core:1.8.0'

OnCreate

public void RateApp(Context mContext) {
    try {
        ReviewManager manager = ReviewManagerFactory.create(mContext);
        manager.requestReviewFlow().addOnCompleteListener(new OnCompleteListener<ReviewInfo>() {
            @Override
            public void onComplete(@NonNull Task<ReviewInfo> task) {
                if(task.isSuccessful()){
                    ReviewInfo reviewInfo = task.getResult();
                    manager.launchReviewFlow((Activity) mContext, reviewInfo).addOnFailureListener(new OnFailureListener() {
                        @Override
                        public void onFailure(Exception e) {
                            Toast.makeText(mContext, "Rating Failed", Toast.LENGTH_SHORT).show();
                        }
                    }).addOnCompleteListener(new OnCompleteListener<Void>() {
                        @Override
                        public void onComplete(@NonNull Task<Void> task) {
                            Toast.makeText(mContext, "Review Completed, Thank You!", Toast.LENGTH_SHORT).show();
                        }
                    });
                }

            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(Exception e) {
                Toast.makeText(mContext, "In-App Request Failed", Toast.LENGTH_SHORT).show();
            }
        });
    } catch (ActivityNotFoundException e) {
        e.printStackTrace();
    }
}
Ike answered 28/8, 2020 at 9:40 Comment(0)
I
3

This solution is very similar to those presented above. The only difference is that you are going to be able to delay the prompt of the rating dialog per launches and days. If the remind me later button is pressed, then I will delay the pop up for 3 days and 10 launches. The same is done for those that selected to rate it, however the delays is longer (not to bother the user so soon in case that he has actually rated the app. This can be changed to not be shown again, then you will have to alter the code to your like). Hope it helps somebody!

public class AppRater {
    private final static String APP_TITLE = "your_app_name";
    private static String PACKAGE_NAME = "your_package_name";
    private static int DAYS_UNTIL_PROMPT = 5;
    private static int LAUNCHES_UNTIL_PROMPT = 10;
    private static long EXTRA_DAYS;
    private static long EXTRA_LAUCHES;
    private static SharedPreferences prefs;
    private static SharedPreferences.Editor editor;
    private static Activity activity;

    public static void app_launched(Activity activity1) {
        activity = activity1;

        Configs.sendScreenView("Avaliando App", activity);

        PACKAGE_NAME = activity.getPackageName();

        prefs = activity.getSharedPreferences("apprater", Context.MODE_PRIVATE);
        if (prefs.getBoolean("dontshowagain", false)) 
            return;

        editor = prefs.edit();

        EXTRA_DAYS = prefs.getLong("extra_days", 0);
        EXTRA_LAUCHES = prefs.getLong("extra_launches", 0);

        // Increment launch counter
        long launch_count = prefs.getLong("launch_count", 0) + 1;
        editor.putLong("launch_count", launch_count);

        // Get date of first launch
        Long date_firstLaunch = prefs.getLong("date_firstlaunch", 0);
        if (date_firstLaunch == 0) {
            date_firstLaunch = System.currentTimeMillis();
            editor.putLong("date_firstlaunch", date_firstLaunch);
        }

        // Wait at least n days before opening
        if (launch_count >= (LAUNCHES_UNTIL_PROMPT + EXTRA_LAUCHES))
            if (System.currentTimeMillis() >= date_firstLaunch + (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000) + EXTRA_DAYS)
                showRateDialog();

        editor.commit();
    }   

    public static void showRateDialog() {
        final Dialog dialog = new Dialog(activity);
        dialog.setTitle("Deseja avaliar o aplicativo " + APP_TITLE + "?");

        LinearLayout ll = new LinearLayout(activity);
        ll.setOrientation(LinearLayout.VERTICAL);
        ll.setPadding(5, 5, 5, 5);

        TextView tv = new TextView(activity);
        tv.setTextColor(activity.getResources().getColor(R.color.default_text));
        tv.setText("Ajude-nos a melhorar o aplicativo com sua avaliação no Google Play!");
        tv.setWidth(240);
        tv.setGravity(Gravity.CENTER);
        tv.setPadding(5, 5, 5, 5);
        ll.addView(tv);

        Button b1 = new Button(activity);
        b1.setTextColor(activity.getResources().getColor(R.color.default_text));
        b1.setBackground(activity.getResources().getDrawable(R.drawable.rounded_blue_box));
        b1.setTextColor(Color.WHITE);
        b1.setText("Avaliar aplicativo " + APP_TITLE + "!");
        b1.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Configs.sendHitEvents(Configs.APP_RATER, Configs.CATEGORIA_ANALYTICS, "Clique", "Avaliar", activity);

                activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + PACKAGE_NAME)));
                delayDays(60);
                delayLaunches(30);
                dialog.dismiss();
            }
        });        
        ll.addView(b1);
        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) b1.getLayoutParams();
        params.setMargins(5, 3, 5, 3);
        b1.setLayoutParams(params);

        Button b2 = new Button(activity);
        b2.setTextColor(activity.getResources().getColor(R.color.default_text));
        b2.setBackground(activity.getResources().getDrawable(R.drawable.rounded_blue_box));
        b2.setTextColor(Color.WHITE);
        b2.setText("Lembre-me mais tarde!");
        b2.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Configs.sendHitEvents(Configs.APP_RATER, Configs.CATEGORIA_ANALYTICS, "Clique", "Avaliar Mais Tarde", activity);
                delayDays(3);
                delayLaunches(10);
                dialog.dismiss();
            }
        });
        ll.addView(b2);
        params = (LinearLayout.LayoutParams) b2.getLayoutParams();
        params.setMargins(5, 3, 5, 3);
        b2.setLayoutParams(params);

        Button b3 = new Button(activity);
        b3.setTextColor(activity.getResources().getColor(R.color.default_text));
        b3.setBackground(activity.getResources().getDrawable(R.drawable.rounded_blue_box));
        b3.setTextColor(Color.WHITE);
        b3.setText("Não, obrigado!");
        b3.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                Configs.sendHitEvents(Configs.APP_RATER, Configs.CATEGORIA_ANALYTICS, "Clique", "Não Avaliar", activity);

                if (editor != null) {
                    editor.putBoolean("dontshowagain", true);
                    editor.commit();
                }
                dialog.dismiss();
            }
        });
        ll.addView(b3);
        params = (LinearLayout.LayoutParams) b3.getLayoutParams();
        params.setMargins(5, 3, 5, 0);
        b3.setLayoutParams(params);

        dialog.setContentView(ll);        
        dialog.show();        
    }

    private static void delayLaunches(int numberOfLaunches) {
        long extra_launches = prefs.getLong("extra_launches", 0) + numberOfLaunches;
        editor.putLong("extra_launches", extra_launches);
        editor.commit();
    }

    private static void delayDays(int numberOfDays) {
        Long extra_days = prefs.getLong("extra_days", 0) + (numberOfDays * 1000 * 60 * 60 * 24);
        editor.putLong("extra_days", extra_days);
        editor.commit();
    }
}

The Buttons have a specific color and background. The background is as shown in this xml file:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:padding="10dp"
    android:shape="rectangle" >

    <solid android:color="#2E78B9" />

    <corners
        android:bottomLeftRadius="6dp"
        android:bottomRightRadius="6dp"
        android:topLeftRadius="6dp"
        android:topRightRadius="6dp" />

</shape>

source: Android approach for "Rate my application"

Iorgo answered 22/4, 2015 at 15:5 Comment(3)
What is "Configs" it's not found when i try.Fauteuil
@Md.ImranChoudhury Sorry for the late reply. The configs is a private class of mine that I use for google analytics. You can just remove that statement without a problem!Iorgo
you should either link to original answer or give credit to him. https://mcmap.net/q/205181/-android-approach-for-quot-rate-my-application-quot-closedStowe
L
3

Use this library, It is simple and easy.. https://github.com/hotchemi/Android-Rate

by adding the dependency..

dependencies {
  compile 'com.github.hotchemi:android-rate:0.5.6'
}
Lemniscus answered 14/1, 2016 at 12:37 Comment(1)
You can use library github.com/Vorlonsoft/AndroidRate (implementation 'com.vorlonsoft:androidrate:1.0.3') . It's up to date.Rohde
C
3

As of August 2020, Google Play's In-App Review API is available and its straightforward implementation is correct as per this answer.

But if you wish add some display logic on top of it, use the Five-Star-Me library.

Set launch times and install days in the onCreate method of the MainActivity to configure the library.

  FiveStarMe.with(this)
      .setInstallDays(0) // default 10, 0 means install day.
      .setLaunchTimes(3) // default 10
      .setDebug(false) // default false
      .monitor();

Then place the below method call on any activity / fragment's onCreate / onViewCreated method to show the prompt whenever the conditions are met.

FiveStarMe.showRateDialogIfMeetsConditions(this); //Where *this* is the current activity.

enter image description here

Installation instructions:

You can download from jitpack.

Step 1: Add this to project (root) build.gradle.

allprojects {
  repositories {
    ...
    maven { url 'https://jitpack.io' }
  }
}

Step 2: Add the following dependency to your module (app) level build.gradle.

dependencies {
  implementation 'com.github.numerative:Five-Star-Me:2.0.0'
}

Chrysoprase answered 14/9, 2020 at 5:45 Comment(4)
I followed your explanation and the one given by the library author (in github), but I coudn't get the InAppRating message. I initialized the Lib in MainActivity#onCreate method then I call it in fragement. is there any issue related to fragments ?Foot
You'll have to upload the apk to at least internal app sharing or internal testing track in order to see this prompt. developer.android.com/guide/playcore/in-app-review/testChrysoprase
Thank you @Michael it worked. i didn't pay attention to testing part where it's explained in the official documentation.Foot
Yeah doesn't work in Production or Debug mode for me. App is already in store so no need. Not sure why its not workingEnlistment
U
2

I'm using this easy solution. You can just add this library with gradle: https://github.com/fernandodev/easy-rating-dialog

compile 'com.github.fernandodev.easyratingdialog:easyratingdialog:+'
Ulrikeulster answered 13/8, 2014 at 11:41 Comment(0)
O
2

Now you can use In App Rating feature by Google.

Here is Kotlin/Java integration official guide

The Google Play In-App Review API lets you prompt users to submit Play Store ratings and reviews without the inconvenience of leaving your app or game.

Generally, the in-app review flow (see figure 1) can be triggered at any time throughout the user journey of your app. During the flow, the user has the ability to rate your app using the 1 to 5 star system and to add an optional comment. Once submitted, the review is sent to the Play Store and eventually displayed.

sc

Oosperm answered 22/1, 2021 at 5:50 Comment(0)
A
1

As you see from the other post you have linked, there isn't a way for the app to know if the user has left a review or not. And for good reason.

Think about it, if an app could tell if the user has left a review or not, the developer could restrict certain features that would only be unlocked if the user leaves a 5/5 rating. This would lead the other users of Google Play to not trust the reviews and would undermine the rating system.

The alternative solutions I have seen is that the app reminds the user to submit a rating whenever the app is opened a specific number of times, or a set interval. For example, on every 10th time the app is opened, ask the user to leave a rating and provide a "already done" and "remind me later" button. Keep showing this message if the user has chosen to remind him/her later. Some other apps developers show this message with an increasing interval (like, 5, 10, 15nth time the app is opened), because if a user hasn't left a review on the, for example, 100th time the app was opened, it's probably likely s/he won't be leaving one.

This solution isn't perfect, but I think it's the best you have for now. It does lead you to trust the user, but realize that the alternative would mean a potentially worse experience for everyone in the app market.

Andry answered 25/1, 2013 at 3:2 Comment(0)
E
1

Kotlin version of Raghav Sood's answer

Rater.kt

    class Rater {
      companion object {
        private const val APP_TITLE = "App Name"
        private const val APP_NAME = "com.example.name"

        private const val RATER_KEY = "rater_key"
        private const val LAUNCH_COUNTER_KEY = "launch_counter_key"
        private const val DO_NOT_SHOW_AGAIN_KEY = "do_not_show_again_key"
        private const val FIRST_LAUNCH_KEY = "first_launch_key"

        private const val DAYS_UNTIL_PROMPT: Int = 3
        private const val LAUNCHES_UNTIL_PROMPT: Int = 3

        fun start(mContext: Context) {
            val prefs: SharedPreferences = mContext.getSharedPreferences(RATER_KEY, 0)
            if (prefs.getBoolean(DO_NOT_SHOW_AGAIN_KEY, false)) {
                return
            }

            val editor: Editor = prefs.edit()

            val launchesCounter: Long = prefs.getLong(LAUNCH_COUNTER_KEY, 0) + 1;
            editor.putLong(LAUNCH_COUNTER_KEY, launchesCounter)

            var firstLaunch: Long = prefs.getLong(FIRST_LAUNCH_KEY, 0)
            if (firstLaunch == 0L) {
                firstLaunch = System.currentTimeMillis()
                editor.putLong(FIRST_LAUNCH_KEY, firstLaunch)
            }

            if (launchesCounter >= LAUNCHES_UNTIL_PROMPT) {
                if (System.currentTimeMillis() >= firstLaunch +
                    (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)
                ) {
                    showRateDialog(mContext, editor)
                }
            }

            editor.apply()
        }

        fun showRateDialog(mContext: Context, editor: Editor) {
            Dialog(mContext).apply {
                setTitle("Rate $APP_TITLE")

                val ll = LinearLayout(mContext)
                ll.orientation = LinearLayout.VERTICAL

                TextView(mContext).apply {
                    text =
                        "If you enjoy using $APP_TITLE, please take a moment to rate it. Thanks for your support!"

                    width = 240
                    setPadding(4, 0, 4, 10)
                    ll.addView(this)
                }

                Button(mContext).apply {
                    text = "Rate $APP_TITLE"
                    setOnClickListener {
                        mContext.startActivity(
                            Intent(
                                Intent.ACTION_VIEW,
                                Uri.parse("market://details?id=$APP_NAME")
                            )
                        );
                        dismiss()
                    }
                    ll.addView(this)
                }

                Button(mContext).apply {
                    text = "Remind me later"
                    setOnClickListener {
                        dismiss()
                    };
                    ll.addView(this)
                }

                Button(mContext).apply {
                    text = "No, thanks"
                    setOnClickListener {
                        editor.putBoolean(DO_NOT_SHOW_AGAIN_KEY, true);
                        editor.commit()
                        dismiss()
                    };
                    ll.addView(this)
                }

                setContentView(ll)
                show()
            }
        }
    }
}

Optimized answer

Rater.kt

class Rater {
    companion object {
        fun start(context: Context) {
            val prefs: SharedPreferences = context.getSharedPreferences(RATER_KEY, 0)
            if (prefs.getBoolean(DO_NOT_SHOW_AGAIN_KEY, false)) {
                return
            }

            val editor: Editor = prefs.edit()

            val launchesCounter: Long = prefs.getLong(LAUNCH_COUNTER_KEY, 0) + 1;
            editor.putLong(LAUNCH_COUNTER_KEY, launchesCounter)

            var firstLaunch: Long = prefs.getLong(FIRST_LAUNCH_KEY, 0)
            if (firstLaunch == 0L) {
                firstLaunch = System.currentTimeMillis()
                editor.putLong(FIRST_LAUNCH_KEY, firstLaunch)
            }

            if (launchesCounter >= LAUNCHES_UNTIL_PROMPT) {
                if (System.currentTimeMillis() >= firstLaunch +
                    (DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000)
                ) {
                    showRateDialog(context, editor)
                }
            }

            editor.apply()
        }

        fun showRateDialog(context: Context, editor: Editor) {
            Dialog(context).apply {
                setTitle("Rate $APP_TITLE")
                LinearLayout(context).let { layout ->
                    layout.orientation = LinearLayout.VERTICAL
                    setDescription(context, layout)
                    setPositiveAnswer(context, layout)
                    setNeutralAnswer(context, layout)
                    setNegativeAnswer(context, editor, layout)
                    setContentView(layout)
                    show()       
                }
            }
        }

        private fun setDescription(context: Context, layout: LinearLayout) {
            TextView(context).apply {
                text = context.getString(R.string.rate_description, APP_TITLE)
                width = 240
                setPadding(4, 0, 4, 10)
                layout.addView(this)
            }
        }

        private fun Dialog.setPositiveAnswer(
            context: Context,
            layout: LinearLayout
        ) {
            Button(context).apply {
                text = context.getString(R.string.rate_now)
                setOnClickListener {
                    context.startActivity(
                        Intent(
                            Intent.ACTION_VIEW,
                            Uri.parse(context.getString(R.string.market_uri, APP_NAME))
                        )
                    );
                    dismiss()
                }
                layout.addView(this)
            }
        }

        private fun Dialog.setNeutralAnswer(
            context: Context,
            layout: LinearLayout
        ) {
            Button(context).apply {
                text = context.getString(R.string.remind_later)
                setOnClickListener {
                    dismiss()
                };
                layout.addView(this)
            }
        }

        private fun Dialog.setNegativeAnswer(
            context: Context,
            editor: Editor,
            layout: LinearLayout
        ) {
            Button(context).apply {
                text = context.getString(R.string.no_thanks)
                setOnClickListener {
                    editor.putBoolean(DO_NOT_SHOW_AGAIN_KEY, true);
                    editor.commit()
                    dismiss()
                };
                layout.addView(this)
            }
        }
    }
}

Constants.kt

object Constants {

    const val APP_TITLE = "App Name"
    const val APP_NAME = "com.example.name"

    const val RATER_KEY = "rater_key"
    const val LAUNCH_COUNTER_KEY = "launch_counter_key"
    const val DO_NOT_SHOW_AGAIN_KEY = "do_not_show_again_key"
    const val FIRST_LAUNCH_KEY = "first_launch_key"

    const val DAYS_UNTIL_PROMPT: Int = 3
    const val LAUNCHES_UNTIL_PROMPT: Int = 3

}

strings.xml

<resources>
    <string name="rate_description">If you enjoy using %1$s, please take a moment to rate it. Thanks for your support!</string>
    <string name="rate_now">Rate now</string>
    <string name="no_thanks">No, thanks</string>
    <string name="remind_later">Remind me later</string>
    <string name="market_uri">market://details?id=%1$s</string>
</resources>
Echoism answered 27/3, 2020 at 10:41 Comment(0)
I
1

Android’s new In-App Review system launched which lets developers ask for Play store reviews without leaving the app.

To check design guidelines and when to display a review card, refer to the official document

https://developer.android.com/guide/playcore/in-app-review

To implement:

  • Add play-core library as a dependency in your build.gradle file.
implementation 'com.google.android.play:core:1.8.0'
  • Create a ReviewManager instance and request ReviewInfo object. The ReviewInfo object to be pre-cached and then can trigger "launchReviewFlow" to present the Review card to the user.

     private var reviewInfo: ReviewInfo? = null
    
     val manager = ReviewManagerFactory.create(context)
    
     val request = manager.requestReviewFlow()
    
     requestFlow.addOnCompleteListener { request ->
         if (request.isSuccessful) {
             //Received ReviewInfo object
             reviewInfo = request.result
         } else {
             //Problem in receiving object
             reviewInfo = null
         }
    
     reviewInfo?.let {
         val flow = reviewManager.launchReviewFlow(this@MainActivity, it)
         flow.addOnCompleteListener {
             //Irrespective of the result, the app flow should continue
         }
     }
    

Note : It is suggested to show the review flow after the user has experienced enough of your app or game.

When to request an in-app review:

  • Trigger the in-app review flow after a user has experienced enough of your app or game to provide useful feedback.
  • Do not prompt the user excessively for a review. This approach helps minimize user frustration and limit API usage (see the section on quotas).
  • Your app should not ask the user any questions before or while presenting the rating button or card, including questions about their opinion (such as “Do you like the app?”) or predictive questions (such as “Would you rate this app 5 stars”).

Few points before testing this:

  • While testing new functionalities, mostly we create a new project that would have new ApplicationId, make sure you give an ApplicationId that is already released and available in the play store.

  • If you have given feedback in the past for your app, in-app review API’s launchReviewFlow will not present any Review card. It simply triggers a success event.

  • Due to quota limitations, calling a launchReviewFlow method might not always display a dialog. It should not be linked with any click event.

Islean answered 26/8, 2020 at 11:56 Comment(0)
S
1

My simple approach using an In-app review system and counters.

import android.content.Context
import androidx.core.content.edit
import java.util.concurrent.TimeUnit

object Rater {
    private const val DAYS_UNTIL_PROMPT = 3L//Min number of days
    private const val LAUNCHES_UNTIL_PROMPT = 3//Min number of launches
    private const val RATER_KEY = "rater_key"
    private const val LAUNCH_COUNTER_KEY = "launch_counter_key"
    private const val FIRST_LAUNCH_KEY = "first_launch_key"

    fun Context.appRateCheck(dialog: () -> Unit) {
        val prefs = getSharedPreferences(RATER_KEY, 0)
        val launchesCounter = prefs.getLong(LAUNCH_COUNTER_KEY, 0) + 1
        prefs.edit { putLong(LAUNCH_COUNTER_KEY, launchesCounter) }

        var firstLaunch = prefs.getLong(FIRST_LAUNCH_KEY, 0)
        if (firstLaunch == 0L) {
            firstLaunch = System.currentTimeMillis()
            prefs.edit { putLong(FIRST_LAUNCH_KEY, firstLaunch) }
        }

        if (launchesCounter >= LAUNCHES_UNTIL_PROMPT) {
            if (System.currentTimeMillis() >= firstLaunch + TimeUnit.DAYS.toMillis(DAYS_UNTIL_PROMPT)) {
                dialog()
            }
        }
    }
}

Usage:

private val manager: ReviewManager by lazy { ReviewManagerFactory.create(context) }

private fun initInAppReview() {
    context?.appRateCheck {
        manager.requestReviewFlow().addOnSuccessListener {
            manager.launchReviewFlow(requireActivity(), it)
        }
    }
}
Suttles answered 11/3, 2021 at 16:4 Comment(0)
M
1

You can call Google In-App Review API. You do not need to count, because of Google handle this issue for you. If someone gave stars or comment previously, Google will not show again this prompt.

Look at the below code snippet.

1-) Firstly add import ReviewManagerFactory

import com.google.android.play.core.review.ReviewManagerFactory

2-) added below code snippet where appropriate place and time.

Use the ReviewInfo instance to launch the in-app review flow. Wait until the user has completed the in-app review flow before your app continues its normal user flow

fun inAppReview() {
    val reviewManager = ReviewManagerFactory.create(this)
    val requestReviewFlow = reviewManager.requestReviewFlow()
    requestReviewFlow.addOnCompleteListener { request ->
        if (request.isSuccessful) {
            // We got the ReviewInfo object
            val reviewInfo = request.result
            val flow = reviewManager.launchReviewFlow(this, reviewInfo)
            flow.addOnCompleteListener {
                // Call back
            }
        } else {
            Log.d("Error: ", request.exception.toString())
            // There was some problem, continue regardless of the result.
        }
    }
}

Google Doc : in-app-review

Marinemarinelli answered 9/10, 2021 at 10:25 Comment(0)
P
0

All those libraries are not the solution for the problem in this post. This libraries just open a webpage to the app on google play. Instead this Play core library has more consistent interface.

So I think this is the problem, ProGuard: it obfscates some classes enough https://mcmap.net/q/205182/-android-play-core-inappreview-api-dialog-not-showing-up

Platoon answered 29/8, 2020 at 18:39 Comment(0)
N
0

Kotlin implementation of user auto popup rating logic. Direct copy past

import android.app.Dialog
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import com.mikashboks.droid.common.AppRatingManager
import com.mikashboks.droid.resource.R
import timber.log.Timber


class RateItDialogFragment : DialogFragment() {

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return AlertDialog.Builder(requireContext())
            .setTitle(R.string.rate_dialog_title)
            .setMessage(R.string.rate_dialog_message)
            .setPositiveButton(R.string.rate_dialog_ok) { dialog, which ->
                AppRatingManager.startAppRatingFlow(requireActivity()) { isCompleted ->
                    Timber.v("App Review $isCompleted")
                }
                getSharedPreferences(requireContext()).edit().putBoolean(DISABLED, true).apply()
                dismiss()
            }
            .setNeutralButton(R.string.rate_dialog_cancel) { dialog, which ->
                getSharedPreferences(requireContext()).edit().putInt(LAUNCHES, 0).apply()
                dismiss()
            }
            .setNegativeButton(R.string.rate_dialog_no) { dialog, which ->
                getSharedPreferences(requireContext()).edit().putBoolean(DISABLED, true).apply()
                dismiss()
            }.create()
    }

    companion object {
        private const val LAUNCHES_UNTIL_PROMPT = 10
        private const val DAYS_UNTIL_PROMPT = 3
        private const val MILLIS_UNTIL_PROMPT = DAYS_UNTIL_PROMPT * 24 * 60 * 60 * 1000
        private const val PREF_NAME = "APP_RATER"
        private const val LAST_PROMPT = "LAST_PROMPT"
        private const val LAUNCHES = "LAUNCHES"
        private const val DISABLED = "DISABLED"
        const val TAG = "RateItDialogFragment"

        fun showDialog(context: Context, manager: FragmentManager, tag: String?) {
            var shouldShow = false
            val sharedPreferences = getSharedPreferences(context)
            val editor = sharedPreferences.edit()
            val currentTime = System.currentTimeMillis()
            var lastPromptTime = sharedPreferences.getLong(LAST_PROMPT, 0)
            if (lastPromptTime == 0L) {
                lastPromptTime = currentTime
                editor.putLong(LAST_PROMPT, lastPromptTime)
            }
            if (!sharedPreferences.getBoolean(DISABLED, false)) {
                val launches = sharedPreferences.getInt(LAUNCHES, 0) + 1
                if (launches > LAUNCHES_UNTIL_PROMPT) {
                    if (currentTime > lastPromptTime + MILLIS_UNTIL_PROMPT) {
                        shouldShow = true
                    }
                }
                editor.putInt(LAUNCHES, launches)
            }
            if (shouldShow) {
                editor.putInt(LAUNCHES, 0).putLong(LAST_PROMPT, System.currentTimeMillis()).apply()
                RateItDialogFragment().show(manager, tag)
            } else {
                editor.commit()
            }
        }

        private fun getSharedPreferences(context: Context): SharedPreferences {
            return context.getSharedPreferences(PREF_NAME, 0)
        }
    }
}
import android.app.Activity
import com.google.android.play.core.review.ReviewManagerFactory
import com.mikashboks.droid.common.tools.IntentTools

object AppRatingManager {

    fun startAppRatingFlow(activity: Activity, onResult: (Boolean) -> Unit) {
        val manager = ReviewManagerFactory.create(activity)
        val reviewRequest = manager.requestReviewFlow()
        reviewRequest.addOnCompleteListener { request ->
            if (request.isSuccessful) {
                val flow = manager.launchReviewFlow(activity, request.result)
                flow.addOnCompleteListener { _ ->
                    // The flow has finished. The API does not indicate whether the user
                    // reviewed or not, or even whether the review dialog was shown. Thus, no
                    // matter the result, we continue our app flow.
                    onResult.invoke(true)
                }
            } else {
                IntentTools.launchPlayStore(activity,activity.packageName)
            }
        }
    }
}
fun launchPlayStore(context: Context, applicationId: String) {
        val url = "https://play.google.com/store/apps/details?id=$applicationId"
        val goToMarket = Intent(Intent.ACTION_VIEW, Uri.parse(url))
        try {
            context.startActivity(goToMarket)
        } catch (e: Exception) {
            e.printStackTrace()
            context.startActivity(
                Intent(
                    Intent.ACTION_VIEW,
                    Uri.parse("https://play.google.com/store/apps/details?id=${applicationId}")
                )
            )
        }
    }
<string name="rate_dialog_title">Rate this app</string>
<string name="rate_dialog_message">If you enjoy playing this app, would you mind taking a moment to rate it? It won\'t take more than a minute. Thanks for your support!</string>
<string name="rate_dialog_ok">Rate It Now</string>
<string name="rate_dialog_cancel">Remind Me Later</string>
<string name="rate_dialog_no">No, Thanks</string>
 implementation 'com.google.android.play:review-ktx:2.0.1'

Final Usage

// Ask user for rating
RateItDialogFragment.showDialog(
    requireContext(), childFragmentManager, RateItDialogFragment.TAG
)
Nanaam answered 5/2 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.