Support payment messaging element#2438
Conversation
Freezed model for Stripe's PaymentMethodMessagingElement (Klarna, Afterpay/Clearpay, Affirm). Includes optional locale to match the Element API surface on both iOS and Android.
Platform-view widget that renders Stripe's promotional messaging element for Klarna/Afterpay/Affirm. Ships on iOS and Android via the PaymentMethodMessagingElement preview API; throws UnsupportedError elsewhere. Native side reports height changes via a per-viewId MethodChannel; didUpdateWidget forwards configuration changes through updateConfiguration; dispose unregisters the handler.
- PaymentMethodMessagingElementView (under Stripe Sdk/) is a plain UIView that owns a Stripe SDK PaymentMethodMessagingElement, kicks off create(configuration:) on an @mainactor Task, embeds element.view with Auto Layout on success, and reports height changes via a callback. Guards against a missing publishable key and collapses to zero height on noContent / failed results. - PaymentMethodMessagingElementFactory is a thin FlutterPlatformView adapter — per-viewId FlutterMethodChannel for updateConfiguration / onHeightChange, NSNumber-based amount decode for the Flutter codec. - Register the factory in StripePlugin with viewId flutter.stripe/payment_method_messaging. - Update Package.resolved to the 25.9.0 SPM revision that matches Package.swift's exact pin (main was committed with a stale resolve). Uses the `@_spi(PaymentMethodMessagingElementPreview) @_spi(STP) import StripePaymentSheet` import that the element is gated behind.
…m view - Add the separate com.stripe:payment-method-messaging artifact dependency, pinned to the same stripe_version as stripe-android. - StripePaymentMethodMessagingPlatformViewFactory mirrors the existing Aubecs/CardField factories, including the Map<String?, Any?>? creationParams type used across the repo. - StripePaymentMethodMessagingPlatformView hosts an AndroidX ComposeView inside a FrameLayout, calls PaymentMethodMessagingElement.create(application) + configure(config) from a scope-tied coroutine, swaps composition state on result (Succeeded → render element.Content(appearance); NoContent/Failed → empty composition + collapsed height). Height changes are reported in dp via Modifier.onSizeChanged. dispose() cancels the scope, disposes composition, and clears the method-channel handler. - Register the factory in StripeAndroidPlugin with viewId flutter.stripe/payment_method_messaging. Uses Stripe's @OptIn(PaymentMethodMessagingElementPreview::class) preview annotation.
payment-method-messaging:23.1.0 declares androidx.compose.foundation, androidx.compose.ui, and androidx.compose.runtime at `runtime` scope in its POM, so Kotlin compilation in this module can't see `Box`, `Modifier`, `remember`, etc. without explicit `implementation` dependencies. Pin the versions to 1.10.4 to match Stripe's own declared dependency graph.
📝 WalkthroughWalkthroughAdds a ChangesPaymentMethodMessaging Feature
Sequence DiagramsequenceDiagram
participant App as Flutter App
participant Widget as PaymentMethodMessaging
participant Channel as FlutterMethodChannel
participant Native as iOS/Android Native View
App->>Widget: build(configuration, appearance)
Widget->>Native: create platform view with creationParams
Native->>Channel: paymentMethodMessagingElementConfigureResult(loading)
Native->>Channel: paymentMethodMessagingElementDidUpdateHeight(height)
Channel->>Widget: setState(_height) + onHeightChange(height)
App->>Widget: setState(new configuration)
Widget->>Channel: invokeMethod(updateConfiguration, params)
Channel->>Native: apply new configuration/appearance
Native->>Channel: paymentMethodMessagingElementDidUpdateHeight(newHeight)
Channel->>Widget: setState(_height) + onHeightChange(newHeight)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Based on #2403 |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
example/ios/Runner.xcodeproj/project.pbxproj (1)
198-205: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRestore CocoaPods embedding for Runner
example/ios/Runner.xcodeproj/project.pbxprojstill linksPods_Runner.framework, andexample/ios/Podfileusesuse_frameworks!; without[CP] Embed Pods Frameworks, CocoaPods frameworks won’t be bundled into the app and launch/archive can fail.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@example/ios/Runner.xcodeproj/project.pbxproj` around lines 198 - 205, The Runner Xcode project is missing the CocoaPods framework embedding step, so the Pods_Runner framework will be linked but not bundled. Update the Runner target’s buildPhases in project.pbxproj to include the CocoaPods “[CP] Embed Pods Frameworks” phase alongside the existing Pods-related phases, and ensure it is wired correctly for the Runner target that already uses use_frameworks! in Podfile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@example/lib/screens/others/payment_method_messaging_screen.dart`:
- Around line 165-176: The payment method chip selection logic in the messaging
screen allows `_paymentMethods` to become empty, which conflicts with the
example state. Update the `FilterChip` `onSelected` handler in the
`PaymentMethodMessaging` screen so the last selected method cannot be removed,
or explicitly treat an empty list as “use dashboard defaults” in the UI
text/state. Use the `PaymentMethodMessagingPaymentMethod.values` loop and
`_paymentMethods` state to locate the change.
In `@packages/stripe_android/android/build.gradle`:
- Around line 77-82: The Compose dependencies added for
StripePaymentMethodMessagingPlatformView are redundantly pinned and override
BOM-managed alignment. Update the android/build.gradle declarations in the
Stripe Android module to keep only the Compose BOM-backed dependencies and
remove the explicit versioned coordinates for androidx.compose.ui:ui,
androidx.compose.foundation:foundation, and androidx.compose.runtime, so the
module stays on a consistent Compose set.
In
`@packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt`:
- Around line 31-38: Event routing in StripePaymentMethodMessagingPlatformView
is shared across instances because the callback registration uses the constant
paymentMethodMessagingElement prefix instead of the PlatformView id. Update the
view’s native event wiring in constructure/create and onMethodCall handling to
key registrations, emissions, and filtering by the provided id so each
PaymentMethodMessaging instance only receives its own height/configure events.
Ensure dispose unregisters only that viewId-specific route and does not affect
other active instances.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt`:
- Around line 57-58: The themed color lookup in
PaymentMethodMesagingElementConfig currently keys off the provided style/theme,
so the dark branch is skipped for { light, dark } colors unless DARK is
explicitly forced. Update the color resolution logic used by
dynamicColorFromParams and the related theme checks to read from the device UI
mode instead of the style parameter, so light/dark variants follow the system
appearance consistently even when light or flat styling is selected.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt`:
- Around line 39-49: The setAppearance prop handler in
PaymentMethodMessagingElementViewManager currently exits early when the value is
null, which leaves the previous native styling applied; update setAppearance so
that clearing the prop explicitly resets PaymentMethodMessagingElementView back
to its default appearance instead of returning unchanged. Use the existing
parseAppearance and view.appearance flow for the non-null case, and add the null
branch to restore defaults on the view instance.
In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`:
- Around line 82-89: updateProps in PaymentMethodMessagingElementFactory is not
resetting containerView.appearance when Dart sends null or omits the field, so
the previous appearance sticks around; change the property update logic so it
explicitly clears or reverts appearance to the SDK default when
arguments["appearance"] is missing or nil, while still applying an NSDictionary
when provided, and keep the same behavior for configuration.
- Around line 63-69: The fixed paymentMethodMessagingElement prefix used in
PaymentMethodMessagingElementFactory is not safe for multiple active views
because StripePlugin.registerChannel(_:forPrefix:) only retains one channel per
prefix. Update the registration flow to use a unique per-instance prefix or
another instance-scoped channel identifier in
PaymentMethodMessagingElementPlatformView/PaymentMethodMessagingElementFactory,
and ensure deinit only unregisters that instance’s own channel so one widget
cannot steal or clear callbacks for another.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/PaymentMethodMessagingElementView.swift:
- Around line 27-45: The MessagingElement update flow in
PaymentMethodMessagingElementView can initialize twice and let an older async
create() completion overwrite newer state. Coalesce appearance and configuration
changes so initMessagingElement(config:) runs once per logical update, or add a
version/token check around createMessagingElement completion to ignore stale
results. Use the appearance, configuration, lastConfig, and initMessagingElement
symbols to keep only the latest element and propagate height/status from the
newest request.
In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart`:
- Around line 116-121: The PlatformViewsService.initSurfaceAndroidView call in
payment_method_messaging.dart is hardcoding left-to-right direction, which
breaks right-to-left layouts. Update the widget code that builds the platform
view to use the ambient direction from context instead of TextDirection.ltr,
specifically by reading the current Directionality in the same build path and
passing that value into the initSurfaceAndroidView call.
- Around line 96-130: Guard the native platform-view selection in
payment_method_messaging.dart by checking kIsWeb before the
defaultTargetPlatform iOS/android branches, since defaultTargetPlatform can
still report native values in browsers. Update the logic around the platform
widget construction so UiKitView and PlatformViewLink are never built on web,
and provide a safe web fallback or throw a web-specific unsupported error from
the same widget-building code.
---
Outside diff comments:
In `@example/ios/Runner.xcodeproj/project.pbxproj`:
- Around line 198-205: The Runner Xcode project is missing the CocoaPods
framework embedding step, so the Pods_Runner framework will be linked but not
bundled. Update the Runner target’s buildPhases in project.pbxproj to include
the CocoaPods “[CP] Embed Pods Frameworks” phase alongside the existing
Pods-related phases, and ensure it is wired correctly for the Runner target that
already uses use_frameworks! in Podfile.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9d919289-13d8-4d4d-9183-c6324c173f1f
📒 Files selected for processing (26)
docs.jsondocs/payment_method_messaging.mdxexample/ios/Runner.xcodeproj/project.pbxprojexample/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvedexample/ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolvedexample/lib/screens/others/payment_method_messaging_screen.dartexample/lib/screens/screens.dartpackages/stripe/lib/flutter_stripe.dartpackages/stripe/lib/src/widgets/payment_method_messaging.dartpackages/stripe_android/android/build.gradlepackages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerDelegate.javapackages/stripe_android/android/src/main/kotlin/com/facebook/react/viewmanagers/PaymentMethodMessagingElementViewManagerInterface.javapackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripeAndroidPlugin.ktpackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.ktpackages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformViewFactory.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.ktpackages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.ktpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodMessagingElementConfig.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe Sdk/PaymentMethodMessagingElementView.swiftpackages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/StripePlugin.swiftpackages/stripe_platform_interface/lib/src/models/payment_method_messaging.dartpackages/stripe_platform_interface/lib/src/models/payment_method_messaging.freezed.dartpackages/stripe_platform_interface/lib/src/models/payment_method_messaging.g.dartpackages/stripe_platform_interface/lib/stripe_platform_interface.dart
| for (final method in PaymentMethodMessagingPaymentMethod.values) | ||
| FilterChip( | ||
| label: Text(method.value), | ||
| selected: _paymentMethods.contains(method), | ||
| onSelected: (selected) => setState(() { | ||
| if (selected) { | ||
| _paymentMethods.add(method); | ||
| } else { | ||
| _paymentMethods.remove(method); | ||
| } | ||
| _isLoading = true; | ||
| }), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep at least one payment method selected.
An empty paymentMethods list falls back to dashboard defaults, so deselecting every chip makes the example UI say “none selected” while the element can still render messaging. Either prevent removing the last chip or label that state explicitly as “use dashboard defaults”.
Suggested fix
- onSelected: (selected) => setState(() {
- if (selected) {
- _paymentMethods.add(method);
- } else {
- _paymentMethods.remove(method);
- }
- _isLoading = true;
- }),
+ onSelected: (selected) {
+ if (!selected && _paymentMethods.length == 1) {
+ return;
+ }
+ setState(() {
+ if (selected) {
+ _paymentMethods.add(method);
+ } else {
+ _paymentMethods.remove(method);
+ }
+ _isLoading = true;
+ });
+ },📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (final method in PaymentMethodMessagingPaymentMethod.values) | |
| FilterChip( | |
| label: Text(method.value), | |
| selected: _paymentMethods.contains(method), | |
| onSelected: (selected) => setState(() { | |
| if (selected) { | |
| _paymentMethods.add(method); | |
| } else { | |
| _paymentMethods.remove(method); | |
| } | |
| _isLoading = true; | |
| }), | |
| for (final method in PaymentMethodMessagingPaymentMethod.values) | |
| FilterChip( | |
| label: Text(method.value), | |
| selected: _paymentMethods.contains(method), | |
| onSelected: (selected) { | |
| if (!selected && _paymentMethods.length == 1) { | |
| return; | |
| } | |
| setState(() { | |
| if (selected) { | |
| _paymentMethods.add(method); | |
| } else { | |
| _paymentMethods.remove(method); | |
| } | |
| _isLoading = true; | |
| }); | |
| }, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@example/lib/screens/others/payment_method_messaging_screen.dart` around lines
165 - 176, The payment method chip selection logic in the messaging screen
allows `_paymentMethods` to become empty, which conflicts with the example
state. Update the `FilterChip` `onSelected` handler in the
`PaymentMethodMessaging` screen so the last selected method cannot be removed,
or explicitly treat an empty list as “use dashboard defaults” in the UI
text/state. Use the `PaymentMethodMessagingPaymentMethod.values` loop and
`_paymentMethods` state to locate the change.
| // Compose modules used by StripePaymentMethodMessagingPlatformView. The | ||
| // payment-method-messaging artifact declares these at runtime scope only, | ||
| // so consuming modules must add them to the compile classpath explicitly. | ||
| implementation "androidx.compose.ui:ui:1.10.4" | ||
| implementation "androidx.compose.foundation:foundation:1.10.4" | ||
| implementation "androidx.compose.runtime:runtime:1.10.4" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== build.gradle snippet =="
sed -n '68,100p' packages/stripe_android/android/build.gradle
echo
echo "== Compose/BOM/compiler settings across the repo =="
rg -n \
-g '*.gradle' -g '*.gradle.kts' \
'compose-bom|androidx\.compose\.ui:ui|androidx\.compose\.foundation:foundation|androidx\.compose\.runtime:runtime|kotlinCompilerExtensionVersion|composeOptions|buildFeatures' \
.Repository: flutter-stripe/flutter_stripe
Length of output: 3153
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== stripe_android/android/build.gradle =="
sed -n '1,130p' packages/stripe_android/android/build.gradle
echo
echo "== any other Compose version declarations =="
rg -n -g '*.gradle' -g '*.gradle.kts' \
'androidx\.compose:compose-bom|androidx\.compose\.ui:ui:|androidx\.compose\.foundation:foundation:|androidx\.compose\.runtime:runtime:|kotlinCompilerExtensionVersion|composeOptions' \
.Repository: flutter-stripe/flutter_stripe
Length of output: 4185
Remove the versioned Compose dependencies here.
packages/stripe_android/android/build.gradle:80-93 already brings in the same artifacts through the Compose BOM, so the explicit 1.10.4 coordinates override BOM-managed alignment and leave this module on a mixed Compose set. Keep only the BOM-backed declarations.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/stripe_android/android/build.gradle` around lines 77 - 82, The
Compose dependencies added for StripePaymentMethodMessagingPlatformView are
redundantly pinned and override BOM-managed alignment. Update the
android/build.gradle declarations in the Stripe Android module to keep only the
Compose BOM-backed dependencies and remove the explicit versioned coordinates
for androidx.compose.ui:ui, androidx.compose.foundation:foundation, and
androidx.compose.runtime, so the module stays on a consistent Compose set.
| class StripePaymentMethodMessagingPlatformView( | ||
| context: Context, | ||
| private val channel: MethodChannel, | ||
| id: Int, | ||
| creationParams: Map<String?, Any?>?, | ||
| private val viewManager: PaymentMethodMessagingElementViewManager, | ||
| sdkAccessor: () -> StripeSdkModule, | ||
| ) : PlatformView, MethodChannel.MethodCallHandler { |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Event routing isn't isolated per PlatformView instance.
Each widget has its own MethodChannel, but this wrapper registers native callbacks against the constant paymentMethodMessagingElement prefix and never uses id. Two PaymentMethodMessaging views on screen cannot disambiguate height/configure events: either the newest view steals them or both views observe the same callbacks. Please key the native event registration/emission by viewId (or include viewId in the payload and filter before forwarding) and only unregister that instance’s route on dispose.
Also applies to: 45-49, 68-71
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/stripe_android/android/src/main/kotlin/com/flutter/stripe/StripePaymentMethodMessagingPlatformView.kt`
around lines 31 - 38, Event routing in StripePaymentMethodMessagingPlatformView
is shared across instances because the callback registration uses the constant
paymentMethodMessagingElement prefix instead of the PlatformView id. Update the
view’s native event wiring in constructure/create and onMethodCall handling to
key registrations, emissions, and filtering by the provided id so each
PaymentMethodMessaging instance only receives its own height/configure events.
Ensure dispose unregisters only that viewId-specific route and does not affect
other active instances.
| val textColor = dynamicColorFromParams(map, "textColor", theme) | ||
| val linkTextColor = dynamicColorFromParams(map, "linkTextColor", theme) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Use UI mode for themed colors instead of style.
Line 119 keys off theme == DARK, so { light, dark } colors never use their dark branch unless the caller explicitly forces the dark theme. On devices running dark mode with light or flat styling, this renders the wrong color.
Proposed fix
- val textColor = dynamicColorFromParams(map, "textColor", theme)
- val linkTextColor = dynamicColorFromParams(map, "linkTextColor", theme)
+ val textColor = dynamicColorFromParams(map, "textColor", context)
+ val linkTextColor = dynamicColorFromParams(map, "linkTextColor", context)
@@
private fun dynamicColorFromParams(
params: ReadableMap?,
key: String,
- theme: PaymentMethodMessagingElement.Appearance.Theme,
+ context: Context,
): Int? {
@@
- val isDark = theme == PaymentMethodMessagingElement.Appearance.Theme.DARK
+ val isDark =
+ (context.resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) ==
+ android.content.res.Configuration.UI_MODE_NIGHT_YESAlso applies to: 107-129
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt`
around lines 57 - 58, The themed color lookup in
PaymentMethodMesagingElementConfig currently keys off the provided style/theme,
so the dark branch is skipped for { light, dark } colors unless DARK is
explicitly forced. Update the color resolution logic used by
dynamicColorFromParams and the related theme checks to read from the device UI
mode instead of the style parameter, so light/dark variants follow the system
appearance consistently even when light or flat styling is selected.
| @ReactProp(name = "appearance") | ||
| override fun setAppearance( | ||
| view: PaymentMethodMessagingElementView?, | ||
| value: Dynamic?, | ||
| ) { | ||
| val readableMap = value?.asMapOrNull() ?: return | ||
| view?.let { | ||
| val appearance = parseAppearance(readableMap, view.context) | ||
| view.appearance(appearance) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files and symbols
git ls-files | rg 'PaymentMethodMessagingElementViewManager\.kt|PaymentMethodMessagingElementView\.kt|Appearance|parseAppearance'
# Show an outline of the manager file
ast-grep outline packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt --view expanded
# Find the appearance-related methods and fields in the view implementation
if [ -f packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt ]; then
ast-grep outline packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt --view expanded
fi
# Search for appearance handling and prop defaults
rg -n "fun setAppearance|appearance\(|parseAppearance|Appearance\(" packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdkRepository: flutter-stripe/flutter_stripe
Length of output: 6164
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementView.kt
printf '\n--- parse helper ---\n'
sed -n '1,220p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMesagingElementConfig.kt
printf '\n--- address sheet comparison ---\n'
sed -n '1,120p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetViewManager.kt
sed -n '1,170p' packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/addresssheet/AddressSheetView.ktRepository: flutter-stripe/flutter_stripe
Length of output: 19891
Reset the native appearance when the prop is cleared.
setAppearance returns on null, so removing or clearing the optional prop leaves the last custom styling in place instead of reverting to defaults.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/stripe_android/android/src/main/kotlin/com/reactnativestripesdk/PaymentMethodMessagingElementViewManager.kt`
around lines 39 - 49, The setAppearance prop handler in
PaymentMethodMessagingElementViewManager currently exits early when the value is
null, which leaves the previous native styling applied; update setAppearance so
that clearing the prop explicitly resets PaymentMethodMessagingElementView back
to its default appearance instead of returning unchanged. Use the existing
parseAppearance and view.appearance flow for the non-null case, and add the null
branch to restore defaults on the view instance.
| StripePlugin.registerChannel(channel, forPrefix: PaymentMethodMessagingElementPlatformView.eventPrefix) | ||
| updateProps(args) | ||
| } | ||
|
|
||
| deinit { | ||
| channel.setMethodCallHandler(nil) | ||
| StripePlugin.unregisterChannel(forPrefix: PaymentMethodMessagingElementPlatformView.eventPrefix) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Per-prefix channel registration is not multi-view safe.
StripePlugin.registerChannel(_:forPrefix:) keeps only one channel for each prefix. Registering every instance under the fixed paymentMethodMessagingElement prefix means the newest view steals all callbacks, and deinit from any instance removes the override for the others. Two widgets on screen—or a replace-before-deinit rebuild—will drop height/configuration events.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`
around lines 63 - 69, The fixed paymentMethodMessagingElement prefix used in
PaymentMethodMessagingElementFactory is not safe for multiple active views
because StripePlugin.registerChannel(_:forPrefix:) only retains one channel per
prefix. Update the registration flow to use a unique per-instance prefix or
another instance-scoped channel identifier in
PaymentMethodMessagingElementPlatformView/PaymentMethodMessagingElementFactory,
and ensure deinit only unregisters that instance’s own channel so one widget
cannot steal or clear callbacks for another.
| private func updateProps(_ args: Any?) { | ||
| guard let arguments = args as? [String: Any] else { return } | ||
| if let appearance = arguments["appearance"] as? NSDictionary { | ||
| containerView.appearance = appearance | ||
| } | ||
| if let configuration = arguments["configuration"] as? NSDictionary { | ||
| containerView.configuration = configuration | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
appearance = null cannot restore the SDK defaults.
updateProps only forwards appearance when it is an NSDictionary. If Dart later omits that field or sends null, the container keeps the previous appearance, so the widget cannot revert to the default native styling.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/PaymentMethodMessagingElementFactory.swift`
around lines 82 - 89, updateProps in PaymentMethodMessagingElementFactory is not
resetting containerView.appearance when Dart sends null or omits the field, so
the previous appearance sticks around; change the property update logic so it
explicitly clears or reverts appearance to the SDK default when
arguments["appearance"] is missing or nil, while still applying an NSDictionary
when provided, and keep the same behavior for configuration.
| @objc var appearance: NSDictionary? { | ||
| didSet { | ||
| if let appearance = appearance { | ||
| appearanceConfig = PaymentMethodMessagingElementConfig.buildAppearanceFromParams(params: appearance) | ||
| // Re-initialize if configuration already exists | ||
| if let config = lastConfig { | ||
| initMessagingElement(config: config) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @objc var configuration: NSDictionary? { | ||
| didSet { | ||
| if let configuration = configuration { | ||
| lastConfig = configuration | ||
| initMessagingElement(config: configuration) | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Coalesce prop updates or discard stale create() completions.
A combined update can initialize twice here: appearance.didSet reuses lastConfig, then configuration.didSet starts a second async create(). Since those tasks are not versioned or cancelled, the older request can finish last and replace the newer element or emit stale height/status back to Dart.
Also applies to: 114-156
🧰 Tools
🪛 GitHub Check: Typo CI
[warning] 27-27: objc
"objc" is a typo. Did you mean "obj"?
[warning] 39-39: objc
"objc" is a typo. Did you mean "obj"?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/stripe_ios/ios/stripe_ios/Sources/stripe_ios/Stripe`
Sdk/PaymentMethodMessagingElementView.swift around lines 27 - 45, The
MessagingElement update flow in PaymentMethodMessagingElementView can initialize
twice and let an older async create() completion overwrite newer state. Coalesce
appearance and configuration changes so initMessagingElement(config:) runs once
per logical update, or add a version/token check around createMessagingElement
completion to ignore stale results. Use the appearance, configuration,
lastConfig, and initMessagingElement symbols to keep only the latest element and
propagate height/status from the newest request.
| if (defaultTargetPlatform == TargetPlatform.iOS) { | ||
| platform = UiKitView( | ||
| viewType: _viewType, | ||
| creationParamsCodec: const StandardMessageCodec(), | ||
| creationParams: creationParams, | ||
| onPlatformViewCreated: onPlatformViewCreated, | ||
| ); | ||
| } else if (defaultTargetPlatform == TargetPlatform.android) { | ||
| platform = PlatformViewLink( | ||
| viewType: _viewType, | ||
| surfaceFactory: (context, controller) { | ||
| return AndroidViewSurface( | ||
| controller: controller as AndroidViewController, | ||
| hitTestBehavior: PlatformViewHitTestBehavior.opaque, | ||
| gestureRecognizers: | ||
| const <Factory<OneSequenceGestureRecognizer>>{}, | ||
| ); | ||
| }, | ||
| onCreatePlatformView: (params) { | ||
| onPlatformViewCreated(params.id); | ||
| return PlatformViewsService.initSurfaceAndroidView( | ||
| id: params.id, | ||
| viewType: _viewType, | ||
| layoutDirection: TextDirection.ltr, | ||
| creationParams: creationParams, | ||
| creationParamsCodec: const StandardMessageCodec(), | ||
| ) | ||
| ..addOnPlatformViewCreatedListener( | ||
| params.onPlatformViewCreated, | ||
| ) | ||
| ..create(); | ||
| }, | ||
| ); | ||
| } else { | ||
| throw UnsupportedError('Unsupported platform view'); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Are UiKitViewandPlatformViewLinksupported on Flutter Web, and candefaultTargetPlatform still resolve to iOS/Android on web?
💡 Result:
UiKitView and PlatformViewLink are not supported on Flutter Web [1][2][3]. UiKitView is specifically designed for embedding native iOS views [1], and PlatformViewLink is a low-level widget used to integrate platform views (like AndroidView or UiKitView) with the Flutter framework's compositing and gesture systems [4][5]. On Flutter Web, the equivalent widget for embedding native browser elements is HtmlElementView [2][6]. Regarding defaultTargetPlatform, it can resolve to TargetPlatform.iOS or TargetPlatform.android when running on the web [7][8]. The Flutter framework determines this value by inspecting the browser's environment [7]. In practice, defaultTargetPlatform on the web is intended to reflect the underlying operating system of the browser environment [7][9]. While it defaulted to TargetPlatform.android in earlier versions [10][11], it now correctly resolves to iOS, macOS, android, windows, or linux based on the detected operating system [7].
Citations:
- 1: https://api.flutter.dev/flutter/widgets/UiKitView-class.html
- 2: https://codebrowser.dev/flutter/flutter/packages/flutter/lib/src/widgets/platform_view.dart.html
- 3: https://github.com/flutter/flutter/blob/main/packages/flutter/lib/src/widgets/platform_view.dart
- 4: https://api.flutter.dev/flutter/widgets/PlatformViewLink-class.html
- 5: https://api.flutter.dev/flutter/widgets/PlatformViewLink/PlatformViewLink.html
- 6: Add PlatformView for all supported platforms flutter/flutter#138380
- 7: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/foundation/_platform_web.dart
- 8: [web] Implement defaultTargetPlatform MacOS/iOS for web. flutter/flutter#56081
- 9: https://api.flutter.dev/flutter/foundation/defaultTargetPlatform.html
- 10: defaultTargetPlatform on web thinks it's Android flutter/flutter#43633
- 11: defaultTargetPlatform on web thinks it's Android (on Linux & Windows) flutter/flutter#61321
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the widget file and surrounding context
sed -n '1,220p' packages/stripe/lib/src/widgets/payment_method_messaging.dart
echo
echo "--- pubspec/package context ---"
sed -n '1,220p' packages/stripe/pubspec.yaml
echo
echo "--- search for web/platform guards in this package ---"
rg -n "kIsWeb|UnsupportedError\\(|defaultTargetPlatform|UiKitView|PlatformViewLink|HtmlElementView" packages/stripe/lib/src -SRepository: flutter-stripe/flutter_stripe
Length of output: 10011
Guard the native platform-view branches on web. defaultTargetPlatform can still resolve to iOS/android in browsers, so this can build UiKitView or PlatformViewLink where platform views are unsupported. Add an explicit kIsWeb check (or a web fallback) before these branches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart` around lines
96 - 130, Guard the native platform-view selection in
payment_method_messaging.dart by checking kIsWeb before the
defaultTargetPlatform iOS/android branches, since defaultTargetPlatform can
still report native values in browsers. Update the logic around the platform
widget construction so UiKitView and PlatformViewLink are never built on web,
and provide a safe web fallback or throw a web-specific unsupported error from
the same widget-building code.
| return PlatformViewsService.initSurfaceAndroidView( | ||
| id: params.id, | ||
| viewType: _viewType, | ||
| layoutDirection: TextDirection.ltr, | ||
| creationParams: creationParams, | ||
| creationParamsCodec: const StandardMessageCodec(), |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
For Flutter Android platform views created with PlatformViewsService.initSurfaceAndroidView, should layoutDirectioncome fromDirectionality.of(context) to support RTL layouts?
💡 Result:
Yes, you should use Directionality.of(context) (or Directionality.maybeOf(context) if the ambient directionality might be missing) for the layoutDirection parameter when creating a Flutter Android platform view via PlatformViewsService.initSurfaceAndroidView [1][2][3]. Providing the correct layout direction allows the native Android view to respect the app's ambient RTL or LTR layout settings [4]. If this value is not provided correctly, the native view may not handle layout-dependent features—such as text alignment, padding, or coordinate systems—in alignment with the rest of your Flutter interface [5][6][7]. While some documentation examples may use a hardcoded TextDirection.ltr for simplicity [8], relying on Directionality.of(context) is the recommended practice for ensuring your platform views behave consistently with the surrounding Flutter widgets in applications that support multiple languages or locales [1][2][3]. Top results: [1][2][3][4]
Citations:
- 1: Android PlatformView in Hybrid mode stops rendering when rotating only on certain devices (Tablet) flutter/flutter#75698
- 2: https://chromium.googlesource.com/external/github.com/flutter/plugins/+/refs/tags/webview_flutter_android-v2.9.3/packages/webview_flutter/webview_flutter_android/lib/webview_surface_android.dart
- 3: Android impeller black screen with PlatformView on some devices flutter/flutter#177796
- 4: https://main-api.flutter.dev/javadoc/io/flutter/embedding/engine/systemchannels/PlatformViewCreationRequest.html
- 5: Fix input mirroring in platform views when system language is RTL, and support is set in manifest. flutter/flutter#183472
- 6: [Android] PlatformView pointer events (tap & drag) are horizontally mirrored in RTL mode flutter/flutter#182823
- 7: When setting left and right padding in RTL mode, the click area will become larger. flutter/flutter#127504
- 8: https://docs.flutter.dev/platform-integration/android/platform-views
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the target file around the cited lines.
sed -n '1,220p' packages/stripe/lib/src/widgets/payment_method_messaging.dartRepository: flutter-stripe/flutter_stripe
Length of output: 5013
Use the ambient text direction here. Hardcoding TextDirection.ltr breaks RTL apps; pass Directionality.maybeOf(context) ?? TextDirection.ltr to PlatformViewsService.initSurfaceAndroidView.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/stripe/lib/src/widgets/payment_method_messaging.dart` around lines
116 - 121, The PlatformViewsService.initSurfaceAndroidView call in
payment_method_messaging.dart is hardcoding left-to-right direction, which
breaks right-to-left layouts. Update the widget code that builds the platform
view to use the ambient direction from context instead of TextDirection.ltr,
specifically by reading the current Directionality in the same build path and
passing that value into the initSurfaceAndroidView call.
Support the payment messaging element
Summary by CodeRabbit
New Features
Bug Fixes