flutter_stripe
flutter_stripe copied to clipboard
Error rendering second CardFormField screen on Android
Describe the bug I am trying to implement the CardFormField widget from the flutter_stripe package. The code works as expected on iOS but on android I am getting an error when trying to render the widget for a second time.
To Reproduce Steps to reproduce the behavior:
- user purchases a product using the CardFormField
- user navigates to another part of the app
- user returns to store and proceeds to checkout screen
- the CardFormField does not render (android only)
NOTE ** if the user navigates to the checkout screen but does not submit the form then they can navigate away and return any number of times and the CardFormField renders, this only occurs if we submit.
Expected behavior The CardFromField renders any time the checkout screen is visited
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: PlatformException(error, java.lang.IllegalStateException: The Android view returned from PlatformView#getView() was already added to a parent view. E/flutter (26589): at io.flutter.plugin.platform.PlatformViewsController$1.createForTextureLayer(PlatformViewsController.java:238) E/flutter (26589): at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.create(PlatformViewsChannel.java:122) E/flutter (26589): at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.onMethodCall(PlatformViewsChannel.java:60) E/flutter (26589): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:262) E/flutter (26589): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295) E/flutter (26589): at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:319) E/flutter (26589): at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12) E/flutter (26589): at android.os.Handler.handleCallback(Handler.java:942) E/flutter (26589): at android.os.Handler.dispatchMessage(Handler.java:99) E/flutter (26589): at android.os.Looper.loopOnce(Looper.java:201) E/flutter (26589): at android.os.Looper.loop(Looper.java:288) E/flutter (26589): at android.app.ActivityThread.main(ActivityThread.java:7898) E/flutter (26589): at java.lang.reflect.Method.invoke(Native Method) E/flutter (26589): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) E/flutter (26589): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) E/flutter (26589): , null, null)
+1 I'm facing this issue in production, I didn't upgraded nothing the app was already released. This is very scaring! The first time was 14/10/22. ISSUE:
Non-fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: LocalizedErrorMessage(code: FailureCode.Canceled, localizedMessage: null, message: null, stripeErrorCode: null, declineCode: null, type: null). Error thrown null.
at ResultParser.parse(result_parser.dart:16)
at MethodChannelStripe.confirmSetupIntent(method_channel_stripe.dart:116)
at Stripe.confirmSetupIntent(stripe.dart:297)
at PaymentSetupState._handlePayPress(payment_setup.dart:312)
@jamesblasco
UPDATE 16/10/2022 9:30 UTC+2
Today I tested and seems to work upgrading version from 3.3.X to ^5.1.0 , it's scary because I didn't change anything on my code and now works.
Plus someone more skilled can explain me how it's possible?
I tried to reproduce it on latest master in the example app but failed to do so. I am able to have multiple successful payments with the cardfield. Seems like a race condition to me. Can you provide a reproduction scenario so we can triage this more closely?
I'm working with proprietary code so I can't post the exact example but here is something I put together. A user is navigated to the payment screen with the CardFieldForm and once payment is complete we push them to a new OrderDetailsScreen. I'm wondering if the push to the new screen isn't doing some kind of cleanup.
import 'package:dirt_slingin_tech/widgets/shopping_cart/order_details_screen.dart';
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart' as stripe;
class FlutterStripeExampleScreen extends StatefulWidget {
const FlutterStripeExampleScreen({Key? key}) : super(key: key);
@override
State<FlutterStripeExampleScreen> createState() =>
_FlutterStripeExampleScreenState();
}
class _FlutterStripeExampleScreenState
extends State<FlutterStripeExampleScreen> {
stripe.CardFormEditController cardController =
stripe.CardFormEditController();
bool processingPayment = false;
@override
void initState() {
super.initState();
cardController.addListener(update);
}
void update() => setState(() {});
@override
void dispose() {
cardController.removeListener(update);
cardController.dispose();
super.dispose();
}
Future<void> _handlePayPress() async {
if (!cardController.details.complete) {
return;
}
setState(() {
processingPayment = true;
});
/// capture the navigator state so we can push later
NavigatorState navigator = Navigator.of(context);
// customer info
const billingDetails = stripe.BillingDetails(email: '[email protected]');
// create payment method
dynamic paymentMethod = await stripe.Stripe.instance.createPaymentMethod(
const stripe.PaymentMethodParams.card(
paymentMethodData: stripe.PaymentMethodData(
billingDetails: billingDetails,
),
),
);
// dummy value for payment intent
dynamic paymentIntentResult = await Future<String>.value('payment intent');
// dummy api request to confirm payment
dynamic response = await Future<String>.value('success');
navigator.pushNamed(OrderDetailsScreen.routeName);
}
@override
Widget build(BuildContext context) {
ThemeData theme = Theme.of(context);
return Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 30),
child: stripe.CardFormField(
controller: cardController,
),
),
if (cardController.details.complete == true)
processingPayment
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Processing',
style: theme.textTheme.bodyMedium,
),
const SizedBox(
width: 10,
),
const SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
],
)
: ElevatedButton(
onPressed: () async {
_handlePayPress();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
minimumSize: const Size(200, 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
),
child: const Text('Pay'),
),
],
);
}
}
Having same issue - I am intentionally loading CardField twice in in the same navigation stack (billing (first load) -> plan selection -> enter credit card (second load), and it crashes
this is still an issue
duplicate of #899