stripe / stripe-android

Stripe Android SDK
https://stripe.com/docs/mobile/android
MIT License
1.23k stars 628 forks source link

[BUG] CardBrandView change to Composable causes app crashes #8625

Open magouyaware opened 2 weeks ago

magouyaware commented 2 weeks ago

Summary

This issue is a weird one in that it can be traced back to the Stripe Android SDK but only manifests itself within React Native. The issue at play affects both Stripe's React Native SDK as well as our Olo Pay React Native SDK, both of which are built on top of Stripe's Android SDK.

It appears that CardBrandView was changed in v20.36.0 to be a Composable as part of the Card Brand Choice feature. This change affects CardInputWidget, CardMultilineWidget, and CardFormView and causes many issues in React Native, which doesn't have great support for Jetpack Compose.

Updating to v20.36.0 or later caused issues that we had never seen before:

These issues occur in our React Native SDK, which is built on top of our Android SDK, and have been traced back to Stripe's Android SDK implementation, specifically related to using a Composable inside normal views.

Stripe's own React Native SDK is also affected by this, and we have found many issues in Stripe's React Native repo that others have reported that ultimately are due to this Android Composable. Here are few (this is by no means an exhaustive list):

Android version

20.36.0 and newer

Installation method

Gradle

SDK classes

Video

https://github.com/stripe/stripe-android/assets/4357444/f8d1ee08-54ef-424a-b4cb-9ee64f1fd51d

https://github.com/stripe/stripe-android/assets/4357444/35475b2e-b53c-460b-a0fd-6188f5b9b40a

Crash Stacktrace

Exception in native call
java.lang.IllegalStateException: Cannot locate windowRecomposer; View androidx.compose.ui.platform.ComposeView{3fa3229 V.E...... ......I. 0,0-0,0 #7f080153 app:id/icon} is not attached to a window
    at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:295)
    at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
    at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
    at androidx.compose.ui.platform.AbstractComposeView.onMeasure(ComposeView.android.kt:288)
    at android.view.View.measure(View.java:26357)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6981)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at android.view.View.measure(View.java:26357)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6981)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at android.view.View.measure(View.java:26357)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6981)
    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:26357)
    at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:466)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:134)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:278)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
    at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
    at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
    at android.view.View.measure(View.java:26357)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6981)
    at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
    at androidx.cardview.widget.CardView.onMeasure(CardView.java:260)
    at com.google.android.material.card.MaterialCardView.onMeasure(MaterialCardView.java:211)
    at android.view.View.measure(View.java:26357)
    at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6981)
    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:26357)
    at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:632)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:323)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.solveHorizontalMatchConstraint(Direct.java:709)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:374)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.solvingPass(Direct.java:144)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.layout(ConstraintWidgetContainer.java:693)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solveLinearSystem(BasicMeasure.java:160)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:291)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
    at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
    at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
    at android.view.View.measure(View.java:26357)
    at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:811)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:632)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:323)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.solveHorizontalMatchConstraint(Direct.java:709)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.horizontalSolvingPass(Direct.java:374)
    at androidx.constraintlayout.core.widgets.analyzer.Direct.solvingPass(Direct.java:144)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.layout(ConstraintWidgetContainer.java:693)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solveLinearSystem(BasicMeasure.java:160)
    at androidx.constraintlayout.core.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:291)
    at androidx.constraintlayout.core.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:120)
    at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1594)
    at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1708)
    at android.view.View.measure(View.java:26357)
    at com.facebook.react.uimanager.NativeViewHierarchyManager.updateLayout(NativeViewHierarchyManager.java:189)
    at com.facebook.react.uimanager.UIViewOperationQueue$UpdateLayoutOperation.execute(UIViewOperationQueue.java:169)
    at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:915)
    at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:1026)
    at com.facebook.react.uimanager.UIViewOperationQueue.access$2600(UIViewOperationQueue.java:47)
    at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:1086)
    at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29)
    at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175)
    at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
    at android.view.Choreographer.doCallbacks(Choreographer.java:899)
    at android.view.Choreographer.doFrame(Choreographer.java:827)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
    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:7872)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
jaynewstrom-stripe commented 1 week ago

Hi @magouyaware thanks for the report. I'm able to reproduce the issue when extending from android.app.Activity. We made some assumptions that aren't true in that case.

If there's any possibility to extend from androidx.appcompat.app.AppCompatActivity instead, the issues should be resolved. That being said, I know that's not possible in all cases.

I'm going to talk with the team to come up with a path forward.

Resource-4 commented 1 week ago

@jaynewstrom-stripe When is the expected fix for this issue?

jaynewstrom-stripe commented 1 week ago

We don't have an estimate for the fix at this time. I'll update this issue when we have more information.

Resource-4 commented 1 week ago

Do you have any workaround or suggestion until the issue is fixed?

magouyaware commented 1 week ago

@jaynewstrom-stripe I just took a look at the react native test harness app we use to test our SDK and verified that we are using a subclass of androidx.appcompat.app.AppCompatActivity

Our activity extends ReactActivity, which I believe is pretty standard for all newish React Native apps. ReactActivity extends AppCompatActivity

Screenshot 2024-06-21 at 4 06 08 PM
magouyaware commented 4 days ago

@jaynewstrom-stripe This is a pretty big deal for us and is preventing us from being able to update our React Native SDK to use a newer version of our Android SDK.

Right now, the current version of our React Native SDK is using a version of our Android SDK that is built on Stripe Android v20.25.5... released over a year ago on June 5th, 2023.

We cannot update to a newer version of our Android SDK because it introduces this crash issue, and we have several bug fixes that we cannot address in our RN SDK simply because we are blocked from updating the Olo Pay Android SDK version it uses.

jaynewstrom-stripe commented 4 days ago

We acknowledge this is a big deal, and we're actively working to fix it. We will post an update when we have more to share.