From f72da4ee2ddf592a1a4ffc02875347b309565b83 Mon Sep 17 00:00:00 2001 From: Stephen McDonald Date: Tue, 18 Aug 2026 08:25:15 +1000 Subject: [PATCH] feat: re-evaluate payment readiness when the app is resumed - Register WidgetsBindingObserver in _PayButtonState to observe app lifecycle transitions. - Re-issue the readiness Future and rebuild when the app is resumed. - Add widget tests to verify that returning to the foreground triggers a re-evaluation. closes #9 --- pay/lib/src/widgets/pay_button.dart | 22 ++++++++- pay/test/pay_button_test.dart | 77 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 pay/test/pay_button_test.dart diff --git a/pay/lib/src/widgets/pay_button.dart b/pay/lib/src/widgets/pay_button.dart index c203ffaf..f828f56b 100644 --- a/pay/lib/src/widgets/pay_button.dart +++ b/pay/lib/src/widgets/pay_button.dart @@ -123,8 +123,8 @@ abstract class PayButton extends StatefulWidget { /// button loads. If the payment provider is available for a given user, the /// [_payButton] is added to the tree. Otherwise, if set, the replacement widget /// in [childOnError] is shown. -class _PayButtonState extends State { - late final Future _userCanPayFuture; +class _PayButtonState extends State with WidgetsBindingObserver { + late Future _userCanPayFuture; /// A method to initialize payment result streams /// @@ -146,6 +146,7 @@ class _PayButtonState extends State { @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); if (!widget._isPlatformSupported) return; _userCanPayFuture = _userCanPay(); @@ -155,6 +156,23 @@ class _PayButtonState extends State { } } + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + if (widget._isPlatformSupported) { + setState(() { + _userCanPayFuture = _userCanPay(); + }); + } + } + } + @override Widget build(BuildContext context) { if (!widget._isPlatformSupported) { diff --git a/pay/test/pay_button_test.dart b/pay/test/pay_button_test.dart new file mode 100644 index 00000000..4a9efe85 --- /dev/null +++ b/pay/test/pay_button_test.dart @@ -0,0 +1,77 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:pay/pay.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + const channel = MethodChannel('plugins.flutter.io/pay'); + const providerGooglePay = PayProvider.google_pay; + const payConfigString = + '{"provider": "google_pay", "data": {}}'; + final paymentConfiguration = + PaymentConfiguration.fromJsonString(payConfigString); + + testWidgets('PayButton re-evaluates readiness when app is resumed', + (WidgetTester tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.android; + final log = []; + var canPayResponse = true; + + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (MethodCall methodCall) async { + log.add(methodCall); + if (methodCall.method == 'userCanPay') { + return canPayResponse; + } + return null; + }); + + // Pump the GooglePayButton widget. + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: GooglePayButton( + paymentConfiguration: paymentConfiguration, + paymentItems: const [], + onPaymentResult: (_) {}, + ), + ), + ), + ); + + // Initial build/initState should call userCanPay. + await tester.pumpAndSettle(); + expect(log.length, 1); + expect(log.first.method, 'userCanPay'); + + // Simulate resuming the app. + tester.binding.handleAppLifecycleStateChanged(AppLifecycleState.resumed); + await tester.pumpAndSettle(); + + // Verify that userCanPay was called a second time. + expect(log.length, 2); + expect(log[1].method, 'userCanPay'); + + // Clean up. + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + debugDefaultTargetPlatformOverride = null; + }); +}