Skip to content

Commit e186c0a

Browse files
[in_app_purchase] Remove use of Pigeon's Dart test generator (#10328)
Updates the production code to use a single instance of each of the two Pigeon host APIs, rather than a private per-file copy, so that tests can inject an alternate implementation directly instead of relying on the method channel hook and Pigeon's dartHostTestHandler. Part of flutter/flutter#159886 ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent 3d926aa commit e186c0a

20 files changed

+160
-1429
lines changed

packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.4.6+1
2+
3+
* Refactors internals for improved testability.
4+
15
## 0.4.6
26

37
* Adds a new case `.unverified` to enum `SK2ProductPurchaseResult`
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2013 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart' show visibleForTesting;
6+
7+
import 'messages.g.dart';
8+
import 'sk2_pigeon.g.dart';
9+
10+
/// The instance of the host API used to communicate with the platform side for
11+
/// the original StoreKit API.
12+
///
13+
/// This is a global to allow tests to override the host API with a mock, since
14+
/// in practice the host API is a singleton, and there is no way to inject it
15+
/// in the usual way since many uses aren't in objects.
16+
InAppPurchaseAPI hostApi = InAppPurchaseAPI();
17+
18+
/// The instance of the host API used to communicate with the platform side
19+
/// for StoreKit2.
20+
///
21+
/// This is a global to allow tests to override the host API with a mock, since
22+
/// in practice the host API is a singleton, and there is no way to inject it
23+
/// in the usual way since many uses aren't in objects.
24+
InAppPurchase2API hostApi2 = InAppPurchase2API();
25+
26+
/// Set up pigeon API.
27+
@visibleForTesting
28+
void setInAppPurchaseHostApis({
29+
InAppPurchaseAPI? api,
30+
InAppPurchase2API? api2,
31+
}) {
32+
if (api != null) {
33+
hostApi = api;
34+
}
35+
36+
if (api2 != null) {
37+
hostApi2 = api2;
38+
}
39+
}

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import '../../store_kit_2_wrappers.dart';
6-
7-
InAppPurchase2API _hostApi = InAppPurchase2API();
5+
import '../in_app_purchase_apis.dart';
86

97
/// Wrapper for StoreKit2's AppStore
108
/// (https://developer.apple.com/documentation/storekit/appstore)
@@ -13,14 +11,14 @@ final class AppStore {
1311
/// Returns a bool that indicates whether the person can make purchases.
1412
/// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments
1513
Future<bool> canMakePayments() {
16-
return _hostApi.canMakePayments();
14+
return hostApi2.canMakePayments();
1715
}
1816

1917
/// Dart wrapper for StoreKit2's sync()
2018
/// Synchronizes your app’s transaction information and subscription status with information from the App Store.
2119
/// Will initiate an authentication pop up.
2220
/// https://developer.apple.com/documentation/storekit/appstore/sync()
2321
Future<void> sync() {
24-
return _hostApi.sync();
22+
return hostApi2.sync();
2523
}
2624
}

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import 'package:flutter/services.dart';
66

77
import '../../store_kit_2_wrappers.dart';
8-
9-
InAppPurchase2API _hostApi = InAppPurchase2API();
8+
import '../in_app_purchase_apis.dart';
109

1110
/// A wrapper around StoreKit2's ProductType
1211
/// https://developer.apple.com/documentation/storekit/product/producttype
@@ -381,7 +380,7 @@ class SK2Product {
381380
/// If any of the identifiers are invalid or can't be found, they are excluded
382381
/// from the returned list.
383382
static Future<List<SK2Product>> products(List<String> identifiers) async {
384-
final List<SK2ProductMessage?> productsMsg = await _hostApi.products(
383+
final List<SK2ProductMessage?> productsMsg = await hostApi2.products(
385384
identifiers,
386385
);
387386
if (productsMsg.isEmpty && identifiers.isNotEmpty) {
@@ -406,17 +405,17 @@ class SK2Product {
406405
}) async {
407406
SK2ProductPurchaseResultMessage result;
408407
if (options != null) {
409-
result = await _hostApi.purchase(id, options: options.convertToPigeon());
408+
result = await hostApi2.purchase(id, options: options.convertToPigeon());
410409
} else {
411-
result = await _hostApi.purchase(id);
410+
result = await hostApi2.purchase(id);
412411
}
413412
return result.convertFromPigeon();
414413
}
415414

416415
/// Checks if the user is eligible for an introductory offer.
417416
/// The product must be an auto-renewable subscription.
418417
static Future<bool> isIntroductoryOfferEligible(String productId) async {
419-
final bool result = await _hostApi.isIntroductoryOfferEligible(productId);
418+
final bool result = await hostApi2.isIntroductoryOfferEligible(productId);
420419

421420
return result;
422421
}
@@ -426,7 +425,7 @@ class SK2Product {
426425
String productId,
427426
String offerId,
428427
) async {
429-
final bool result = await _hostApi.isWinBackOfferEligible(
428+
final bool result = await hostApi2.isWinBackOfferEligible(
430429
productId,
431430
offerId,
432431
);

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_storefront_wrapper.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
import '../../store_kit_2_wrappers.dart';
6-
7-
InAppPurchase2API _hostApi = InAppPurchase2API();
5+
import '../in_app_purchase_apis.dart';
86

97
/// Wrapper for StoreKit2's Storefront
108
/// (https://developer.apple.com/documentation/storekit/storefront)
@@ -13,6 +11,6 @@ final class Storefront {
1311
/// Returns the 3 letter code for a store's locale
1412
/// (https://developer.apple.com/documentation/storekit/storefront/countrycode)
1513
Future<String> countryCode() async {
16-
return _hostApi.countryCode();
14+
return hostApi2.countryCode();
1715
}
1816
}

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte
88

99
import '../../in_app_purchase_storekit.dart';
1010
import '../../store_kit_wrappers.dart';
11+
import '../in_app_purchase_apis.dart';
1112
import '../sk2_pigeon.g.dart';
1213

13-
InAppPurchase2API _hostApi = InAppPurchase2API();
14-
1514
/// Dart wrapper around StoreKit2's [Transaction](https://developer.apple.com/documentation/storekit/transaction)
1615
/// Note that in StoreKit2, a Transaction encompasses the data contained by
1716
/// SKPayment and SKTransaction in StoreKit1
@@ -72,14 +71,14 @@ class SK2Transaction {
7271
/// Indicates to the App Store that the app delivered the purchased content
7372
/// or enabled the service to finish the transaction.
7473
static Future<void> finish(int id) async {
75-
await _hostApi.finish(id);
74+
await hostApi2.finish(id);
7675
}
7776

7877
/// A wrapper around [Transaction.all]
7978
/// https://developer.apple.com/documentation/storekit/transaction/3851203-all
8079
/// A sequence that emits all the customer’s transactions for your app.
8180
static Future<List<SK2Transaction>> transactions() async {
82-
final List<SK2TransactionMessage> msgs = await _hostApi.transactions();
81+
final List<SK2TransactionMessage> msgs = await hostApi2.transactions();
8382
final List<SK2Transaction> transactions = msgs
8483
.map((SK2TransactionMessage e) => e.convertFromPigeon())
8584
.toList();
@@ -89,17 +88,17 @@ class SK2Transaction {
8988
/// Start listening to transactions.
9089
/// Call this as soon as you can your app to avoid missing transactions.
9190
static void startListeningToTransactions() {
92-
_hostApi.startListeningToTransactions();
91+
hostApi2.startListeningToTransactions();
9392
}
9493

9594
/// Stop listening to transactions.
9695
static void stopListeningToTransactions() {
97-
_hostApi.stopListeningToTransactions();
96+
hostApi2.stopListeningToTransactions();
9897
}
9998

10099
/// Restore previously completed purchases.
101100
static Future<void> restorePurchases() async {
102-
await _hostApi.restorePurchases();
101+
await hostApi2.restorePurchases();
103102
}
104103
}
105104

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,12 @@ import 'package:json_annotation/json_annotation.dart';
1111

1212
import '../../store_kit_wrappers.dart';
1313
import '../channel.dart';
14+
import '../in_app_purchase_apis.dart';
1415
import '../in_app_purchase_storekit_platform.dart';
1516
import '../messages.g.dart';
1617

1718
part 'sk_payment_queue_wrapper.g.dart';
1819

19-
InAppPurchaseAPI _hostApi = InAppPurchaseAPI();
20-
21-
/// Set up pigeon API.
22-
@visibleForTesting
23-
void setInAppPurchaseHostApi(InAppPurchaseAPI api) {
24-
_hostApi = api;
25-
}
26-
2720
/// A wrapper around
2821
/// [`SKPaymentQueue`](https://developer.apple.com/documentation/storekit/skpaymentqueue?language=objc).
2922
///
@@ -54,12 +47,12 @@ class SKPaymentQueueWrapper {
5447
///
5548
/// Returns `null` if the user's device is below iOS 13.0 or macOS 10.15.
5649
Future<SKStorefrontWrapper?> storefront() async {
57-
return SKStorefrontWrapper.convertFromPigeon(await _hostApi.storefront());
50+
return SKStorefrontWrapper.convertFromPigeon(await hostApi.storefront());
5851
}
5952

6053
/// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc).
6154
Future<List<SKPaymentTransactionWrapper>> transactions() async {
62-
final List<SKPaymentTransactionMessage?> pigeonMsgs = await _hostApi
55+
final List<SKPaymentTransactionMessage?> pigeonMsgs = await hostApi
6356
.transactions();
6457
return pigeonMsgs
6558
.map(
@@ -70,7 +63,7 @@ class SKPaymentQueueWrapper {
7063
}
7164

7265
/// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc).
73-
static Future<bool> canMakePayments() async => _hostApi.canMakePayments();
66+
static Future<bool> canMakePayments() async => hostApi.canMakePayments();
7467

7568
/// Sets an observer to listen to all incoming transaction events.
7669
///
@@ -89,15 +82,15 @@ class SKPaymentQueueWrapper {
8982
/// Call this method when the first listener is subscribed to the
9083
/// [InAppPurchaseStoreKitPlatform.purchaseStream].
9184
Future<void> startObservingTransactionQueue() =>
92-
_hostApi.startObservingPaymentQueue();
85+
hostApi.startObservingPaymentQueue();
9386

9487
/// Instructs the iOS implementation to remove the transaction observer and
9588
/// stop listening to it.
9689
///
9790
/// Call this when there are no longer any listeners subscribed to the
9891
/// [InAppPurchaseStoreKitPlatform.purchaseStream].
9992
Future<void> stopObservingTransactionQueue() =>
100-
_hostApi.stopObservingPaymentQueue();
93+
hostApi.stopObservingPaymentQueue();
10194

10295
/// Sets an implementation of the [SKPaymentQueueDelegateWrapper].
10396
///
@@ -111,10 +104,10 @@ class SKPaymentQueueWrapper {
111104
/// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)).
112105
Future<void> setDelegate(SKPaymentQueueDelegateWrapper? delegate) async {
113106
if (delegate == null) {
114-
await _hostApi.removePaymentQueueDelegate();
107+
await hostApi.removePaymentQueueDelegate();
115108
paymentQueueDelegateChannel.setMethodCallHandler(null);
116109
} else {
117-
await _hostApi.registerPaymentQueueDelegate();
110+
await hostApi.registerPaymentQueueDelegate();
118111
paymentQueueDelegateChannel.setMethodCallHandler(
119112
handlePaymentQueueDelegateCallbacks,
120113
);
@@ -149,7 +142,7 @@ class SKPaymentQueueWrapper {
149142
'[in_app_purchase]: Trying to add a payment without an observer. One must be set using `SkPaymentQueueWrapper.setTransactionObserver` before the app launches.',
150143
);
151144

152-
await _hostApi.addPayment(payment.toMap());
145+
await hostApi.addPayment(payment.toMap());
153146
}
154147

155148
/// Finishes a transaction and removes it from the queue.
@@ -167,7 +160,7 @@ class SKPaymentQueueWrapper {
167160
SKPaymentTransactionWrapper transaction,
168161
) async {
169162
final Map<String, String?> requestMap = transaction.toFinishMap();
170-
await _hostApi.finishTransaction(requestMap);
163+
await hostApi.finishTransaction(requestMap);
171164
}
172165

173166
/// Restore previously purchased transactions.
@@ -191,7 +184,7 @@ class SKPaymentQueueWrapper {
191184
/// or [`-[SKPayment restoreCompletedTransactionsWithApplicationUsername:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1505992-restorecompletedtransactionswith?language=objc)
192185
/// depending on whether the `applicationUserName` is set.
193186
Future<void> restoreTransactions({String? applicationUserName}) async {
194-
await _hostApi.restoreTransactions(applicationUserName);
187+
await hostApi.restoreTransactions(applicationUserName);
195188
}
196189

197190
/// Present Code Redemption Sheet
@@ -201,7 +194,7 @@ class SKPaymentQueueWrapper {
201194
/// This method triggers [`-[SKPayment
202195
/// presentCodeRedemptionSheet]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3566726-presentcoderedemptionsheet?language=objc)
203196
Future<void> presentCodeRedemptionSheet() async {
204-
await _hostApi.presentCodeRedemptionSheet();
197+
await hostApi.presentCodeRedemptionSheet();
205198
}
206199

207200
/// Shows the price consent sheet if the user has not yet responded to a
@@ -213,7 +206,7 @@ class SKPaymentQueueWrapper {
213206
///
214207
/// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc).
215208
Future<void> showPriceConsentIfNeeded() async {
216-
await _hostApi.showPriceConsentIfNeeded();
209+
await hostApi.showPriceConsentIfNeeded();
217210
}
218211

219212
/// Triage a method channel call from the platform and triggers the correct observer method.

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_receipt_manager.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
import 'dart:async';
66

7-
import '../messages.g.dart';
8-
9-
InAppPurchaseAPI _hostApi = InAppPurchaseAPI();
7+
import '../in_app_purchase_apis.dart';
108

119
// ignore: avoid_classes_with_only_static_members
1210
/// This class contains static methods to manage StoreKit receipts.
@@ -19,6 +17,6 @@ class SKReceiptManager {
1917
/// For more details on how to validate the receipt data, you can refer to Apple's document about [`About Receipt Validation`](https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Introduction.html#//apple_ref/doc/uid/TP40010573-CH105-SW1).
2018
/// If the receipt is invalid or missing, you can use [SKRequestMaker.startRefreshReceiptRequest] to request a new receipt.
2119
static Future<String> retrieveReceiptData() async {
22-
return (await _hostApi.retrieveReceiptData()) ?? '';
20+
return (await hostApi.retrieveReceiptData()) ?? '';
2321
}
2422
}

packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import 'dart:async';
66

77
import 'package:flutter/services.dart';
88

9+
import '../in_app_purchase_apis.dart';
910
import '../messages.g.dart';
1011
import 'sk_product_wrapper.dart';
1112

12-
InAppPurchaseAPI _hostApi = InAppPurchaseAPI();
13-
1413
/// A request maker that handles all the requests made by SKRequest subclasses.
1514
///
1615
/// There are multiple [SKRequest](https://developer.apple.com/documentation/storekit/skrequest?language=objc) subclasses handling different requests in the `StoreKit` with multiple delegate methods,
@@ -29,7 +28,7 @@ class SKRequestMaker {
2928
Future<SkProductResponseWrapper> startProductRequest(
3029
List<String> productIdentifiers,
3130
) async {
32-
final SKProductsResponseMessage productResponsePigeon = await _hostApi
31+
final SKProductsResponseMessage productResponsePigeon = await hostApi
3332
.startProductRequest(productIdentifiers);
3433

3534
// should products be null or <String>[] ?
@@ -55,11 +54,11 @@ class SKRequestMaker {
5554
Future<void> startRefreshReceiptRequest({
5655
Map<String, Object?>? receiptProperties,
5756
}) {
58-
return _hostApi.refreshReceipt(receiptProperties: receiptProperties);
57+
return hostApi.refreshReceipt(receiptProperties: receiptProperties);
5958
}
6059

6160
/// Check if current device supports StoreKit 2.
6261
static Future<bool> supportsStoreKit2() async {
63-
return _hostApi.supportsStoreKit2();
62+
return hostApi.supportsStoreKit2();
6463
}
6564
}

packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:pigeon/pigeon.dart';
77
@ConfigurePigeon(
88
PigeonOptions(
99
dartOut: 'lib/src/messages.g.dart',
10-
dartTestOut: 'test/test_api.g.dart',
1110
objcHeaderOut:
1211
'darwin/in_app_purchase_storekit/Sources/in_app_purchase_storekit_objc/include/in_app_purchase_storekit_objc/messages.g.h',
1312
objcSourceOut:
@@ -238,7 +237,7 @@ class SKProductSubscriptionPeriodMessage {
238237

239238
enum SKSubscriptionPeriodUnitMessage { day, week, month, year }
240239

241-
@HostApi(dartHostTestHandler: 'TestInAppPurchaseApi')
240+
@HostApi()
242241
abstract class InAppPurchaseAPI {
243242
/// Returns if the current device is able to make payments
244243
bool canMakePayments();

0 commit comments

Comments
 (0)