rewarded_ad_counter
A stateful Flutter package to manage, count, and persist AdMob rewarded ad views. Easily implement "watch X ads to unlock" monetization mechanics: your users watch a few rewarded ads to get several days of premium features for free — with an in-app purchase option to buy the Pro product instead.
What is this?
Most apps with a paid PRO version give users a taste of the premium experience. With this package you can offer:
- Rewarded ads path: the user watches
rewardTimesrewarded ads (1–5, default 3) and getsrewardDaysdays of free premium access (default 7, minimum 1). - Purchase path: below the rewarded section, a card invites the user to buy your non-consumable or subscription product directly.
Progress is saved with shared_preferences, so the counter and the active unlock survive leaving the page and even full app restarts. Everything — texts, colors, ad units, product ID — is configurable by you; nothing is hardcoded in the package.
Features
- ✅ Rewarded ads card first, Pro purchase card below — fully localized via a single
stringsobject - ✅ Configurable
rewardTimes(1–5) andrewardDays(≥ 1) - ✅ Persisted progress — counter and unlock expiry survive restarts
- ✅ IAP handled — product query, purchase, restore, and stream updates built in
- ✅ No bundled ad unit IDs — you always provide your own; the package never falls back to a default
- ✅ Theme-aware with optional custom button background colors
- ✅ Entry button widget to open the page with a single tap
Screenshot

Getting started
Add the dependency to your pubspec.yaml:
dependencies:
rewarded_ad_counter: ^0.0.2
Then run:
flutter pub get
Usage
1. Create a config
You must provide the product ID and the rewarded ad unit IDs for Android and iOS (use Google's test ad unit IDs while developing):
const config = RewardedAdCounterConfig(
productId: 'com.example.app.pro',
rewardedAdUnitIdAndroid: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
rewardedAdUnitIdIos: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
);
If you only have one ad unit ID, use the singleAdUnit factory — it applies the same ID to both platforms:
const config = RewardedAdCounterConfig.singleAdUnit(
productId: 'com.example.app.pro',
rewardedAdUnitId: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
);
2. Show the page
From a button anywhere in your app:
RewardedAdCounterEntryButton(
label: 'Remove ads',
config: config,
),
Or push the page directly:
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const RewardedAdCounterPage(config: config),
),
);
The page comes with an app bar that includes a back button.
Configuration reference
| Param | Default | Description |
|---|---|---|
productId |
required | ID of the non-consumable or subscription product queried for the purchase flow. |
rewardedAdUnitIdAndroid |
null |
Rewarded ad unit ID for Android. Mandatory to show ads; if omitted the ad never loads. |
rewardedAdUnitIdIos |
null |
Rewarded ad unit ID for iOS. If omitted, falls back to the Android ID. |
rewardTimes |
3 |
Number of rewarded ads to watch to unlock. Must be between 1 and 5. |
rewardDays |
7 |
Days of premium access granted after completing the ads. Must be at least 1. |
watchAdButtonBgColor |
Color(0xFF4AB907) |
Background color of the "Watch ad" button. |
buyButtonBgColor |
null |
Background color of the "Buy Pro" button. When null, the theme's default filled button color is used. |
strings |
RewardedAdCounterStrings() |
All texts shown in the page (English defaults). |
sharedPreferences |
null |
Optional pre-initialized SharedPreferences instance. When omitted, the package creates its own, so persistence works out of the box. |
Note on ad unit IDs: the package intentionally ships no ad unit IDs — not even test ones. You always provide your own, so there is no risk of another app earning from your inventory. In debug builds, point the config at Google's official test ad units.
Customizing texts
All texts are grouped in a single RewardedAdCounterStrings object with sensible English defaults. Override just the ones you need and pass it as strings:
const strings = RewardedAdCounterStrings(
pageTitle: 'Get premium',
rewardTitle: 'Watch {count} ads for {days} days free',
rewardDescription:
'Watch {count} rewarded ads and get {days} days without ads.',
watchButtonText: 'Watch ad',
buyButtonText: 'Buy Pro',
activeBadgeText: 'Active — {val} remaining',
watchedCountText: '{val} of {total} ads watched',
);
const config = RewardedAdCounterConfig(
productId: 'com.example.app.pro',
rewardedAdUnitIdAndroid: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
rewardedAdUnitIdIos: 'ca-app-pub-XXXXXXXXXXXXXXXX/XXXXXXXXXX',
strings: strings,
);
Placeholders supported: {count} (ads to watch), {days} (days granted), {val} (dynamic value), {total} (total ads).
How it works
- The page creates a
RewardedAdCounterController, loads the previous state fromshared_preferences, queries the store for your product, and preloads a rewarded ad. - Each completed ad increments the persisted counter. When the target is reached, a
rewardDays-long unlock starts and an expiry timer keeps the UI honest. - While an unlock is active, the rewarded card shows a green "Active" badge and the remaining time, and no ads are loaded.
- Buying the product (or restoring a previous purchase) permanently disables ads and the rewarded flow.
Contributing
Found a bug or have a feature request? Open an issue or a pull request — contributions are welcome.
Support & Follow 🚀
If this package helped you, consider supporting my work:
