Ads And Iap Monetization logo

Ads And Iap Monetization

Community
zakariaf
ads-and-iap-monetization

Enforces opt-in, rewarded-first monetization over the service seam — a rewarded view grants exactly one benefit and only on a genuine reward outcome (a sealed Rewarded/Dismissed/NoFill), earn loops capped per day against an injected Clock, the earn control preloaded and hidden unless an ad is loaded so a fresh account never dead-ends on "no ads available" (a Guideline 2.1 risk), interstitials only at a natural break under a count-and-elapsed cap owned by the service so callers stay dumb, no banner on the primary work surface, one non-consumable unlock plus Restore re-checked each launch and committed through the single write path before the UI reacts, one derived isEntitled provider as the only gate, and the entitlement restored BEFORE the ad SDK initializes so a paying user never starts it, resolves consent, or sees a tracking prompt. Use when adding ads, wiring a rewarded earn loop, placing an interstitial, building a paywall, gating an entitlement, or setting up ad units.

Overview

Publisherzakariaf
RepositoryFlutter-Skills
Skill nameads-and-iap-monetization
Stars
64
Forks
15
Bundled files
4
LicenseMIT
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • 4 bundled files

    Scripts, templates, and references the model can read while it works. Files are read-only and never executed.

  • Open source

    Published by zakariaf on GitHub. Read the source before you install it.

Installation

Install the Ads And Iap Monetization AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/zakariaf/Flutter-Skills/tree/main/skills/ads-and-iap-monetization
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/zakariaf/Flutter-Skills.git /tmp/Flutter-Skills
mkdir -p .claude/skills
cp -r /tmp/Flutter-Skills/skills/ads-and-iap-monetization .claude/skills/ads-and-iap-monetization
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Ads And Iap Monetization in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Ads And Iap Monetization on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Ads And Iap Monetization is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Ads and IAP monetization

The policy layer over the ads and billing seams: what a rewarded view grants, when an interstitial may fire and how often, how an entitlement gates both, and in what order they initialize at launch. It assumes the seam already exists — declaring the interface, overriding it per flavor, and keeping SDK types out of feature code belong to service-boundary-and-native.

The model this encodes is free, opt-in-first: value is exchanged for attention the user chose to give, and one non-consumable purchase removes the exchange. It is not the only viable model, but the rules below are what keep any ad-funded app honest, reviewable, and out of the "feels like adware" bucket.

Non-negotiable rules

  1. Every ad and store call goes through an injected interface, never an SDK type in feature code. AdsService, BillingService; the concrete SDK is wired once at the composition root and faked in tests. Mechanism is owned by service-boundary-and-native — do not re-derive it here.

  2. A rewarded view is opt-in and grants exactly one concrete benefit. It fires only from an explicit "watch to earn" tap, never automatically, and it hands back one unit — a credit, an extra attempt, a partial reveal — never the whole outcome the user is working toward. WHY: an auto-playing "rewarded" ad is an interstitial wearing a costume, and a grant that finishes the task for the user destroys the thing they came for.

  3. Grant only on a genuine reward outcome. Model the result as a sealed type (Rewarded / Dismissed / NoFill) and switch exhaustively; Dismissed (closed early) and NoFill grant nothing and leave state untouched. WHY: a bool return collapses "no ad existed" into "user declined" and the two need different UI.

  4. Cap the earn loop per day, in the policy layer, against an injected Clock. An uncapped watch-to-earn loop lets any user bypass whatever the benefit was rationing, and the ceiling belongs next to the rule it protects — not in the ad network console (see references/ad-unit-setup.md for why a console-side frequency cap actively harms an opt-in format). Key the cap to a calendar day derived from clock.now(), never DateTime.now().

  5. Never ship an earn control that can dead-end. A brand-new ad account has near-zero fill, so an always-visible "watch to earn" button answers "no ads available" every time — poor UX and a Guideline 2.1 rejection risk for a control that looks broken. Preload, expose availability as a stream, and show the control only while an ad is actually loaded (or a redemption is in flight). Whatever the user needs must remain reachable without watching anything.

  6. Interstitials fire only at a natural break, under a two-part cap the service owns. The only eligible moment is a completion or transition boundary the user already expected — never mid-task, never during onboarding, never in the first session. Both halves of the cap must pass: at most one per N completions and at least M seconds since the last one. Put the counting and the clock inside maybeShowInterstitial(), and return whether it showed, so no caller counts anything. WHY: a cap enforced at three call sites is three caps that will disagree.

  7. No banner on the primary work surface. A banner on the screen where the user is actually doing the thing reflows layout, invites mis-taps, and earns the least of any format. A menu or home surface is the only defensible placement, and it is optional.

  8. One non-consumable unlock plus Restore, re-checked on every launch. ⚠️ On the app's first release the unlock must be submitted in the same App Review submission as the app version — Apple requires the first purchase of each type to ride a version, and shipping the version alone gets the whole submission closed unreviewed under Guideline 2.1(b) (release-and-store-shipping). Sell it once; expose a visible Restore; silently re-check entitlement at launch so a reinstall or a new device re-grants it without a support ticket. Not a subscription for a one-time capability, and not a consumable currency sold for cash where an opt-in rewarded path already exists.

  9. One derived isEntitled provider is the only gate. Everything — every ad call site, every cap, every paywall affordance — reads it. Nothing branches on the raw entitlement stream. WHY: a second read of the same truth is a second gate that will drift out of agreement with the first.

  10. Restore the entitlement BEFORE initializing the ad SDK. No-op-ing ad calls is not enough: if AdsService.init() already ran, a paying user has had the SDK started, consent resolved, a tracking prompt shown, and their device identified — everything they paid to avoid. Await the restore first and initialize ads only for a confirmed non-entitled user. A store or network failure resolves to not entitled and initializes — never withhold ads from every free user because one call flaked — so cache the last known entitlement locally to keep an offline launch correct for an owner. See references/entitlement-and-restore.md.

  11. A purchase persists through the single write path before the UI reacts. One committed transaction through the repository, then the UI updates because isEntitled re-emits — never an optimistic dismiss. WHY: a crash mid-flow must never leave a "paid but not entitled" ghost. Write-path mechanics are owned by persistence-drift.

  12. Never paid-upfront and ad-supported. Pick one. Charging for the app and then monetizing the buyer's attention is the pattern users punish in reviews, and it makes the paywall unarguable.

  13. Ad unit identifiers are configuration, not secrets — but sample ids must never ship. They are visible in any decompiled app, so compile-time constants beat --dart-define (a release build cannot forget a constant). Gate the release on it: scripts/check-release-ad-ids.sh fails a release that still carries the network's well-known sample ids, which serve "test ad" creatives and read as broken to a reviewer.

The earn loop, end to end

dart
// Policy layer: depends on the interfaces and the entitlement gate — never on an SDK.
sealed class RewardOutcome { const RewardOutcome(); }
final class Rewarded  extends RewardOutcome { const Rewarded(); }
final class Dismissed extends RewardOutcome { const Dismissed(); }   // closed early
final class NoFill    extends RewardOutcome { const NoFill(); }      // no ad existed

class CreditEconomy extends Notifier<CreditState> {
  static const int freeCreditsPerDay = 1;   // the allowance the design assumes
  static const int maxEarnedPerDay = 3;     // the integrity ceiling (rule 4)

  @override
  CreditState build() => const CreditState();

  /// Opt-in earn path. Grants ONLY on `Rewarded`, and only under the daily cap.
  Future<Result<Credit, MonetizationFailure>> watchToEarn() async {
    if (ref.read(isEntitledProvider).valueOrNull ?? false) return _grant(); // no ad at all
    if (state.earnedToday(ref.read(clockProvider)) >= maxEarnedPerDay) {
      return const Err(EarnCapReached());
    }
    return switch (await ref.read(adsServiceProvider).showRewarded()) {
      Rewarded()  => _grant(),
      Dismissed() => const Err(RewardNotEarned()),   // distinct: the user closed it
      NoFill()    => const Err(NoAdAvailable()),     // distinct: nothing to show
    };
  }
}

The control that calls it is bound to availability, so it is never a dead end (rule 5):

dart
// Shown only while an ad is genuinely loaded; the free path stays reachable regardless.
final rewardedReadyProvider =
    StreamProvider<bool>((ref) => ref.watch(adsServiceProvider).rewardedAvailability);

if (ref.watch(rewardedReadyProvider).valueOrNull ?? false)
  const WatchToEarnButton(),

And the interstitial call site counts nothing (rule 6):

dart
Future<void> onTaskCompleted(WidgetRef ref) async {
  if (ref.read(isEntitledProvider).valueOrNull ?? false) return;   // rule 9
  await ref.read(adsServiceProvider).maybeShowInterstitial();      // owns both cap halves
}

Launch order

dart
// Entitlement first, ads only if the user is not entitled (rule 10).
// Startup-sequence mechanics are owned by `app-startup-and-bootstrap`.
final entitlement = await ref.read(billingServiceProvider).restore();   // failure ⇒ not entitled
if (!entitlement.isEntitled) {
  await ref.read(adsServiceProvider).init();      // consent / tracking prompt happens in here
  await ref.read(adsServiceProvider).preloadRewarded();
}

Cover it with a test asserting the fake ads service records initCount == 0 for an entitled user — that single assertion is what keeps rule 10 true through future refactors.

Anti-patterns

  • Auto-playing a "rewarded" ad, or granting on Dismissed/NoFill — it is opt-in, and the grant is for a completed view only.
  • A bool rewarded result — "no fill" and "user closed it" need different UI; use the sealed type.
  • An uncapped earn loop, or a cap enforced in the ad console instead of the app — the console cap produces no-fill, and no-fill hides the control (rule 5), so the user loses the path with no visible reason.
  • An always-visible earn button on a new account — permanent "no ads available"; Guideline 2.1.
  • A grant that completes the user's task — reveal a step, never the destination.
  • Interstitials mid-task, on first launch, during onboarding, or a caller that counts completions or reads a clock — the break is the only trigger and the cap lives in the service.
  • A banner on the working surface — the one placement that is never worth it.
  • Widgets reading the raw entitlement stream — everything reads the one derived gate.
  • No-op-ing ads for an entitled user without moving the restore before init() — they still got the SDK, the consent flow, and the tracking prompt.
  • Blocking ads for everyone when a restore call fails — failure means not entitled; cache the last known state so an offline owner stays correct.
  • Dismissing the paywall before the entitlement is durably written — persist, then let the gate re-emit.
  • Skipping the launch restore — reinstalls and device swaps become support tickets.
  • Paid-upfront plus ads, or a subscription for a one-time unlock.
  • Shipping the network's sample ad ids — "test ad" creatives earn nothing and look broken on review.

Definition of done

  • Ads and billing are reached only through injected interfaces; no SDK type appears in feature code.
  • Rewarded is opt-in, grants exactly one benefit, and grants only on Rewarded; the outcome type is sealed and switched exhaustively.
  • The earn loop is capped per calendar day against the injected Clock; the cap lives in the app.
  • The earn control is preloaded and hidden unless an ad is loaded; the task stays completable without watching anything.
  • Interstitials fire only at a break; maybeShowInterstitial() owns the count and elapsed caps and reports whether it showed; no caller counts.
  • No banner on any primary work surface.
  • One non-consumable + a visible Restore; entitlement silently re-checked at every launch.
  • On a first release, the product ships in the same submission as the app version, with its App Review screenshot, tax category, availability, and one localization per app locale.
  • isEntitled is the single gate; no feature branches on the raw entitlement stream.
  • Restore runs before AdsService.init(); a test asserts the fake's initCount == 0 for an entitled user; a failed restore falls back to not entitled with a local cache for offline.
  • The purchase commits through the single write path before any UI reacts.
  • Release ad ids verified by scripts/check-release-ad-ids.sh; no sample ids in a release build.
  • Tests drive: watch → granted, cap reached → refused, no-fill → control hidden, purchase → ads gone and caps lifted, against fakes.

Related skills

  • See service-boundary-and-native for declaring AdsService/BillingService, the per-flavor live implementation, and the fakes these tests use.
  • See app-startup-and-bootstrap for where the restore-then-init sequence belongs in main().
  • See state-management-riverpod for the derived-gate provider and persistence-drift for the single write path the purchase commits through.
  • See error-handling-typed-results for the Result/Failure arms returned above.
  • See release-and-store-shipping for the privacy declaration the ad SDK drives and the reviewer note that explains empty ad inventory during review.

References

Bundled files

The model reads these on demand while the skill is loaded. They are exposed as readable files and are never executed.

Frequently asked questions

What does the Ads And Iap Monetization AI skill do?

Enforces opt-in, rewarded-first monetization over the service seam — a rewarded view grants exactly one benefit and only on a genuine reward outcome (a sealed Rewarded/Dismissed/NoFill), earn loops capped per day against an injected Clock, the earn control preloaded and hidden unless an ad is loaded so a fresh account never dead-ends on "no ads available" (a Guideline 2.1 risk), interstitials only at a natural break under a count-and-elapsed cap owned by the service so callers stay dumb, no banner on the primary work surface, one non-consumable unlock plus Restore re-checked each launch and...

Why use Ads And Iap Monetization on TypingMind?

Because you install it once and use it with any model. Ads And Iap Monetization is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Ads And Iap Monetization in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/zakariaf/Flutter-Skills/tree/main/skills/ads-and-iap-monetization. TypingMind reads its SKILL.md and bundles its files and installs it as a skill you can enable per chat.

Which AI models can use Ads And Iap Monetization?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Ads And Iap Monetization?

As many as you like. As long as a model supports skills, you can use Ads And Iap Monetization with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Ads And Iap Monetization AI skill free?

Yes. It is published on GitHub by zakariaf under the MIT license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇