
Upgrading purchases_flutter? Here's every breaking change that'll bite you (and how I fixed each one)
I run a subscription-based AI chatbot app built with Flutter, and last week I bumped purchases_flutter to pick up the newer Customer Center UI. What followed was a chain of three separate breaking changes — a type signature change, a CocoaPods version lock conflict, and a platform support gap — that took me most of an afternoon to untangle. Writing this down so the next person (probably future-me) doesn't have to.
1. purchasePackage() no longer returns CustomerInfo
The first thing that broke was compile-time, which is at least the friendly kind of broken:
A value of type 'PurchaseResult' can't be assigned to a variable of type 'CustomerInfo'.
Older versions of the SDK had Purchases.purchasePackage() return a CustomerInfo object directly. Newer versions wrap it in a PurchaseResult, which also carries transaction metadata alongside the customer info. The fix is a one-line change — pull .customerInfo off the result instead of assigning the whole thing:
1Future<void> purchasePackage(BuildContext context) async {
2 if (selectedPackage != null) {
3 setBusyForObject(purchasing, true);
4 try {
5 final PurchaseResult result =
6 await Purchases.purchasePackage(selectedPackage!);
7 final CustomerInfo purchaserInfo = result.customerInfo;
8 setBusyForObject(purchasing, false);
9
10 if (purchaserInfo.entitlements.active.entries
11 .any((element) => element.value.isActive)) {
12 successfullySubscribed = true;
13 SharedModel.shared.customerPurchaseInfo = purchaserInfo;
14 SharedModel.shared.notifyListeners();
15 notifyListeners();
16 Navigator.pop(context);
17 }
18 } on PlatformException catch (e) {
19 setError(e.message);
20 setBusyForObject(purchasing, false);
21 }
22 }
23}Worth doing: grep your whole codebase for every purchase call site before you assume you're done — I had this pattern duplicated across two separate paywall flows and would have shipped one of them broken if I'd only fixed the first hit.
1grep -rn "Purchases.purchasePackage\|Purchases.purchaseStoreProduct" lib/2. CocoaPods can't resolve PurchasesHybridCommon
Next, running on macOS threw this:
1[!] CocoaPods could not find compatible versions for pod "PurchasesHybridCommon":
2 In snapshot (Podfile.lock):
3 PurchasesHybridCommon (= 13.29.1)
4 In Podfile:
5 purchases_flutter (from `Flutter/ephemeral/.symlinks/plugins/purchases_flutter/macos`) was resolved to 10.9.0, which depends on
6 PurchasesHybridCommon (= 18.30.0)This is a stale Podfile.lock problem, not a real incompatibility. Your lockfile still has the old dependency resolution pinned from before the upgrade, and CocoaPods won't just silently override it — it wants you to explicitly regenerate the lock.
1cd macos # or ios
2rm Podfile.lock
3rm -rf Pods
4pod repo update
5pod installThe pod repo update step matters more than it looks — without it, your local CocoaPods spec repo cache might not even have the newer PurchasesHybridCommon version indexed, and you'll get a confusing "found but incompatible" error even after clearing the lockfile.
If you still get a conflict after that, read the error message carefully — it usually also says the newer pod "requires a higher minimum deployment target." Check two places:
1# macos/Podfile — near the top
2platform :osx, '10.15' # bump this if CocoaPods complainsAnd match it in Xcode: select the Runner target → Build Settings → macOS Deployment Target.
3. Customer Center doesn't work on every platform
The last one wasn't a crash — it was a MissingPluginException:
MissingPluginException(No implementation found for method clearCustomerCenterCallbacks on channel purchases_ui_flutter)
I'd wired up RevenueCatUI.presentCustomerCenter() to let subscribed users manage/cancel their plan from an in-app settings screen. It worked on iOS, threw this on macOS. Turns out purchases_ui_flutter's Customer Center implementation isn't uniformly supported across every platform the base purchases_flutter package supports — check the plugin's own pubspec.yaml for its platforms: declaration before assuming feature parity:
bash
cat ~/.pub-cache/hosted/pub.dev/purchases_ui_flutter-*/pubspec.yaml
The pragmatic fix is just to gate the entry point:
1mport 'dart:io' show Platform;
2
3Future<void> _openCustomerCenter() async {
4 if (Platform.isMacOS) {
5 ScaffoldMessenger.of(context).showSnackBar(
6 const SnackBar(
7 content: Text(
8 "Subscription management isn't available on macOS yet — "
9 "please use the iOS or Android app.",
10 ),
11 ),
12 );
13 return;
14 }
15 setState(() => _opening = true);
16 try {
17 await RevenueCatUI.presentCustomerCenter();
18 } finally {
19 if (mounted) setState(() => _opening = false);
20 }
21}Not glamorous, but it beats a runtime crash on launch, and one clean version bump later (once RevenueCat ships macOS support) you delete four lines and you're done.
The general lesson
None of these three were hard individually — the type change was a one-liner, the pod conflict was a clean-and-reinstall, and the platform gap was a guard clause. What actually cost the afternoon was not knowing which of the three I was looking at from the error message alone. A MissingPluginException looks identical whether it's a stale build cache, a genuinely unsupported platform, or a plugin registration bug — the only way to tell them apart is process of elimination: full clean rebuild first (rules out cache), then check the plugin's own platform declarations (rules out unsupported platform), and only then start suspecting your own code.
If you're mid-upgrade right now: do the clean rebuild first, before you start reading GitHub issues. It's the fastest way to rule out half the possible causes in one shot.



