Skip to main content
Question

Error Fetching Offerings Post-Flutter Upgrade: Null Type Cast Exception


Forum|alt.badge.img
  • New Member
  • 3 replies

After upgrading our Flutter environment from version 3.13.8 to 3.19.3, we encountered a persistent error when fetching offerings from RevenueCat using the purchases_flutter plugin. The application throws a type cast exception, specifically type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast, during the offerings fetch operation.

 

Details

  • Flutter Version: Upgraded from 3.13.8 to 3.19.3.
  • purchases_flutter Version: Initially encountered on version 6.21.0; upgraded to 6.24.0 in an attempt to resolve the issue, but the error persists.
  • Error Message: type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast
  • Error Location: The error occurs within the try-catch block around the Purchases.getOfferings() call, indicating an issue with handling the fetched offerings data.
  • Observations:
    • The debug logs indicate that the getOfferings call successfully contacts RevenueCat and receives a response, but fails during JSON deserialization or handling within the plugin.
    • The issue arose immediately following the Flutter upgrade.

 

Code

1Future<void> updateAvailablePlans() async {
2 _logVerboseLevelMessage('Updating Available Plans');
3 purchasesUpdating.value = true;
4
5 Offerings? offerings;
6 try {
7 _logVerboseLevelMessage('Getting Offerings from RevenueCat');
8 offerings = await Purchases.getOfferings();
9 _logVerboseLevelMessage('Offerings: $offerings');
10 } on PlatformException catch (e) {
11 debugPrint('Offerings Error: $e');
12 Get.customSnackBar(
13 status: StatusType.error,
14 compact: true,
15 title: e.message ?? 'Unknown error',
16 );
17 allAvailablePlans.clear();
18 return;
19 } catch (e) {
20 _logErrorLevelMessage('Error getting Offerings: $e', e, null);
21 Get.customSnackBar(
22 status: StatusType.error,
23 compact: true,
24 title: e.toString(),
25 );
26 allAvailablePlans.clear();
27 return;
28 }
29
30 purchasesUpdating.value = false;
31
32 // .... Rest of the code
33 }


Logs

1D/[Purchases] - DEBUG(25790): ℹ️ Debug logging enabled
2D/[Purchases] - DEBUG(25790): ℹ️ SDK Version - 7.5.2
3D/[Purchases] - DEBUG(25790): ℹ️ Package name - com.deepklarity.storia
4D/[Purchases] - DEBUG(25790): 👤 Initial App User ID: {Omitted}
5D/[Purchases] - DEBUG(25790): ℹ️ Purchases configured with response verification: DISABLED
6D/[Purchases] - DEBUG(25790): 👤 Identifying App User ID: {Omitted}
7D/[Purchases] - DEBUG(25790): ℹ️ Deleting old synced subscriber attributes that don't belong to {Omitted}
8D/[Purchases] - DEBUG(25790): ℹ️ App foregrounded
9D/[Purchases] - DEBUG(25790): ℹ️ CustomerInfo cache is stale, updating from network in foreground.
10D/[Purchases] - DEBUG(25790): Retrieving customer info with policy: FETCH_CURRENT
11D/[Purchases] - DEBUG(25790): ℹ️ Updating pending purchase queue
12D/[Purchases] - DEBUG(25790): ℹ️ Offerings cache is stale, updating from network in foreground
13D/[Purchases] - DEBUG(25790): 😻 Start Offerings update from network.
14D/[Purchases] - DEBUG(25790): ℹ️ Querying purchases
15D/[Purchases] - DEBUG(25790): Request already scheduled with jitter delay, adding existing callbacks to unjittered request with key: BackgroundAwareCallbackCacheKey(cacheKey=[/subscribers/{Omitted}/offerings], appInBackground=false)
16D/[Purchases] - DEBUG(25790): ℹ️ Updating pending purchase queue
17D/[Purchases] - DEBUG(25790): ℹ️ No subscriber attributes to synchronize.
18D/[Purchases] - DEBUG(25790): ℹ️ Listener set
19D/[Purchases] - DEBUG(25790): ℹ️ Sending latest CustomerInfo to listener.
20D/[Purchases] - DEBUG(25790): ℹ️ Starting connection for com.android.billingclient.api.BillingClientImpl@af4104a
21D/[Purchases] - DEBUG(25790): ℹ️ Ending connection for com.android.billingclient.api.BillingClientImpl@2285499
22D/[Purchases] - DEBUG(25790): ℹ️ Billing Service Setup finished for com.android.billingclient.api.BillingClientImpl@af4104a
23D/[Purchases] - DEBUG(25790): ℹ️ Updating pending purchase queue
24D/[Purchases] - DEBUG(25790): Retrieving customer info with policy: CACHED_OR_FETCHED
25D/[Purchases] - DEBUG(25790): ℹ️ Vending CustomerInfo from cache.
26D/[Purchases] - DEBUG(25790): ℹ️ Checking if cache is stale AppInBackground false
27D/[Purchases] - DEBUG(25790): ℹ️ Syncing purchases
28D/[Purchases] - DEBUG(25790): ℹ️ Querying purchase history for type subs
29D/[Purchases] - DEBUG(25790): ℹ️ Cleaning previously sent tokens
30D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
31D/[Purchases] - DEBUG(25790): ℹ️ Saving tokens []
32D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
33D/[Purchases] - DEBUG(25790): ℹ️ No pending purchases to sync
34D/[Purchases] - DEBUG(25790): Request already scheduled with jitter delay, adding existing callbacks to unjittered request with key: BackgroundAwareCallbackCacheKey(cacheKey=[/subscribers/{Omitted}], appInBackground=false)
35D/[Purchases] - DEBUG(25790): Retrieving customer info with policy: CACHED_OR_FETCHED
36D/[Purchases] - DEBUG(25790): ℹ️ Vending CustomerInfo from cache.
37D/[Purchases] - DEBUG(25790): ℹ️ Checking if cache is stale AppInBackground false
38D/[Purchases] - DEBUG(25790): ℹ️ No cached Offerings, fetching from network
39D/[Purchases] - DEBUG(25790): 😻 Start Offerings update from network.
40D/[Purchases] - DEBUG(25790): Same call already in progress, adding to callbacks map with key: BackgroundAwareCallbackCacheKey(cacheKey=[/subscribers/{Omitted}/offerings], appInBackground=false)
41D/[Purchases] - DEBUG(25790): Request already scheduled with jitter delay, adding existing callbacks to unjittered request with key: BackgroundAwareCallbackCacheKey(cacheKey=[/subscribers/{Omitted}/offerings], appInBackground=false)
42D/[Purchases] - DEBUG(25790): Billing connected with country code: IN
43D/TrafficStats(25790): tagSocket(225) with statsTag=0xffffffff, statsUid=-1
44W/WindowOnBackDispatcher(25790): OnBackInvokedCallback is not enabled for the application.
45W/WindowOnBackDispatcher(25790): Set 'android:enableOnBackInvokedCallback="true"' in the application manifest.
46D/[Purchases] - DEBUG(25790): API request started: GET /subscribers/{Omitted}/offerings
47D/[Purchases] - DEBUG(25790): API request completed with status: GET /subscribers/{Omitted}/offerings 304
482
49D/[Purchases] - DEBUG(25790): ℹ️ Requesting products from the store with identifiers: storia_premium, storia_topup_con_199
502
51D/[Purchases] - DEBUG(25790): ℹ️ Querying purchases
52D/[Purchases] - DEBUG(25790): ℹ️ Cleaning previously sent tokens
53D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
54D/[Purchases] - DEBUG(25790): ℹ️ Saving tokens []
55D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
56D/[Purchases] - DEBUG(25790): ℹ️ No pending purchases to sync
57D/[Purchases] - DEBUG(25790): ℹ️ Cleaning previously sent tokens
58D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
59D/[Purchases] - DEBUG(25790): ℹ️ Saving tokens []
60D/[Purchases] - DEBUG(25790): ℹ️ Tokens already posted: []
61D/[Purchases] - DEBUG(25790): ℹ️ No pending purchases to sync
62D/[Purchases] - DEBUG(25790): ℹ️ Products request finished for storia_premium, storia_topup_con_199
63D/[Purchases] - DEBUG(25790): ℹ️ Requesting products from the store with identifiers: storia_topup_con_199
64D/[Purchases] - DEBUG(25790): ℹ️ Products request finished for storia_topup_con_199
65D/[Purchases] - DEBUG(25790): 💰 Retrieved productDetailsList: ProductDetails{jsonString='{"productId":"storia_topup_con_199","type":"inapp","title":"TopUp (Storia - AI generated stories)","name":"TopUp","description":"TopUp","localizedIn":["en-US"],"skuDetailsToken":"AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=","oneTimePurchaseOfferDetails":{"priceAmountMicros":180000000,"priceCurrencyCode":"INR","formattedPrice":"₹180.00"}}', parsedJson={"productId":"storia_topup_con_199","type":"inapp","title":"TopUp (Storia - AI generated stories)","name":"TopUp","description":"TopUp","localizedIn":["en-US"],"skuDetailsToken":"AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=","oneTimePurchaseOfferDetails":{"priceAmountMicros":180000000,"priceCurrencyCode":"INR","formattedPrice":"₹180.00"}}, productId='storia_topup_con_199', productType='inapp', title='TopUp (Storia - AI generated stories)', productDetailsToken='AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=', subscriptionOfferDetails=null}
662
67D/[Purchases] - DEBUG(25790): 💰 storia_topup_con_199 - ProductDetails{jsonString='{"productId":"storia_topup_con_199","type":"inapp","title":"TopUp (Storia - AI generated stories)","name":"TopUp","description":"TopUp","localizedIn":["en-US"],"skuDetailsToken":"AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=","oneTimePurchaseOfferDetails":{"priceAmountMicros":180000000,"priceCurrencyCode":"INR","formattedPrice":"₹180.00"}}', parsedJson={"productId":"storia_topup_con_199","type":"inapp","title":"TopUp (Storia - AI generated stories)","name":"TopUp","description":"TopUp","localizedIn":["en-US"],"skuDetailsToken":"AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=","oneTimePurchaseOfferDetails":{"priceAmountMicros":180000000,"priceCurrencyCode":"INR","formattedPrice":"₹180.00"}}, productId='storia_topup_con_199', productType='inapp', title='TopUp (Storia - AI generated stories)', productDetailsToken='AEuhp4JGY4oM_SKIBDBFHhLlW741aOpnE0ho-4WBrg6sV2zuuAMgxsSjQ3rMm-iz0BM=', subscriptionOfferDetails=null}
68
69D/[Purchases] - DEBUG(25790): ℹ️ Building offerings response with 2 products
70I/flutter (25790): ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
71I/flutter (25790): │ type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast
72I/flutter (25790): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
73I/flutter (25790): │ #0 LoggerHelper.logErrorLevelMessage (package:storia/logic/services/misc_helpers/logger_helper.dart:33:13)
74I/flutter (25790): │ #1 PurchasesHelper._logErrorLevelMessage (package:storia/logic/services/purchases_helpers/purchases_helper.dart:386:10)
75I/flutter (25790): │ #2 PurchasesHelper.updateAvailablePlans (package:storia/logic/services/purchases_helpers/purchases_helper.dart:309:7)
76I/flutter (25790): │ #3 <asynchronous suspension>
77I/flutter (25790): │ #4 PurchasesHelper._customerUpdateListener (package:storia/logic/services/purchases_helpers/purchases_helper.dart:63:5)
78I/flutter (25790): │ #5 <asynchronous suspension>
79I/flutter (25790): ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
80I/flutter (25790): │ ⛔ Error getting Offerings: type 'Null' is not a subtype of type 'Map<dynamic, dynamic>' in type cast
81I/flutter (25790): └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
82D/[Purchases] - DEBUG(25790): API request started: GET /subscribers/6VhC9mt2AFYPWg7puz2nqnzrbqR2
83D/[Purchases] - DEBUG(25790): API request completed with status: GET /subscribers/6VhC9mt2AFYPWg7puz2nqnzrbqR2 304
84D/[Purchases] - DEBUG(25790): 😻 CustomerInfo updated from network.
85D/[Purchases] - DEBUG(25790): ℹ️ Purchase history is empty.
86D/[Purchases] - DEBUG(25790): ℹ️ Querying purchase history for type inapp
87D/[Purchases] - DEBUG(25790): ℹ️ Purchase history is empty.
88D/[Purchases] - DEBUG(25790): Retrieving customer info with policy: CACHED_OR_FETCHED
89D/[Purchases] - DEBUG(25790): ℹ️ Vending CustomerInfo from cache.
90D/[Purchases] - DEBUG(25790): ℹ️ Checking if cache is stale AppInBackground false
91D/ScrollOptim [SceneManager](25790): updateCurrentActivity: mCurrentActivityName=null, isOptEnable=true, isAnimAheadEnable=true, isFrameInsertEnable=true, InsertNum=1, isEnabledForScrollChanged=false

 

This post has been closed for comments

6 replies

Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • March 15, 2024

I have conducted further investigation to pinpoint the exact location and cause of the error we're experiencing when fetching offerings. The error occurs during the deserialization process in the Offerings.fromJson method, specifically when handling the availablePackages within our all.current offering.

 

Debugging Process and Findings:

  • Invocation of getOfferings Method: I added print statements to trace the execution flow and found that the error is emitted during the execution of Offerings.fromJson(Map<String, dynamic>.from(res)) in getOfferings method:
1static Future<Offerings> getOfferings() async {
2 print('getOfferings Called');
3 final res = await _channel.invokeMethod('getOfferings');
4 final enc = json.encode(res);
5 log('enc: $enc');
6 return Offerings.fromJson(
7 Map<String, dynamic>.from(res),
8 );
9}
10
  • Serialized Data Structure: The error occurs specifically when attempting to serialize the available packages inside our all.current offering. I observed the data structure of res right before serialization:
1{
2 "all": {
3 "current": {
4 "identifier": "current",
5 "serverDescription": "The standard set of packages",
6 "metadata": {},
7 "availablePackages": [
8 {
9 "identifier": "$rc_annual",
10 "packagesType": "ANNUAL",
11 "product": {...},
12 "offeringIdentifier": "current"
13 },
14 {
15 "identifier": "$rc_monthly",
16 "packagesType": "MONTHLY",
17 "product": {...},
18 "offeringIdentifier": "current"
19 }
20 ],
21 "lifetime": null,
22 "annual": {...},
23 "sixMonth": null,
24 "threeMonth": null,
25 "twoMonth": null,
26 "monthly": {...},
27 "weekly": null
28 }
29 },
30 "current": {...}
31}
32
  • Mismatch in Expected Data Structure: The availablePackages key within res contains 4 keys: identifier, packagesType, product, and offeringIdentifier. However, the serializer expects the structure to include presentedOfferingContext instead of offeringIdentifier. This discrepancy between expected and received data structures is the root cause of the serialization error.
  • The serialization logic in our code attempts to create a PackageImpl object from JSON data. However, it fails because it expects a presentedOfferingContext key that is not present in the incoming JSON. Here's the relevant part of the serialization logic:
1_$PackageImpl _$$PackageImplFromJson(Map json) => _$PackageImpl(
2 json['identifier'] as String,
3 $enumDecode(_$PackageTypeEnumMap, json['packageType'],
4 unknownValue: PackageType.unknown),
5 StoreProduct.fromJson(Map<String, dynamic>.from(json['product'] as Map)),
6 PresentedOfferingContext.fromJson(
7 Map<String, dynamic>.from(json['presentedOfferingContext'] as Map)),
8);
9
10Map<String, dynamic> _$$PackageImplToJson(_$PackageImpl instance) =>
11 <String, dynamic>{
12 'identifier': instance.identifier,
13 'packageType': _$PackageTypeEnumMap[instance.packageType]!,
14 'product': instance.storeProduct.toJson(),
15 'presentedOfferingContext': instance.presentedOfferingContext.toJson(),
16 };
17
  • The attempt to deserialize presentedOfferingContext from the JSON data fails because the actual JSON contains offeringIdentifier instead, leading to the error from:
    1PresentedOfferingContext.fromJson(
    2 Map<String, dynamic>.from(json['presentedOfferingContext'] as Map)),
    3

 


I have provided as many details as I could from my end. If anything else is required to solve this issue please let me know.


Forum|alt.badge.img+8
  • RevenueCat Staff
  • 674 replies
  • March 22, 2024

Hi, thank you for this detailed information! The team and I are investigating this and will let you know if we need any other info and what we find.


Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • March 22, 2024

Hey Haley, thanks for checking into that issue for us. Just an update - we've gone ahead and switched back to using an older version of the package in our app, specifically 5.8.2, alongside the new Flutter SDK version 3.19.3 for the time being.


Forum|alt.badge.img+8
  • RevenueCat Staff
  • 674 replies
  • April 9, 2024

Hi, so sorry for the delay! Can you let us know the following:

  • Does the issue happens in iOS, Android or both (logs are from android so I imagine it happens on android at least)
  • Can you try with a clean build flutter clean and (assuming android) cd android && ./gradlew clean and to try to remove any caches you might be using?
  • Can you also try after updating to our newest Flutter 6.25.0 and let us know if the issue persists?  https://github.com/RevenueCat/purchases-flutter/releases/tag/6.25.0

 


Forum|alt.badge.img+4
  • Helper
  • 3 replies
  • July 14, 2024
Haley Pace wrote:

Hi, so sorry for the delay! Can you let us know the following:

  • Does the issue happens in iOS, Android or both (logs are from android so I imagine it happens on android at least)
  • Can you try with a clean build flutter clean and (assuming android) cd android && ./gradlew clean and to try to remove any caches you might be using?
  • Can you also try after updating to our newest Flutter 6.25.0 and let us know if the issue persists?  https://github.com/RevenueCat/purchases-flutter/releases/tag/6.25.0

 

Running cd android && ./gradlew clean solved the issue for me. I am using Flutter 3.19.6 and purchases_flutter: 6.30.1.


Forum|alt.badge.img
  • Author
  • New Member
  • 3 replies
  • July 31, 2024
Haley Pace wrote:

Hi, so sorry for the delay! Can you let us know the following:

  • Does the issue happens in iOS, Android or both (logs are from android so I imagine it happens on android at least)
  • Can you try with a clean build flutter clean and (assuming android) cd android && ./gradlew clean and to try to remove any caches you might be using?
  • Can you also try after updating to our newest Flutter 6.25.0 and let us know if the issue persists?  https://github.com/RevenueCat/purchases-flutter/releases/tag/6.25.0

 

We update to version 6.30.x and this fixed it for both Android and iOS.


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings