purchases-flutter icon indicating copy to clipboard operation
purchases-flutter copied to clipboard

There is no singleton instance, even if it is configured

Open hirosz opened this issue 1 year ago • 2 comments

‼️ Required data ‼️

Do not remove any of the steps from the template below. If a step is not applicable to your issue, please leave that step empty.

There are a lot of things that can contribute to things not working. Having a very basic understanding of your environment will help us understand your issue faster!

Environment

  • [x] Output of flutter doctor [✓] Flutter (Channel stable, 3.24.3, on macOS 14.6.1 23G93 darwin-arm64, locale pl-PL) [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) [✓] Xcode - develop for iOS and macOS (Xcode 16.0) [✓] Chrome - develop for the web [!] Android Studio (version unknown) ✗ Unable to determine Android Studio version. [✓] VS Code (version 1.94.1) [✓] Connected device (5 available) [✓] Network resources
  • [x] Version of purchases-flutter : 8.1.6, purchases-ui-flutter : 8.1.6
  • [ ] Testing device version e.g.: different versions of Android
  • [x] How often the issue occurs- every one of your customers is impacted? Only in dev? It is in production version. Not all users
  • [x] Debug logs that reproduce the issue
  • [ ] Steps to reproduce, with a description of expected vs. actual behavior Other information (e.g. stacktraces, related issues, suggestions how to fix, links for us to have context, eg. stackoverflow, etc.)

Describe the bug

RevenueCat Paywall make sometimes error:

Exception wf.f0: There is no singleton instance. Make sure you configure Purchases before trying to get the default instance. More info here: https://errors.rev.cat/configuring-sdk
  at com.revenuecat.purchases.Purchases$Companion.getSharedInstance (Purchases.java)
  at com.revenuecat.purchases.ui.revenuecatui.data.PurchasesImpl.<init> (PurchasesImpl.java:3)
  at com.revenuecat.purchases.ui.revenuecatui.data.PaywallViewModelImpl.<init> (PaywallViewModelImpl.java:2)
  at com.revenuecat.purchases.ui.revenuecatui.data.PaywallViewModelFactory.create (PaywallViewModelFactory.java:1)
  at androidx.lifecycle.ViewModelProvider$Factory.create (ViewModelProvider.java:2)
  at androidx.lifecycle.ViewModelProvider.get (ViewModelProvider.java:1)
  at androidx.lifecycle.viewmodel.compose.ViewModelKt.get (ViewModel.kt:1)
  at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel (ViewModel.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt.getPaywallViewModel (InternalPaywall.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt.InternalPaywall (InternalPaywall.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.PaywallKt.Paywall (Paywall.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivity$onCreate$1$1$1.invoke (PaywallActivity.java:2)
  at com.revenuecat.purchases.ui.revenuecatui.activity.PaywallActivity$onCreate$1$1$1.invoke (PaywallActivity.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:2)
  at androidx.compose.material3.ScaffoldKt$ScaffoldLayoutWithMeasureFix$1$1$bodyContentPlaceables$1.invoke (Scaffold.kt:2)
  at androidx.compose.material3.ScaffoldKt$ScaffoldLayoutWithMeasureFix$1$1$bodyContentPlaceables$1.invoke (Scaffold.kt:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$3$1$1.invoke (LayoutNodeSubcompositionsState.java:2)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$subcompose$3$1$1.invoke (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable (ActualJvm_jvm.kt:1)
  at androidx.compose.runtime.ComposerImpl.doCompose (ComposerImpl.java:1)
  at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release (ComposerImpl.java:1)
  at androidx.compose.runtime.CompositionImpl.composeContent (CompositionImpl.java:1)
  at androidx.compose.runtime.Recomposer.composeInitial$runtime_release (Recomposer.java:1)
  at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release (ComposerImpl.java:1)
  at androidx.compose.runtime.CompositionImpl.composeInitial (CompositionImpl.java:1)
  at androidx.compose.runtime.CompositionImpl.setContent (CompositionImpl.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.material3.ScaffoldKt$ScaffoldLayoutWithMeasureFix$1$1.invoke-0kLqBqw (Scaffold.kt:1)
  at androidx.compose.material3.ScaffoldKt$ScaffoldLayoutWithMeasureFix$1$1.invoke (Scaffold.kt:1)
  at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s (LayoutNodeSubcompositionsState.java:1)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:2)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.foundation.layout.BoxMeasurePolicy.measure-3p2s80s (BoxMeasurePolicy.java:1)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.java:1)
  at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s (SimpleGraphicsLayerModifier.java:1)
  at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0 (LayoutModifierNodeCoordinator.java:1)
  at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s (SimpleGraphicsLayerModifier.java:1)
  at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0 (LayoutModifierNodeCoordinator.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:2)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.measure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.layout.RootMeasurePolicy.measure-3p2s80s (RootMeasurePolicy.java:1)
  at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0 (InnerNodeCoordinator.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:2)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.runtime.snapshots.Snapshot$Companion.observe (Snapshot.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe (SnapshotStateObserver.java:1)
  at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads (SnapshotStateObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release (OwnerSnapshotObserver.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0 (LayoutNodeLayoutDelegate.java:1)
  at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release (LayoutNode.java:1)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA (MeasureAndLayoutDelegate.java:1)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureOnly (MeasureAndLayoutDelegate.java:1)
  at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureOnly (MeasureAndLayoutDelegate.java:1)
  at androidx.compose.ui.platform.AndroidComposeView.onMeasure (AndroidComposeView.java:1)
  at android.view.View.measure (View.java:28571)
  at androidx.compose.ui.platform.AbstractComposeView.internalOnMeasure$ui_release (AbstractComposeView.java:1)
  at androidx.compose.ui.platform.AbstractComposeView.onMeasure (AbstractComposeView.java:1)
  at android.view.View.measure (View.java:28571)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7390)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at android.view.View.measure (View.java:28571)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7390)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1608)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:878)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:721)
  at android.view.View.measure (View.java:28571)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7390)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at com.android.internal.policy.DecorView.onMeasure (DecorView.java:1036)
  at android.view.View.measure (View.java:28571)
  at android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:5203)
  at android.view.ViewRootImpl.measureHierarchy (ViewRootImpl.java:3603)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3973)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:3288)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:11344)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1689)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1698)
  at android.view.Choreographer.doCallbacks (Choreographer.java:1153)
  at android.view.Choreographer.doFrame (Choreographer.java:1079)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1646)
  at android.os.Handler.handleCallback (Handler.java:958)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:230)
  at android.os.Looper.loop (Looper.java:319)
  at android.app.ActivityThread.main (ActivityThread.java:8919)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:578)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)

Additional context

I configure RevenueCat in main with this:

  Future<void> configure() async {

      PurchasesConfiguration configuration;

      if (Platform.isAndroid) {
        configuration = PurchasesConfiguration(googleApiKey)..appUserID = null
          ..purchasesAreCompletedBy = const PurchasesAreCompletedByRevenueCat();
      } else if (Platform.isIOS) {
        configuration = PurchasesConfiguration(appleApiKey)..appUserID = null
          ..purchasesAreCompletedBy = const PurchasesAreCompletedByRevenueCat();
      } else {
        throw UnsupportedError('Unsupported platform');
      }

      await Purchases.configure(configuration);

  }

and when I'm showing paywall I use that code:

  Future<bool> presentPaywall(
      BuildContext context, String previousScreen) async {
    await checkPurchaseConfiguration();

    bool purchaseOrRestoreSuccess = false;
    final completer = Completer<bool>();

    CustomerInfo customerInfo = await Purchases.getCustomerInfo();
    if (customerInfo.entitlements.all[entitlementID]?.isActive != true) {
      final remoteConfigHelper = await RemoteConfigHelper.getInstance();
      await remoteConfigHelper.updateConfigIfNeeded();

      if (remoteConfigHelper.showRevenueCatPaywall(previousScreen)) {
        try {
          await context.router.push(
            ParentalGateRoute(
              onVerified: () async {
                final paywallResult = await RevenueCatUI.presentPaywall(
                  displayCloseButton: true,
                );
                FirebaseAnalytics.instance.logEvent(
                  name: 'view_rc_subscription_screen',
                  parameters: {
                    'timestamp': DateTime.now().toIso8601String(),
                    'previous_screen': previousScreen ?? 'none',
                  },
                );
                if (paywallResult == PaywallResult.purchased ||
                    paywallResult == PaywallResult.restored) {
                  appData.updatePremiumStatus(ref, true);
                  FirebaseAnalytics.instance.logEvent(
                    name: 'trial_activated_rc',
                    parameters: {
                      'timestamp': DateTime.now().toIso8601String(),
                      'paywallResult': paywallResult.toString(),
                      'previous_screen': previousScreen ?? 'none',
                    },
                  );
                  purchaseOrRestoreSuccess = true;
                }
                // Mark the completer as complete after handling the paywall result
                completer.complete(purchaseOrRestoreSuccess);
              },
            ),
          );
        } catch (e) {
          // If there's an error (e.g., network issue), log it and fall back to the custom subscription screen
          print("Error while fetching CustomerInfo or RevenueCat paywall : $e");
          FirebaseCrashlytics.instance.recordError(e, null);
          // Fallback: Show your own subscription screen if there's a network error or other issue
          bool? result = await context.router.push<bool>(
            SubscriptionRoute(previousScreen: previousScreen),
          );
          purchaseOrRestoreSuccess = result ?? false;
          completer.complete(purchaseOrRestoreSuccess);
        }
      } else {
        bool? result = await context.router.push<bool>(
          SubscriptionRoute(previousScreen: previousScreen),
        );
        purchaseOrRestoreSuccess = result ?? false;
        completer.complete(purchaseOrRestoreSuccess);
        // }
      }
    } else {
      // Complete the completer if the user already has premium active
      completer.complete(false);
    }

    // Wait for the paywall process to finish before returning
    return completer.future;
  }
}

  Future<void> checkPurchaseConfiguration() async {
    bool isConfigured = await Purchases.isConfigured;
    if (!isConfigured) {
      PurchasesConfiguration configuration;
      if (Platform.isAndroid) {
        configuration = PurchasesConfiguration(googleApiKey);
      } else {
        configuration = PurchasesConfiguration(appleApiKey);
      }
      await Purchases.configure(configuration);
    }
  }

hirosz avatar Oct 17 '24 16:10 hirosz

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

RCGitBot avatar Oct 17 '24 16:10 RCGitBot

Hi @hirosz, thank you for reporting this and sharing the relevant code. Is your paywall possibly shown in a different snippet than the one you shared? For example, are you calling RevenueCatUI.presentPaywall anywhere else, that possibly isn't doing the isConfigured check?

Also, in your main flow, how quickly is your paywall shown after configuring the RevenueCat SDK? For example, after the configure block, is the paywall immediately shown afterwards? Or is there a delay in attempting to show it?

Jethro87 avatar Oct 21 '24 16:10 Jethro87

Hi! I use RevenueCatUI.presentPaywall only in that one place. I use configure in main and show paywall much later, depending on the user need to see first-time instructions.

hirosz avatar Nov 10 '24 12:11 hirosz

@hirosz Can you upgrade to the latest version of the Flutter SDK? We recently fixed a configuration issue like the one you were experiencing. I'm going to close this for now, but please re-open it if, after upgrading, you continue to experience this. Thanks!

Jethro87 avatar Feb 07 '25 19:02 Jethro87