RevenueCat / purchases-flutter

Flutter plugin for in-app purchases and subscriptions. Supports iOS, macOS and Android.
https://www.revenuecat.com/
MIT License
607 stars 168 forks source link

IllegalArgumentException: width and height must be > 0 (Android) #1186

Open hirosz opened 1 day ago

hirosz commented 1 day ago

‼️ 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

Describe the bug

RevenueCat Paywall make sometimes error:

Exception java.lang.IllegalArgumentException: width and height must be > 0
  at android.graphics.Bitmap.createBitmap (Bitmap.java:1111)
  at android.graphics.Bitmap.createBitmap (Bitmap.java:1078)
  at android.graphics.Bitmap.createBitmap (Bitmap.java:1028)
  at android.graphics.Bitmap.createBitmap (Bitmap.java:989)
  at androidx.core.graphics.drawable.DrawableKt.toBitmap (Drawable.kt:1)
  at androidx.core.graphics.drawable.DrawableKt.toBitmap$default (Drawable.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.composables.IconImageKt.AppIcon (IconImage.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.composables.IconImageKt.IconImage-djqs-MU (IconImage.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.templates.Template2Kt.IconImage (Template2.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.templates.Template2Kt.Template2PortraitContent (Template2.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.templates.Template2Kt.Template2 (Template2.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.LoadingPaywallKt$LoadingPaywall$3$1.invoke (LoadingPaywall.kt:2)
  at com.revenuecat.purchases.ui.revenuecatui.LoadingPaywallKt$LoadingPaywall$3$1.invoke (LoadingPaywall.kt:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at com.revenuecat.purchases.ui.revenuecatui.composables.DisableTouchesComposableKt.DisableTouchesComposable (DisableTouchesComposable.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.LoadingPaywallKt.LoadingPaywall (LoadingPaywall.kt:2)
  at com.revenuecat.purchases.ui.revenuecatui.LoadingPaywallKt.LoadingPaywall (LoadingPaywall.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt$InternalPaywall$2$1.invoke (InternalPaywall.kt:2)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt$InternalPaywall$2$1.invoke (InternalPaywall.kt:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:2)
  at androidx.compose.animation.AnimatedVisibilityKt.AnimatedEnterExitImpl (AnimatedVisibility.kt:1)
  at androidx.compose.animation.AnimatedVisibilityKt.AnimatedVisibilityImpl (AnimatedVisibility.kt:1)
  at androidx.compose.animation.AnimatedVisibilityKt.AnimatedVisibility (AnimatedVisibility.kt:1)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt$InternalPaywall$2.invoke (InternalPaywall.kt:2)
  at com.revenuecat.purchases.ui.revenuecatui.InternalPaywallKt$InternalPaywall$2.invoke (InternalPaywall.kt:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke (ComposableLambdaImpl.java:1)
  at com.revenuecat.purchases.ui.revenuecatui.fonts.PaywallThemeKt.PaywallTheme (PaywallTheme.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:26535)
  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:26535)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7109)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at android.view.View.measure (View.java:26535)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7109)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1552)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:842)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:721)
  at android.view.View.measure (View.java:26535)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:7109)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:194)
  at com.android.internal.policy.DecorView.onMeasure (DecorView.java:762)
  at android.view.View.measure (View.java:26535)
  at android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:4064)
  at android.view.ViewRootImpl.measureHierarchy (ViewRootImpl.java:2779)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:3044)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:2495)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:9251)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1234)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:1242)
  at android.view.Choreographer.doCallbacks (Choreographer.java:902)
  at android.view.Choreographer.doFrame (Choreographer.java:835)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:1217)
  at android.os.Handler.handleCallback (Handler.java:942)
  at android.os.Handler.dispatchMessage (Handler.java:99)
  at android.os.Looper.loopOnce (Looper.java:201)
  at android.os.Looper.loop (Looper.java:288)
  at android.app.ActivityThread.main (ActivityThread.java:8115)
  at java.lang.reflect.Method.invoke
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:703)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:911)

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);
    }
  }
RCGitBot commented 1 day ago

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