Solved

Need clarification on how to use Purchases with iOS widgets

  • 6 February 2023
  • 9 replies
  • 499 views

Userlevel 2
Badge +3

Hello.  I want functionality in my iOS widgets to only be available for subscribers and it’s not clear at all to me how to accomplish that.

This page describes how to “enable data sharing between the main app and extensions”. So I’m assuming, but am not 100% sure, that means that Purchases.configure would be called by both the main and the widgets. And since they share a UserDefaults area they’ll know the same things. The SwiftUI Magic Weather app doesn’t seem to have widgets so I have nothing to go on. If this is correct where do I call Purchases.configure from the extension? The @main struct for WidgetBundle doesn’t seem to use a .init like a standard struct for a SwiftUI View. Maybe in an .onAppear attached to the WidgetEntryView?

 

I also have another question. In the scenario where the subscription expired BUT it auto-renewed would calling Purchases.shared.customerInfo() be sufficient for checking entitlements or is it a requirement to call Purchases.shared.getCustomerInfo instead? 

 

icon

Best answer by RomanLFB 14 February 2023, 16:02

View original

9 replies

Userlevel 4
Badge +6

Hey @Chris-29,

Yes, you’re correct, you will call configure in both the main app and the extension! I believe there's an entry point for widgets where you have to configure the framework. We don’t have any example code or an extension within the sample app, unfortunately. 

For your second question - you really can’t call getCustomerInfo() too often - it’s totally safe to call frequently and can be a good way to check the record in situations like the one you described. Typically, you can expect the customerInfo cache to update when you call getCustomerInfo, when a purchase is made, and when purchases are restored.

Userlevel 2
Badge +3

For your second question - you really can’t call getCustomerInfo() too often - it’s totally safe to call frequently and can be a good way to check the record in situations like the one you described. Typically, you can expect the customerInfo cache to update when you call getCustomerInfo, when a purchase is made, and when purchases are restored.

 

Thanks kaitlin.  My question was actually would it be sufficient to call customerInfo() *instead* of getCustomerInfo(). I ended up finding a way to call getCustomerInfo() that I was happy with so I ended up not using customerInfo() like I had planned.

Badge +3

For your second question - you really can’t call getCustomerInfo() too often - it’s totally safe to call frequently and can be a good way to check the record in situations like the one you described. Typically, you can expect the customerInfo cache to update when you call getCustomerInfo, when a purchase is made, and when purchases are restored.

 

Thanks kaitlin.  My question was actually would it be sufficient to call customerInfo() *instead* of getCustomerInfo(). I ended up finding a way to call getCustomerInfo() that I was happy with so I ended up not using customerInfo() like I had planned.

I believe customerInfor() is just the async version of getCursomerInfo(), the latter relies on a completionHandler for when you’re not in an asynchronous piece of code.

I’m curious however how you got this working in a widget? I’ve setup Purchases as described in this page but I’m not sure where to setup Purchases in my widget code. I’ve tried in getTimeline() but when querying customerInfo, it does not seem to get the right data.

Userlevel 2
Badge +3

 

I’m curious however how you got this working in a widget? I’ve setup Purchases as described in this page but I’m not sure where to setup Purchases in my widget code. I’ve tried in getTimeline() but when querying customerInfo, it does not seem to get the right data.

 

I didn’t want to put it in getTimeline() because the documentation says this:

You should only configure Purchases once, usually early in your application lifecycle. 

That doesn’t describe getTimeline(), which is called multiple time in a widget lifecycle. I didn’t feel comfortable doing any of this without an example of how to do it. I have my main app determine what the subscription expiration date is and my widgets are just going off of that.

Badge +3

I didn’t want to put it in getTimeline() because the documentation says this:

You should only configure Purchases once, usually early in your application lifecycle. 

That doesn’t describe getTimeline(), which is called multiple time in a widget lifecycle. I didn’t feel comfortable doing any of this without an example of how to do it. I have my main app determine what the subscription expiration date is and my widgets are just going off of that.

 

I see, I’ve managed to retrieve the data in my widget and thought the init of my widget’s IntentTimelineProvider would be a safer place to configure Purchases. But I’m not even sure that’s safe either since I have multiple widgets that are likely to call their own providers. Some code example from RevenueCat would be great here!

Userlevel 2
Badge +3

I see, I’ve managed to retrieve the data in my widget and thought the init of my widget’s IntentTimelineProvider would be a safer place to configure Purchases. But I’m not even sure that’s safe either since I have multiple widgets that are likely to call their own providers. Some code example from RevenueCat would be great here!

Yeah, the provider stuff can get hairy really quickly. Say I have widget A with size medium and small installed and widget B with size large and rectangular installed. When I added logging I saw that meant there were FOUR calls to getTimeline() at the same time (one for each size of each widget).

Badge

If I understand this all correctly, I shouldn’t need to call `Purhcases.configure()` from my extension, so long as I properly configure it in the main app as is described here: https://www.revenuecat.com/docs/ios-app-extensions. Is that correct? If so, calling `Purchases.shared.getCustomerInfo()` should work properly from within the App extension?

Badge

If I understand this all correctly, I shouldn’t need to call `Purhcases.configure()` from my extension, so long as I properly configure it in the main app as is described here: https://www.revenuecat.com/docs/ios-app-extensions. Is that correct? If so, calling `Purchases.shared.getCustomerInfo()` should work properly from within the App extension?

 

When I attempt this, I get a fatal error: Purchases has not been configured. Please call Purchases.configure(). 

That seems to be in direct conflict with the advice to only configure `Purchases` once. I assume I also need to configure an instance from my extension?

Badge +2

I have found success calling Purchases.configure, and Purchases.shared.getCustomerInfo within my widgets’ TimelineProvider.timeline() functions. I do configure Purchases to use the shared UserDefaults as their docs recommend for iOS extensions (Chris-free’s link above).

As has been noted above, this results in a lot of calls to Purchases.configure (one per widget, per widget timeline refresh). It’s working in my test project, but it would be nice to confirm with RevenueCat staff that this isn’t unwise. I don’t see any alternative as I don’t see any place that Purchases could persist in memory between timeline() calls.

Reply