Architecture recap:
SubscriptionProviding is the seam (in AcornCore). The
default StoreKitSubscriptionProvider (StoreKit 2, zero dependencies) ships in
core; RevenueCatSubscriptionProvider ships in the optional AcornCoreRevenueCat
module. The flow gate (AppFlowCoordinator + AcornGateView), every paywall
(PlanListPaywall / SingleCTAPaywall), and the Settings status card talk to the
protocol — any SubscriptionProviding — so they are unchanged by the swap.Part A — RevenueCat setup (one-time, ~20 min)
A1. Create the project + get the API key
- app.revenuecat.com → + New Project. Add an App under it (Project Settings → Apps → + App Store), bind it to your App Store Connect app, and upload your App Store Connect In-App Purchase Key (.p8) so RevenueCat can read transactions.
- Copy the public SDK key for the Apple app (Project Settings → API Keys → “App-specific” / “Public” — starts with
appl_). This is the only value the app needs. It’s a public key, safe to ship in the client — never ship a secret API key.
A2. Create the entitlement
Project → Entitlements → + New → identifierpro. This is the single entitlement that unlocks the app. (Override the id in the initializer if you name it differently — see Step 2.)
A3. Create products + an offering mapped to the four plans
The provider maps RevenueCat package types to AcornCore’sSubscriptionPlan:
AcornCore SubscriptionPlan | RevenueCat package type |
|---|---|
.weekly | Weekly |
.monthly | Monthly |
.annual | Annual |
.lifetime | Lifetime |
- Products → import / add your App Store Connect products. Use the same id convention as the StoreKit path —
<prefix>.{weekly,monthly,yearly,lifetime}— so the tier resolves exactly (see Step 2’sproductIDPrefix). - Offerings → make a current offering and attach a package per plan you sell, each pointing at the matching product. The provider reads prices from the current offering’s packages and attaches each package’s entitlement on purchase.
Part B — App swap (one line)
This is the whole point of the protocol seam: only the composition root changes. The paywall, the gate, and Settings are typed againstany SubscriptionProviding, so
they compile and run untouched.
Add the dependency
The app already depends on
AcornCore. Add the AcornCoreRevenueCat product to
the app target (it pulls purchases-ios, ≥ 5.0). Local-package apps just add the
product; SPM apps get https://github.com/RevenueCat/purchases-ios.git transitively
via the module. Core stays dependency-free — only apps that opt in pull the SDK.Swap the provider in AppConfig.makeSubscriptions()
makeSubscriptions() already returns any SubscriptionProviding. Change the body
from the StoreKit provider to the RevenueCat one — nothing downstream changes:import AcornCoreRevenueCat at the top of AppConfig.swift. That, plus the SPM
product in Step 1, is the entire code change.Supply the key (keep it out of source)
Mirror the PostHog key pattern — read it from Info.plist so it isn’t hardcoded:Set
REVENUECAT_API_KEY in build settings / Info.plist (the public appl_… key is
safe to ship). That’s it — run the app; the paywall now shows RevenueCat prices and
purchases flow through RevenueCat.Optional — tie entitlements to a signed-in account
If you also ship auth (Auth setup), pass the user id so RevenueCat entitlements follow the account across devices/reinstalls:logIn) after sign-in so the id aligns.
Why this is genuinely one line
Every consumer of the provider —AppShell, PlanListPaywall, SingleCTAPaywall,
PaywallContent, RootTabView, HomeView, SettingsView — declares
subscriptions: any SubscriptionProviding, and the gate (AppFlowCoordinator) always
took the protocol. No call site references a StoreKit or RevenueCat type. So swapping
the return value in makeSubscriptions() is the only code edit; nothing else can break.
This is the protocol-seam selling point in action: choice, not lock-in.
Pre-ship checklist
- RevenueCat project + Apple app bound to App Store Connect; IAP key uploaded.
-
proentitlement created (or id overridden in the initializer). - Current offering has a package per plan you sell, mapped to the right products.
-
AcornCoreRevenueCatproduct added to the app target. -
makeSubscriptions()returnsRevenueCatSubscriptionProvider;REVENUECAT_API_KEYset in Info.plist (public key, not a secret key). - Live test in simulator/device: prices load, purchase a plan, the gate unlocks
paywall → main, restore purchases works, Settings shows the active tier. - If using auth:
appUserIDset to the account id after sign-in; entitlements follow the account.