Skip to main content
Question

App crashes on reopen from homescreen due to PurhcasesHybridCommon error

  • February 12, 2024
  • 9 replies
  • 689 views

Forum|alt.badge.img+1

When I open the app everything works fine but when i close it and reopen it from the home screen it crashes on TestFlight.

Here's the crash log of the thread its crashing on:

1Thread 0 name:
2Thread 0 Crashed:
30 libswiftCore.dylib 0x00000001847363fc _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
41 PurchasesHybridCommon 0x00000001019af1a4 closure #1 in variable initialization expression of static FatalErrorUtil.defaultFatalErrorClosure + 64 (FatalErrorUtil.swift:15)
52 PurchasesHybridCommon 0x00000001019a73a0 fatalError(_:file:line:) + 60 (FatalErrorUtil.swift:27)
63 PurchasesHybridCommon 0x00000001019a73a0 static CommonFunctionality.sharedInstance.getter + 84 (CommonFunctionality.swift:21)
74 PurchasesHybridCommon 0x00000001019a73a0 static CommonFunctionality.customerInfo(fetchPolicy:completion:) + 84 (<compiler-generated>:405)
85 PurchasesHybridCommon 0x00000001019a73a0 specialized static CommonFunctionality.customerInfo(completion:) + 372 (CommonFunctionality.swift:398)
96 PurchasesHybridCommon 0x00000001019a3964 @objc static CommonFunctionality.restorePurchases(completion:) + 76
107 purchases_flutter 0x0000000102cf5f2c -[PurchasesFlutterPlugin getCustomerInfoWithResult:] + 48 (PurchasesFlutterPlugin.m:347)
118 purchases_flutter 0x0000000102cf493c -[PurchasesFlutterPlugin handleMethodCall:result:] + 2048 (PurchasesFlutterPlugin.m:100)
129 Flutter 0x000000010342196c 0x102e40000 + 6166892
1310 Flutter 0x0000000102e83c00 0x102e40000 + 277504
1411 libdispatch.dylib 0x00000001933106a8 _dispatch_call_block_and_release + 32 (init.c:1530)
1512 libdispatch.dylib 0x0000000193312300 _dispatch_client_callout + 20 (object.m:561)
1613 libdispatch.dylib 0x0000000193320998 _dispatch_main_queue_drain + 984 (queue.c:7813)
1714 libdispatch.dylib 0x00000001933205b0 _dispatch_main_queue_callback_4CF + 44 (queue.c:7973)
1815 CoreFoundation 0x000000018b34cf9c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1780)
1916 CoreFoundation 0x000000018b349ca8 __CFRunLoopRun + 1996 (CFRunLoop.c:3149)
2017 CoreFoundation 0x000000018b3493f8 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420)
2118 GraphicsServices 0x00000001ce8d74f8 GSEventRunModal + 164 (GSEvent.c:2196)
2219 UIKitCore 0x000000018d76f8a0 -[UIApplication _run] + 888 (UIApplication.m:3685)
2320 UIKitCore 0x000000018d76eedc UIApplicationMain + 340 (UIApplication.m:5270)
2421 Runner 0x000000010030877c main + 64 (AppDelegate.swift:7)
2522 dyld 0x00000001ae09edcc start + 2240 (dyldMain.cpp:1269)
26

this is my main.dart code:

1import 'dart:io';
2
3import 'package:awesome_notifications/awesome_notifications.dart';
4import 'package:firebase_crashlytics/firebase_crashlytics.dart';
5import 'package:flutter/material.dart';
6import 'package:flutter_localizations/flutter_localizations.dart';
7import 'package:presentpal/api/constants.dart';
8import 'package:presentpal/api/firebase_api.dart';
9import 'package:presentpal/api/purchases_api.dart';
10import 'package:presentpal/pages/gift_chose.dart';
11import 'package:presentpal/pages/home.dart';
12import 'package:presentpal/provider/locale_provider.dart';
13import 'package:presentpal/services/analytics_service.dart';
14import 'package:shared_preferences/shared_preferences.dart';
15import 'provider/theme_provider.dart';
16import 'package:flutter_gen/gen_l10n/app_localizations.dart';
17import 'services/preferences_service.dart';
18import 'pages/change_notifier.dart' as presentpal;
19import 'package:provider/provider.dart';
20import 'package:firebase_core/firebase_core.dart';
21import 'package:google_mobile_ads/google_mobile_ads.dart';
22import 'pages/onboarding.dart';
23import 'package:flutter_dotenv/flutter_dotenv.dart';
24import 'store_config.dart';
25import 'package:purchases_flutter/purchases_flutter.dart';
26import 'package:presentpal/services/set_notifications.dart';
27import 'package:timezone/data/latest.dart' as tz;
28
29final navigatorKey = GlobalKey<NavigatorState>();
30
31
32Future<void> configureLocalTimeZone() async {
33 tz.initializeTimeZones();
34}
35
36Future<void> main() async {
37 WidgetsFlutterBinding.ensureInitialized();
38 presentpal.AppState appState = presentpal.AppState();
39 try {
40 MobileAds.instance.initialize();
41
42 //get user data
43 await Firebase.initializeApp();
44
45 FlutterError.onError = (errorDetails) {
46 FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
47 };
48
49 await FirebaseApi().initNotifications();
50
51 await setNotifications.initializeNotifications();
52
53 await configureLocalTimeZone();
54
55 String? initialLocale = await getLocale();
56 if (initialLocale != null && initialLocale.isNotEmpty) {
57 appState.setLocale(initialLocale);
58 }
59 try {
60 await dotenv.load(fileName: ".env");
61 } catch (e) {
62 print(e.toString());
63 }
64 if (Platform.isIOS) {
65 StoreConfig(
66 store: Store.appStore,
67 apiKey: appleApiKey,
68 );
69 } else if (Platform.isAndroid) {
70 StoreConfig(
71 store: Store.playStore,
72 apiKey: googleApiKey,
73 );
74 }
75 await PurchaseApi.init();
76 } catch (e) {
77 print(e.toString());
78 AnalyticsService analyticsService = AnalyticsService();
79 analyticsService.logEvent('error: ${e.toString()}');
80 }
81
82 runApp(
83 MultiProvider(
84 providers: [
85 ChangeNotifierProvider(create: (_) => LocaleModel()),
86 ],
87 child: MyApp(appState: appState),
88 ),
89 );
90}
91
92class MyApp extends StatefulWidget {
93 final presentpal.AppState appState;
94
95 const MyApp({super.key, required this.appState});
96
97 @override
98 State<MyApp> createState() => _MyAppState();
99}
100
101class _MyAppState extends State<MyApp> {
102 bool hasSeenOnboarding = false;
103
104 @override
105 void initState() {
106 super.initState();
107 _populateFields();
108 checkOnboardingStatus();
109 AwesomeNotifications().isNotificationAllowed().then((isAllowed) {
110 if (!isAllowed) {
111 AwesomeNotifications().requestPermissionToSendNotifications();
112 }
113 });
114 }
115
116 Future<void> checkOnboardingStatus() async {
117 final SharedPreferences prefs = await SharedPreferences.getInstance();
118 final bool seenOnboarding = prefs.getBool('hasSeenOnboarding') ?? false;
119 setState(() {
120 hasSeenOnboarding = seenOnboarding;
121 });
122 }
123
124 final _preferenceServices = PreferencesService();
125
126 String selectedLanguage = 'en';
127
128 void _populateFields() async {
129 final settings = await _preferenceServices.getSettings();
130 String? storedLanguage = settings.languageCode;
131 if (storedLanguage.isNotEmpty) {
132 setState(() {
133 selectedLanguage = storedLanguage;
134 });
135 widget.appState.setLocale(selectedLanguage);
136 } else {
137 String languageCode;
138 try {
139 //get locale from settings
140 Locale userLocale = WidgetsBinding.instance.window.locale;
141 languageCode = userLocale.languageCode;
142 if ([
143 'en',
144 'ar',
145 'bn',
146 'de',
147 'es',
148 'fr',
149 'hi',
150 'it',
151 'ko',
152 'pt',
153 'ru',
154 'zh',
155 'zh_HK'
156 ].contains(languageCode)) {
157 setState(() {
158 selectedLanguage = languageCode;
159 });
160 widget.appState.setLocale(selectedLanguage);
161 }
162 } catch (e) {
163 languageCode = 'en';
164 }
165 }
166 }
167
168 @override
169 Widget build(BuildContext context) {
170 return ChangeNotifierProvider(
171 create: (context) => LocaleProvider(),
172 builder: (context, child) {
173 final provider = Provider.of<LocaleProvider>(context);
174 return MaterialApp(
175 debugShowCheckedModeBanner: false,
176 title: 'PresentPal',
177 themeMode: ThemeMode.system,
178 theme: MyThemes.lightTheme,
179 navigatorObservers: [AnalyticsService().getAnalyticsObserver()],
180 navigatorKey: navigatorKey,
181 locale: provider.locale,
182 supportedLocales: const [
183 Locale('en'), // English
184 Locale('ar'), //Arabic
185 Locale('bn'), //Bengali
186 Locale('de'), //German
187 Locale('es'), //Spanish
188 Locale('fr'), //French
189 Locale('hi'), //Hindi
190 Locale('it'), //Italian
191 Locale('ko'), //Korean
192 Locale('pt'), //Portuguese
193 Locale('ru'), //Russian
194 Locale('zh'), //Chinese
195 Locale('zh_HK') //Chinese (Hong Kong)
196 ],
197 localizationsDelegates: const [
198 AppLocalizations.delegate,
199 GlobalMaterialLocalizations.delegate,
200 GlobalWidgetsLocalizations.delegate,
201 GlobalCupertinoLocalizations.delegate,
202 ],
203 darkTheme: MyThemes.darkTheme,
204 routes: {
205 '/home': (context) => const HomePage(),
206 '/onboarding': (context) => const OnboardingScreen(),
207 '/giftChoose': (context) => const GiftChoose(),
208 },
209 home:
210 hasSeenOnboarding ? const HomePage() : const OnboardingScreen(),
211 );
212 }
213 );
214 }
215}
216
217Future<void> setLocale(String languageCode) async {
218 final SharedPreferences preferences = await SharedPreferences.getInstance();
219 preferences.setString('languageCode', languageCode);
220}
221
222Future<String?> getLocale() async {
223 WidgetsFlutterBinding.ensureInitialized();
224 final SharedPreferences preferences = await SharedPreferences.getInstance();
225 return preferences.getString('languageCode');
226}
227
228class LocaleModel extends ChangeNotifier {
229 Locale? _locale;
230
231 Locale? get locale => _locale;
232
233 void set(Locale locale) {
234 _locale = locale;
235 notifyListeners();
236 }
237}
This post has been closed for comments

9 replies

Forum|alt.badge.img+3
  • RevenueCat Staff
  • 62 replies
  • February 12, 2024

Hi @cian-robertson-2bf7fd, thanks for reaching out.

Looking at the stack trace, I believe you might be calling `getCustomerInfo` or `restorePurchases` before calling `Purchases.configure`. Is it possible in one of your paths, you’re calling one of those methods before actually configuring the SDK?

 


Forum|alt.badge.img+1

I noticed i was using a static variable to avoid calling the configure method multiple times but it wont hold if the app is stopped so i might be calling the configure multiple times. I’m trying to see if using shared preferences will stop the error. Could this be the cause of the error?


Forum|alt.badge.img+1

Nevermind it doesn’t work, this is what I tried: 

static bool isConfigured = false;

  static Future init() async {
    try {
      PreferencesService preferencesService = PreferencesService();
      isConfigured = await preferencesService.getConfigurationStatus();
      await Purchases.setLogLevel(LogLevel.debug);
      if (isConfigured == false) {
        await Purchases.configure(
            PurchasesConfiguration(StoreConfig.instance.apiKey));
        isConfigured = true;
        await preferencesService.setConfigurationStatus(true);
      }
    } catch (e) {
      print(e);
    }
  }


Forum|alt.badge.img+1

this is the error log i get now in the xcode logs: 

```

Thread 0 name:
Thread 0 Crashed:
0   libswiftCore.dylib                0x00000001847363fc _assertionFailure(_:_:file:line:flags:) + 264 (AssertCommon.swift:144)
1   PurchasesHybridCommon             0x0000000106477d0c 0x106464000 + 81164
2   PurchasesHybridCommon             0x000000010646f3b4 0x106464000 + 46004
3   PurchasesHybridCommon             0x000000010646b980 0x106464000 + 31104
4   purchases_flutter                 0x00000001077f9f2c 0x1077f0000 + 40748
5   purchases_flutter                 0x00000001077f893c 0x1077f0000 + 35132
6   Flutter                           0x0000000107f2596c 0x107944000 + 6166892
7   Flutter                           0x0000000107987c00 0x107944000 + 277504
8   libdispatch.dylib                 0x00000001933106a8 _dispatch_call_block_and_release + 32 (init.c:1530)
9   libdispatch.dylib                 0x0000000193312300 _dispatch_client_callout + 20 (object.m:561)
10  libdispatch.dylib                 0x0000000193320998 _dispatch_main_queue_drain + 984 (queue.c:7813)
11  libdispatch.dylib                 0x00000001933205b0 _dispatch_main_queue_callback_4CF + 44 (queue.c:7973)
12  CoreFoundation                    0x000000018b34cf9c __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1780)
13  CoreFoundation                    0x000000018b349ca8 __CFRunLoopRun + 1996 (CFRunLoop.c:3149)
14  CoreFoundation                    0x000000018b3493f8 CFRunLoopRunSpecific + 608 (CFRunLoop.c:3420)
15  GraphicsServices                  0x00000001ce8d74f8 GSEventRunModal + 164 (GSEvent.c:2196)
16  UIKitCore                         0x000000018d76f8a0 -[UIApplication _run] + 888 (UIApplication.m:3685)
17  UIKitCore                         0x000000018d76eedc UIApplicationMain + 340 (UIApplication.m:5270)
18  Runner                            0x0000000104dd077c 0x104dc8000 + 34684
19  dyld                              0x00000001ae09edcc start + 2240 (dyldMain.cpp:1269)

```


Forum|alt.badge.img+3
  • RevenueCat Staff
  • 62 replies
  • February 13, 2024

Hi @cian-robertson-2bf7fd,

Calling configure multiple times is not recommended and could result in some undetermined behavior, it should only be called once on app open (not app foreground or other situations where memory is not released).

As for the latest trace, I’m not sure, since that crash is not symbolicated… But it could still be the same issue… Can you add some logs to make sure the configure is called only once on this scenario? (If you force close the app, it should be called again upon reopening it).

when i close it and reopen it from the home screen it crashes on TestFlight.

Do you mean when you force-close the app, or when you background it, then foreground it?

Also, a couple more questions:

  • What version of the SDK are you using?
  • Have you been able to test/reproduce in Android?

 


Forum|alt.badge.img+1

I mean when i force close the app and then open it again, opening it once backgrounded all works. I changed it so that the configuration is only called once within the apps lifecycle, however that did not resolve the problem, it got rid of the crash and error reports but the white screen still persists.

  • I have not been able to replicate it on android, it seems like its only an ios problem. 
  • I’m using the latest SDK:  purchases_flutter: ^6.21.0

Forum|alt.badge.img+3
  • RevenueCat Staff
  • 62 replies
  • February 14, 2024

Hi again @cian-robertson-2bf7fd,

Thanks for the info! After more research, it still seems to me that you are calling a method of the SDK BEFORE calling `Purchases.configure`.

Could you add some breakpoints/prints BEFORE calling all methods of RevenueCat’s SDK and reproduce again? You should make sure `configure` is called before any others. 

If you’re not able to find the issue, could you share the pieces of code where you call each method of the SDK and when you’re calling those?

Thanks again and sorry for the problems you’re having.

 


Forum|alt.badge.img+1

in my main.dart Future<void> main async{} method: 

```

try {

    PurchasesConfiguration configuration =

        PurchasesConfiguration(StoreConfig.instance.apiKey);

    await Purchases.configure(configuration);

    await PurchaseApi.identifyUser();

  } catch (e) {

    FirebaseCrashlytics.instance.log(e.toString());

  }

```

```

static Future<void> identifyUser() async {

    try {

      Auth auth = Auth();

      String userId = auth.userId?.toString() ?? '';

      if (userId.isNotEmpty) {

        await Purchases.logIn(userId);

      }

    } catch (e) {

      print(e);

    }

  }

```

I use this multiple times throughout screens to determine if users are subscribed

```

void initState() {

    super.initState();

    fetchSubscriptionStatus();

  }

 

  void fetchSubscriptionStatus() async {

    final PurchaseApi purchaseApi = PurchaseApi();

    bool subscriptionStatus = await purchaseApi.isPremium();

    setState(() {

      _isSubscribed = subscriptionStatus;

      //_isSubscribed = true;

      //pTesting

    });

  }

```

 

In my screen where user can purchase premium: 

```

Future fetchOffers(context) async {

    final offerings = await PurchaseApi.fetchOffers();

    Purchases.getOfferings();

    String price = await _premiumPrice;

 

    if (offerings.isEmpty) {

      ScaffoldMessenger.of(context).showSnackBar(

        SnackBar(

          showCloseIcon: true,

          content: Text(

              AppLocalizations.of(context)?.noPlansFound ?? 'No Plans Found'),

        ),

      );

      AnalyticsService().logEvent('error_in_retrieving_ffers');

    } else {

      final packages = offerings

          .map((offer) => offer.availablePackages)

          .expand((pair) => pair)

          .toList();

 

      AnalyticsService().logEvent('offers_retrieved');

 

      Utils.showSheet(

        context,

        (context) => PaywallWidget(

          packages: packages,

          title: AppLocalizations.of(context)?.joinPremium ??

              "Join PresentPal Premium",

          desciption: AppLocalizations.of(context)?.pricingMessage(price) ??

              'Unlock all premium features for just $price/month.',

          onClickedPackage: (package) async {

            await PurchaseApi.purchasePackage(package);

            if (!context.mounted) return;

            Navigator.pop(context);

          },

          footer: AppLocalizations.of(context)?.paywallFooter ??

              "Cancelations should be made at least 24 hours before the end of the current period. You can manage and cancel your subscriptions by going to your account settings. ",

          footerLink: AppLocalizations.of(context)?.paywallFooterLink ??

              'View Terms and Conditions',

        ),

      );

    }

  }

 

  Future<String> getPrice() async {

    try {

      final offerings = await PurchaseApi.fetchOffers();

      final packages = offerings

          .map((offer) => offer.availablePackages)

          .expand((pair) => pair)

          .toList();

 

      return packages[0].storeProduct.priceString;

    } catch (e) {

      return "\$4.99";

    }

  }

```

 

When a user logs in on login and sign up:

```

Future<void> logInPurchases() async {

    PurchaseApi.identifyUser();

  }

```

 

in settings:

```

Future<void> setCancelSubscriptionUrl() async {

    PurchaseApi purchaseApi = PurchaseApi();

    String? url = await purchaseApi.getSubscriptionManagementURL();

    setState(() {

      cancelSubscriptionURL = url.toString();

    });

  }

```

 

```

Future<String?> getSubscriptionManagementURL() async {

    try {

      final customerInfo = await Purchases.getCustomerInfo();

      if (customerInfo.managementURL != null) {

        return customerInfo.managementURL;

      } else {

        return '';

      }

    } catch (error) {

      print('Error fetching customer info: $error');

      return '';

    }

  }

```

 

```

Future<bool> isPremium() async {

    try {

      CustomerInfo customerInfo = await Purchases.getCustomerInfo();

      if (customerInfo.entitlements.all[entitlementID] != null &&

          customerInfo.entitlements.all[entitlementID]!.isActive == true) {

        return true;

      } else {

        return false;

      }

    } on PlatformException {

      return false;

    }

  }

```


Forum|alt.badge.img+3
  • RevenueCat Staff
  • 62 replies
  • February 14, 2024

Hi @cian-robertson-2bf7fd,

Is it possible the “await Purchases.configure(configuration);” is returning an error and getting swallowed by the try-catch around it? Can you confirm that is working correctly when you reproduce?


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