From 88d7d8840e5425a382a67093f71252447cf39369 Mon Sep 17 00:00:00 2001 From: viktor Date: Thu, 12 Feb 2026 11:08:40 +0200 Subject: [PATCH 1/2] refactor: simplify CheckoutWidget implementation --- .../lib/feature/checkout/checkout_screen.dart | 61 ++------------- .../com/ottu/flutter/checkout/CheckoutView.kt | 15 ++-- .../CheckoutPlatformView.swift | 9 +-- lib/src/checkout_widget.dart | 76 +++++++++++++++---- 4 files changed, 79 insertions(+), 82 deletions(-) diff --git a/Sample/lib/feature/checkout/checkout_screen.dart b/Sample/lib/feature/checkout/checkout_screen.dart index 3e02565..67f6b3d 100644 --- a/Sample/lib/feature/checkout/checkout_screen.dart +++ b/Sample/lib/feature/checkout/checkout_screen.dart @@ -1,17 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/src/painting/text_style.dart' as ts; import 'package:logger/logger.dart'; import 'package:ottu_flutter_checkout/ottu_flutter_checkout.dart'; import 'package:ottu_flutter_checkout_sample/l10n/app_localizations.dart'; -const _defaultCheckoutViewHeight = 200; -const _methodCheckoutHeight = "METHOD_CHECKOUT_HEIGHT"; -const _methodPaymentSuccessResult = "METHOD_PAYMENT_SUCCESS_RESULT"; -const _methodPaymentErrorResult = "METHOD_PAYMENT_ERROR_RESULT"; -const _methodPaymentCancelResult = "METHOD_PAYMENT_CANCEL_RESULT"; -const _methodChannel = MethodChannel('com.ottu.sample/checkout'); - class CheckoutScreen extends StatefulWidget { const CheckoutScreen({ super.key, @@ -29,7 +21,6 @@ class CheckoutScreen extends StatefulWidget { } class _CheckoutScreenState extends State { - final ValueNotifier _checkoutHeight = ValueNotifier(_defaultCheckoutViewHeight); final ValueNotifier<(String, String)> _checkoutMessage = ValueNotifier<(String, String)>(( "", "", @@ -49,41 +40,6 @@ class _CheckoutScreenState extends State { _checkoutMessage.removeListener(_showMessageDialog); } - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _methodChannel.setMethodCallHandler((call) async { - switch (call.method) { - case _methodCheckoutHeight: - { - int height = call.arguments as int; - logger.d("didChangeDependencies, height: $height"); - _checkoutHeight.value = height; - } - case _methodPaymentSuccessResult: - { - String message = call.arguments as String; - logger.d("didChangeDependencies, success: $message"); - _checkoutMessage.value = ("Success", message); - } - - case _methodPaymentCancelResult: - { - String message = call.arguments as String; - logger.d("didChangeDependencies, cancel: $message"); - _checkoutMessage.value = ("Cancel", message); - } - - case _methodPaymentErrorResult: - { - String message = call.arguments as String; - logger.d("didChangeDependencies, error: $message"); - _checkoutMessage.value = ("Error", message); - } - } - }); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -111,17 +67,12 @@ class _CheckoutScreenState extends State { //End of Merchant content Padding( padding: const EdgeInsets.all(12.0), - child: ValueListenableBuilder( - builder: (BuildContext context, int height, Widget? child) { - return SizedBox( - height: height.toDouble(), - child: OttuCheckoutWidget( - arguments: widget.checkoutArguments, - verifyPayment: _verifyPayment, - ), - ); - }, - valueListenable: _checkoutHeight, + child: OttuCheckoutWidget( + arguments: widget.checkoutArguments, + verifyPayment: _verifyPayment, + successCallback: (message) => _checkoutMessage.value = ("Success", message), + cancelCallback: (message) => _checkoutMessage.value = ("Cancel", message), + errorCallback: (message) => _checkoutMessage.value = ("Error", message), ), ), const SizedBox(height: 20), diff --git a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt index cbfd07d..9c935be 100644 --- a/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt +++ b/android/src/main/kotlin/com/ottu/flutter/checkout/CheckoutView.kt @@ -41,9 +41,9 @@ private const val METHOD_CHECKOUT_HEIGHT = "METHOD_CHECKOUT_HEIGHT" private const val METHOD_PAYMENT_SUCCESS_RESULT = "METHOD_PAYMENT_SUCCESS_RESULT" private const val METHOD_PAYMENT_ERROR_RESULT = "METHOD_PAYMENT_ERROR_RESULT" private const val METHOD_PAYMENT_CANCEL_RESULT = "METHOD_PAYMENT_CANCEL_RESULT" +private const val METHOD_ON_WIDGET_DETACHED = "METHOD_ON_WIDGET_DETACHED" private const val METHOD_VERIFY_PAYMENT = "METHOD_VERIFY_PAYMENT" private const val CHANNEL = "com.ottu.sample/checkout" -private const val CHANNEL_PAYMENT_VERIFY = "com.ottu.sample/checkout/payment/verify" private const val TAG = "CheckoutView" private val sessionCoroutineExceptionHandler = CoroutineExceptionHandler { _, t -> @@ -67,8 +67,6 @@ internal class CheckoutView( private val arguments: CheckoutArguments, ) : PlatformView { private val methodChannel: MethodChannel - private val methodChannelVerifyPayment: MethodChannel - private val checkoutView: FrameLayout = LayoutInflater.from(context) .inflate(R.layout.fragment_checkout_wrapper_view, null) as FrameLayout @@ -80,7 +78,6 @@ internal class CheckoutView( ) checkoutView.layoutParams = vParams methodChannel = MethodChannel(messenger, CHANNEL) - methodChannelVerifyPayment = MethodChannel(messenger, CHANNEL_PAYMENT_VERIFY) } override fun getView(): View = checkoutView @@ -95,6 +92,14 @@ internal class CheckoutView( override fun onFlutterViewAttached(flutterView: View) { super.onFlutterViewAttached(flutterView) + methodChannel.setMethodCallHandler { call, _ -> + when (call.method) { + METHOD_ON_WIDGET_DETACHED -> { + Log.d(TAG, "onFlutterViewAttached, handle onWidgetDetached") + } + } + } + Log.i(TAG, "onFlutterViewAttached, isCheckoutInitializing: ${isCheckoutInitializing.get()}") if (isCheckoutInitializing.getAndSet(true).not()) { val fa = checkoutView.context as FragmentActivity @@ -219,7 +224,7 @@ internal class CheckoutView( private suspend fun verifyPayment(payload: String?): CardVerificationResult { val deferredResult = CompletableDeferred>() - methodChannelVerifyPayment.invokeMethod( + methodChannel.invokeMethod( METHOD_VERIFY_PAYMENT, payload, object : MethodChannel.Result { diff --git a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift index 4935e9b..c8fb694 100644 --- a/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift +++ b/ios/ottu_flutter_checkout/Sources/ottu_flutter_checkout/CheckoutPlatformView.swift @@ -12,8 +12,6 @@ import ottu_checkout_sdk // private let checkoutChannelName = "com.ottu.sample/checkout" -private let checkoutVerifyPaymentChannelName = - "com.ottu.sample/checkout/payment/verify" private let methodCheckoutHeight = "METHOD_CHECKOUT_HEIGHT" private let _methodPaymentSuccessResult = "METHOD_PAYMENT_SUCCESS_RESULT" private let _methodPaymentErrorResult = "METHOD_PAYMENT_ERROR_RESULT" @@ -24,7 +22,6 @@ private let _methodVerifyPayment = "METHOD_VERIFY_PAYMENT" public class CheckoutPlatformView: NSObject, FlutterPlatformView { private let viewId: Int64 private let channel: FlutterMethodChannel - private let channelVerifyPayment: FlutterMethodChannel private let _view: CheckoutContainerView private weak var paymentViewController: UIViewController? private var checkout: Checkout? @@ -58,10 +55,6 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { name: checkoutChannelName, binaryMessenger: messenger ) - self.channelVerifyPayment = FlutterMethodChannel( - name: checkoutVerifyPaymentChannelName, - binaryMessenger: messenger - ) _view = CheckoutContainerView() super.init() @@ -426,7 +419,7 @@ public class CheckoutPlatformView: NSObject, FlutterPlatformView { return await withCheckedContinuation { continuation in Logger.sdk.info("CheckoutPlatformView.verifyPaymentCallback") DispatchQueue.main.async { - self.channelVerifyPayment.invokeMethod( + self.channel.invokeMethod( _methodVerifyPayment, arguments: payload, result: { [weak self] (result) -> Void in diff --git a/lib/src/checkout_widget.dart b/lib/src/checkout_widget.dart index cfc5a49..74b1d89 100644 --- a/lib/src/checkout_widget.dart +++ b/lib/src/checkout_widget.dart @@ -6,10 +6,15 @@ import 'package:flutter/services.dart'; import 'package:ottu_flutter_checkout/ottu_flutter_checkout.dart'; const _checkoutViewType = 'OttuCheckoutWidget'; +const _methodCheckoutHeight = "METHOD_CHECKOUT_HEIGHT"; const _methodOnWidgetDetached = "METHOD_ON_WIDGET_DETACHED"; const _methodVerifyPayment = "METHOD_VERIFY_PAYMENT"; +const _methodPaymentSuccessResult = "METHOD_PAYMENT_SUCCESS_RESULT"; +const _methodPaymentErrorResult = "METHOD_PAYMENT_ERROR_RESULT"; +const _methodPaymentCancelResult = "METHOD_PAYMENT_CANCEL_RESULT"; const _methodChannel = MethodChannel('com.ottu.sample/checkout'); -const _methodChannelPaymentVerify = MethodChannel('com.ottu.sample/checkout/payment/verify'); + +const _defaultCheckoutViewHeight = 200; typedef VerifyPaymentDelegate = Future> Function( String? payload); @@ -17,10 +22,21 @@ typedef VerifyPaymentDelegate = Future> Fun class OttuCheckoutWidget extends StatefulWidget { final CheckoutArguments arguments; final VerifyPaymentDelegate? _verifyPayment; + final Function(String message)? _successCallback; + final Function(String message)? _cancelCallback; + final Function(String message)? _errorCallback; - const OttuCheckoutWidget( - {super.key, required this.arguments, VerifyPaymentDelegate? verifyPayment}) - : _verifyPayment = verifyPayment; + const OttuCheckoutWidget({ + super.key, + required this.arguments, + VerifyPaymentDelegate? verifyPayment, + Function(String message)? successCallback, + Function(String message)? cancelCallback, + Function(String message)? errorCallback, + }) : _verifyPayment = verifyPayment, + _successCallback = successCallback, + _cancelCallback = cancelCallback, + _errorCallback = errorCallback; @override State createState() => _OttuCheckoutWidgetState(); @@ -28,6 +44,7 @@ class OttuCheckoutWidget extends StatefulWidget { class _OttuCheckoutWidgetState extends State { static const StandardMessageCodec _decoder = StandardMessageCodec(); + final ValueNotifier _checkoutHeight = ValueNotifier(_defaultCheckoutViewHeight); @override void dispose() { @@ -38,9 +55,31 @@ class _OttuCheckoutWidgetState extends State { @override void didChangeDependencies() { - _methodChannelPaymentVerify.setMethodCallHandler((call) async { - print("CheckoutWidget, method call: ${call.method}"); + super.didChangeDependencies(); + _methodChannel.setMethodCallHandler((call) async { switch (call.method) { + case _methodCheckoutHeight: + { + int height = call.arguments as int; + _checkoutHeight.value = height; + } + case _methodPaymentSuccessResult: + { + String message = call.arguments as String; + widget._successCallback?.call(message); + } + + case _methodPaymentCancelResult: + { + String message = call.arguments as String; + widget._cancelCallback?.call(message); + } + + case _methodPaymentErrorResult: + { + String message = call.arguments as String; + widget._errorCallback?.call(message); + } case _methodVerifyPayment: { final result = await widget._verifyPayment?.call(call.arguments); @@ -56,7 +95,6 @@ class _OttuCheckoutWidgetState extends State { } } }); - super.didChangeDependencies(); } @override @@ -64,12 +102,22 @@ class _OttuCheckoutWidgetState extends State { final argsMap = widget.arguments.toJson(); final jsonArg = json.encode(argsMap); final args = {"args": jsonArg}; - if (defaultTargetPlatform == TargetPlatform.android) { - return AndroidView( - viewType: _checkoutViewType, creationParams: args, creationParamsCodec: _decoder); - } else { - return UiKitView( - viewType: _checkoutViewType, creationParams: args, creationParamsCodec: _decoder); - } + return ValueListenableBuilder( + valueListenable: _checkoutHeight, + builder: (BuildContext context, int height, Widget? child) { + return SizedBox( + height: height.toDouble(), + child: defaultTargetPlatform == TargetPlatform.android + ? AndroidView( + viewType: _checkoutViewType, + creationParams: args, + creationParamsCodec: _decoder) + : defaultTargetPlatform == TargetPlatform.iOS + ? UiKitView( + viewType: _checkoutViewType, + creationParams: args, + creationParamsCodec: _decoder) + : SizedBox.shrink()); + }); } } From 6270a98260c9ef5ba0fb7ecae25c8687fe7da24d Mon Sep 17 00:00:00 2001 From: viktor Date: Thu, 12 Feb 2026 11:45:55 +0200 Subject: [PATCH 2/2] address comments --- lib/src/checkout_widget.dart | 85 +++++++++++++++++++----------------- 1 file changed, 46 insertions(+), 39 deletions(-) diff --git a/lib/src/checkout_widget.dart b/lib/src/checkout_widget.dart index 74b1d89..cff4901 100644 --- a/lib/src/checkout_widget.dart +++ b/lib/src/checkout_widget.dart @@ -46,55 +46,62 @@ class _OttuCheckoutWidgetState extends State { static const StandardMessageCodec _decoder = StandardMessageCodec(); final ValueNotifier _checkoutHeight = ValueNotifier(_defaultCheckoutViewHeight); + @override + void initState() { + super.initState(); + _methodChannel.setMethodCallHandler(_handleChannelMethod); + } + @override void dispose() { - print("OttuCheckoutWidget.dispose()"); + if (kDebugMode) { + print("OttuCheckoutWidget.dispose()"); + } + _methodChannel.setMethodCallHandler(null); _methodChannel.invokeMethod(_methodOnWidgetDetached); super.dispose(); } - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _methodChannel.setMethodCallHandler((call) async { - switch (call.method) { - case _methodCheckoutHeight: - { - int height = call.arguments as int; - _checkoutHeight.value = height; - } - case _methodPaymentSuccessResult: - { - String message = call.arguments as String; - widget._successCallback?.call(message); - } + Future _handleChannelMethod(call) async { + switch (call.method) { + case _methodCheckoutHeight: + { + int height = call.arguments as int; + _checkoutHeight.value = height; + } + case _methodPaymentSuccessResult: + { + String message = call.arguments as String; + widget._successCallback?.call(message); + } - case _methodPaymentCancelResult: - { - String message = call.arguments as String; - widget._cancelCallback?.call(message); - } + case _methodPaymentCancelResult: + { + String message = call.arguments as String; + widget._cancelCallback?.call(message); + } - case _methodPaymentErrorResult: - { - String message = call.arguments as String; - widget._errorCallback?.call(message); - } - case _methodVerifyPayment: - { - final result = await widget._verifyPayment?.call(call.arguments); + case _methodPaymentErrorResult: + { + String message = call.arguments as String; + widget._errorCallback?.call(message); + } + case _methodVerifyPayment: + { + final result = await widget._verifyPayment?.call(call.arguments); + if (kDebugMode) { print("CheckoutWidget, verifyPayment, result: ${result.runtimeType}"); - switch (result) { - case Success(): - return ""; - case Failure(:final message): - throw PlatformException(code: _methodVerifyPayment, message: message); - case null: - throw PlatformException(code: _methodVerifyPayment, message: "Unknown error"); - } } - } - }); + switch (result) { + case Success(): + return ""; + case Failure(:final message): + throw PlatformException(code: _methodVerifyPayment, message: message); + case null: + throw PlatformException(code: _methodVerifyPayment, message: "Unknown error"); + } + } + } } @override