All posts
Build log

Adding subscriptions to a Flutter app without losing your mind

The Trott team 7 min read

Short answer: don't build cross-platform billing yourself. Use RevenueCat. It sits on top of Apple and Google's in-app purchase systems, handles receipts and renewals, and gives your Flutter app one clean way to ask "is this person subscribed?" We wired it into Trott during the Shipaton. Here's the honest, practical version.

Why RevenueCat instead of rolling your own

In-app subscriptions sound simple until you read the docs. Apple and Google each have their own purchase flow, their own receipt format, their own renewal and refund webhooks, their own sandbox quirks. Then you have to keep a user's subscription status in sync across reinstalls, new devices, and the moment a card gets declined at 3am.

That's a lot of plumbing for two people with day jobs. RevenueCat absorbs most of it. You get a single SDK, a dashboard, and one question you can ask anywhere in the app: does this user have the premium entitlement, yes or no? We'd rather spend our hours on the part people actually pay for.

Set up products, entitlements, and offerings first

Before you touch any Flutter code, model your pricing in the RevenueCat dashboard. Three concepts do all the work, and it helps to keep them straight:

  • Products are the actual things people buy — a monthly plan, an annual plan.
  • Entitlements are what those purchases unlock. Most apps need exactly one, something like "premium." A monthly and an annual product both grant the same entitlement.
  • Offerings are the bundles you show on the paywall. This is where you decide which products appear and in what order, and you can change that later without shipping an app update.

Get this layer right and the rest gets easy. Your app stops caring which product someone bought and only checks the entitlement. Cleaner code, fewer branches.

Mirror everything in App Store Connect and Play Console

RevenueCat doesn't replace the stores — it talks to them. So every product also has to exist in App Store Connect and the Google Play Console, with matching identifiers, prices, and a description. This is the tedious part. Tax forms, banking details, subscription groups, review notes. Plan an afternoon for it, not ten minutes.

One thing that bit us: the stores take time to propagate new products. If a fresh subscription doesn't show up immediately, that's usually normal, not a bug in your code. Give it a beat before you start debugging the wrong thing.

Add the SDK and a paywall worth paying for

Adding the RevenueCat SDK to a Flutter project is the quick bit: pull in the package, configure it once on startup with your API key, and you can fetch the current offering. The SDK hands you the products with localized prices already filled in, so you never hardcode "$4.99" anywhere.

The paywall matters more than the wiring. A paywall isn't a price tag — it's where you make the case. Show what the subscription unlocks in plain terms, lead with the value not the cost, and make the primary action obvious. We frame it around the thing people came for: save anything, then actually find it again later. List the benefits, show the options, get out of the way.

Handle the purchase, then handle the unhappy paths

A successful purchase is the easy case. The SDK completes it, the entitlement flips to active, and you unlock the feature. Done. The work is everything that isn't success:

  • Declined or failed payment. Tell the user plainly, leave them where they were, don't lock them out of free features.
  • Cancelled. Someone tapping the system dialog's cancel is not an error. Treat it as a normal exit, no scary alert.
  • No network. Fail gracefully and let them retry. Don't pretend the purchase went through.

And the one people forget: restore purchases. When someone reinstalls or switches phones, they need a way to get their subscription back without paying twice. Apple effectively requires the button, and it's the kind of thing that generates angry reviews the moment it's missing. Put it on the paywall. Test it on a real reinstall.

Test in the sandbox, gate features cleanly

You can test the whole flow without spending real money. Both stores have sandbox environments with test accounts, and renewals run on a compressed clock so you can watch a "yearly" plan renew in minutes. Run every path through it: buy, cancel, restore, let it lapse. The sandbox is fiddly and occasionally lies to you, so test on a real device, not just the simulator.

For gating, resist scattering subscription checks everywhere. Read the entitlement in one place, expose a simple "is premium" value to the rest of the app, and let your UI react to that. When you change pricing or add a tier later, you change one thing, not fifty.

None of this is glamorous, but it's a few days of focused work, not a few weeks of building a billing system you'll regret. Get the products and entitlements modeled, lean on RevenueCat for the rest, and test the unhappy paths harder than the happy one. That's most of the battle.