App reviews play an important role in the success of your apps. They not only provide essential feedback but also significantly affect the app's store performance:
- Positive reviews boost the app's ranking and drive more downloads.
- Negative reviews can discourage new users from downloading.
For example, here are the reviews from my Flutter Tips app:
Glowing reviews don't just fall from the sky: you have to earn them by making a good app and make it as easy as possible for users to leave a review.
The best way to do this is by showing an in-app rating prompt at the right moment—when users are most engaged and satisfied. This could be after completing a task, leveling up in a game, or using a feature successfully.
Timing the prompt correctly increases the chances of getting positive reviews, boosting your app’s rating and visibility in the app store.
And by using the in_app_review package and a bit of extra code, you can do exactly that.
The in_app_review package
The in_app_review package allows you to ask for reviews in two different ways:
- Call-to-action: by redirecting the user to the store with the
openStoreListing
API - Programmatically: by triggering the in-app review prompt with the
requestReview
API
The second method is most effective, since it can be triggered when the user is most satisfied with the app. For example, I've programmed my Flutter Tips app to show the prompt after users like 5 tips in the app:
What we will cover
In this article, I'll show you how to apply the same technique to your apps.
Here's what we will cover:
- in_app_review installation and how to show the app-review prompt programmatically
- How to avoid showing the prompt too early (by following the quotas on the app stores)
- How to use analytics to ensure the prompt shows at the right time
When to show the prompt?
Showing the prompt is the easy part.
The real challenge is deciding when to do it:
- too early, and you will annoy your users (so many apps get this wrong!)
- too late, and hardly any users will even see it at all
As we will see, by using analytics and a data-driven approach, we can show the prompt at the right time, for the most engaged users, thus maximising our chances of getting positive reviews.
Ready? Let's go! 🚀
This article is not a step-by-step tutorial—it's more of a high-level guide showing how things fit together. If you want to go deeper, check my latest course about Flutter in Production.
Installation
Installing the in_app_review package is easy enough:
dart pub add in_app_review:2.0.9
flutter pub get
If you're using Riverpod, consider creating a separate provider for this:
// in_app_review_provider.dart
import 'package:in_app_review/in_app_review.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'in_app_review_provider.g.dart';
@riverpod
InAppReview inAppReview(InAppReviewRef ref) {
return InAppReview.instance;
}
Since the code above relies on code generation, you'll need to run
dart run build_runner watch -d
to generate your provider. To learn more, read: How to Auto-Generate your Providers with Flutter Riverpod Generator.
Showing the in-app review prompt
As discussed, we need to show the prompt at the right time.
This means that you need to choose the single, most important event that happens when the user is most satisfied with your app (e.g. completed a task, uses a feature successfully).
For my Flutter Tips app, this is the "tip liked" event:
The callback handler for this button looks something like this:
Future<void> updateTipLiked(int tipIndex, bool isLiked) async {
if (isLiked) {
// * Show app review prompt based on some conditional logic
await inAppRatingService.requestReviewIfNeeded(
userTotalLikesCount: userTotalLikesCount
);
// * Log the event with analytics
unawaited(analyticsFacade.trackTipLiked(
tipIndex: tipIndex,
userTotalLikesCount: userTotalLikesCount,
));
}
}
Two things to note:
- The
updateTipLiked
method has two purposes: show the app review prompt and track the event. - Both the
requestReviewIfNeeded
andtrackTipLiked
methods takeuserTotalLikesCount
as an argument.
We'll get back to some of these details later. But for now, let's focus on the InAppRatingService
.
If you're not familiar with the
unawaited
function, read: Futures: await vs unawaited vs ignore and Use unawaited for your analytics calls.
The InAppRatingService class
To handle all the in-app review logic, we can use a dedicated class that looks like this:
/// Helper class used to show the in-app rating prompt when a certain number of
/// tips has been liked
class InAppRatingService {
const InAppRatingService(this.ref);
final Ref ref;
// * Used to show the prompt
InAppReview get _inAppReview => ref.read(inAppReviewProvider);
/// Requests a review if certain conditions are met
Future<void> requestReviewIfNeeded({required int userTotalLikesCount}) async {
// * Don't show rating prompt on web (not supported)
if (kIsWeb) {
return;
}
// TODO: Only show prompt after a certain number of tips has been liked
// * If we can show a review dialog
if (await _inAppReview.isAvailable()) {
// * Request the review
await _inAppReview.requestReview();
}
}
}
@riverpod
InAppRatingService inAppRatingService(InAppRatingServiceRef ref) {
return InAppRatingService(ref);
}
Some notes:
- The class takes a
ref
argument, which can be used to read other providers (such asinAppReviewProvider
). - The
requestReviewIfNeeded
method doesn't do anything ifkIsWeb
is true (we can only show the prompt on iOS and Android). - We check if the review dialog is available before showing it.
If your app doesn't use Riverpod, you can delete the provider and inject
InAppReview
as a constructor argument instead.
Does this code work as intended?
If we added the InAppRatingService
class above to Flutter Tips app and ran it on iOS, the rating prompt would appear as soon as we like a tip for the first time, and then again for each subsequent like:
That's a bit too eager!
To avoid giving a bad first impression, we should add some logic that says "wait until the user liked N tips before showing the prompt".
Showing the Review Prompt After N Events
To accomplish what we want, we can use the userTotalLikesCount
variable I mentioned before:
Future<void> updateTipLiked(int tipIndex, bool isLiked) async {
if (isLiked) {
// * Show app review prompt based on some conditional logic
await inAppRatingService.requestReviewIfNeeded(
userTotalLikesCount: userTotalLikesCount
);
// * Log the event with analytics
unawaited(analyticsFacade.trackTipLiked(
tipIndex: tipIndex,
userTotalLikesCount: userTotalLikesCount,
));
}
}
In your apps, this variable may have a different name. You could store it locally with Shared Preferences, and increment it every time the user completes a certain action.
Here's an updated version of the InAppReviewService
:
/// Helper class used to show the in-app rating prompt when a certain number of
/// tips has been liked
class InAppRatingService {
const InAppRatingService(this.ref);
final Ref ref;
// * Used to show the prompt
InAppReview get _inAppReview => ref.read(inAppReviewProvider);
// * Used to keep track of how many times we've requested
// * a review from the user
SharedPreferences get _sharedPreferences =>
ref.read(sharedPreferencesProvider).requireValue;
static const key = 'in_app_rating_prompt_count';
int get _inAppReviewRequestCount => _sharedPreferences.getInt(key) ?? 0;
/// Requests a review if certain conditions are met
Future<void> requestReviewIfNeeded({required int userTotalLikesCount}) async {
// * Don't show rating prompt on web (not supported)
if (kIsWeb) {
return;
}
// * If we can show a review dialog
if (await _inAppReview.isAvailable()) {
// * Use an exponential backoff function:
// * - 1st request after 5 liked tips
// * - 2nd request after another 10 liked tips
// * - 3rd request after another 20 liked tips
if (completedTasksCount >= 5 && _inAppReviewRequestCount == 0 ||
completedTasksCount >= 15 && _inAppReviewRequestCount == 1 ||
completedTasksCount >= 35 && _inAppReviewRequestCount == 2) {
// * Request the review
await _inAppReview.requestReview();
// * Increment the count
await _sharedPreferences.setInt(key, _inAppReviewRequestCount + 1);
}
}
}
}
Note about storing the review request count with Shared Preferences
The code above uses Shared Preferences to keep track of how many times we've requested an in-app review.
This ensures that the request count is persisted locally and can be retrieved even if we quit the app and restart it. However, if we delete and reinstall the app, or clear its storage, the count will be reset, but the app stores will still remember the quota (more on this below).
If you want to persist this kind of information across app reinstalls and your app supports authentication, consider storing the event count on your remote database, for each user.
Reviewing the Conditional Logic
The most important code in the InAppReviewService
class is this:
// * Use an exponential backoff function:
// * - 1st request after 5 liked tips
// * - 2nd request after another 10 liked tips
// * - 3rd request after another 20 liked tips
if (completedTasksCount >= 5 && _inAppReviewRequestCount == 0 ||
completedTasksCount >= 15 && _inAppReviewRequestCount == 1 ||
completedTasksCount >= 35 && _inAppReviewRequestCount == 2) {
// * Request the review
await _inAppReview.requestReview();
// * Increment the count
await _sharedPreferences.setInt(key, _inAppReviewRequestCount + 1);
}
By adding the conditional logic above, we can ensure the review is requested after 5, 15, and 35 liked tips, respectively.
This is in line with the iOS App Store quotas, which state that:
The system automatically limits the display of the prompt to three occurrences per app within a 365-day period (source).
But hold on! 5
, 15
, and 35
are arbitrary numbers! How have I decided they were optimal for my Flutter Tips app?
The answer lies in my analytics. 👇
Making Data-Driven Decisions with Analytics
Recall that in addition to requesting the in-app review, I'm also calling this method when a user likes a tip:
// * Log the event with analytics
unawaited(analyticsFacade.trackTipLiked(
tipIndex: tipIndex,
userTotalLikesCount: userTotalLikesCount,
));
Under the hood, this sends a custom event named "Tip Liked" to Mixpanel.
As a result, after publishing my app, I created a custom analytics report that looks like this:
This shows the maximum number of user total likes on the 75th and 90th percentile across all users.
Based on the given time period, I can see that, on average:
- users on the 75th percentile like 6.9 tips
- users on the 90th percentile like 17.1 tips
In plain English, this tells me how many tips are liked by the most engaged users. As a result, 5
, 15
, and 35
seemed to be reasonable thresholds for my app:
if (userTotalLikesCount >= 5 && inAppRatingPromptCount == 0 ||
userTotalLikesCount >= 15 && inAppRatingPromptCount == 1 ||
userTotalLikesCount >= 35 && inAppRatingPromptCount == 2) {
// Request in-app review
}
But how should you approach this in your apps?
Data-Driven In-App Review Prompt
Here are some guidelines for showing the app review prompt and maximising the number of positive reviews in your apps:
- Decide which is the most important event that happens when the user is most satisfied with your app (e.g. completed a task, uses a feature successfully)
- Add some analytics code to track that event, as well as how many times it has happened for that user (or device)
- Launch the app on the stores
- Create a custom report and measure the 75th and 90th percentile for that event (this is easy to do with Mixpanel)
- Once you have enough data, add the in-app review logic and choose the appropriate thresholds, as shown above
- Release a new version of your app
This approach has served me well, and I plan to use it in all my apps. Feel free to borrow the code in this article and tweak it for your own needs.
Caveat: if users don't enjoy using your app, you're more likely to get negative reviews. So make sure you build a good app first, and then you can optimise for getting more reviews. I recommend collecting user feedback separately with the feedback package, which also offers a Sentry plugin.
Conclusion
The in_app_review package makes it super easy to show an in-app review prompt in your app.
But the real challenge is deciding when to show the prompt:
- too early, and you will annoy your users
- too late, and hardly any users will even see it at all
As we've seen, by using analytics and a data-driven approach, we can ensure the prompt is shown at the right time for the most engaged users, thus maximising our chances of getting positive reviews.
And with this, I wish you the best in launching your apps! ⭐️
New course: Flutter in Production
In-app reviews play an important role in the success of your apps in the stores.
But when it comes to shipping and monitoring apps in production, there are many more things to consider:
- Preparing for release: splash screens, flavors, environments, error reporting, analytics, force update, privacy, T&Cs
- App Submissions: app store metadata & screenshots, compliance, testing vs distribution tracks, dealing with rejections
- Release automation: CI workflows, environment variables, custom build steps, code signing, uploading to the stores
- Post-release: error monitoring, bug fixes, addressing user feedback, adding new features, over-the-air updates
My latest course will help you get your app to the stores faster and with fewer headaches.
If you’re interested, you can learn more and enroll here. 👇