From 0283a99ce39d947766892cd8d682375bd4c3ce61 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Wed, 2 Mar 2022 02:46:23 -0500 Subject: [PATCH] [in_app_purchase] Update app-facing package to new analysis options (#4969) --- .../in_app_purchase/CHANGELOG.md | 4 + .../in_app_purchase/analysis_options.yaml | 1 - .../example/lib/consumable_store.dart | 13 +- .../in_app_purchase/example/lib/main.dart | 134 +++++++++--------- .../in_app_purchase/example/pubspec.yaml | 4 +- .../in_app_purchase/lib/in_app_purchase.dart | 2 +- .../in_app_purchase/pubspec.yaml | 5 +- .../test/in_app_purchase_test.dart | 58 ++++---- script/configs/custom_analysis.yaml | 1 - 9 files changed, 116 insertions(+), 106 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase/analysis_options.yaml diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 588993a01fd0..134fd9052a63 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.0.1 + +* Internal code cleanup for stricter analysis options. + ## 3.0.0 * **BREAKING CHANGE** Updates `restorePurchases` to emit an empty list of purchases on StoreKit when there are no purchases to restore (same as Android). diff --git a/packages/in_app_purchase/in_app_purchase/analysis_options.yaml b/packages/in_app_purchase/in_app_purchase/analysis_options.yaml deleted file mode 100644 index 5aeb4e7c5e21..000000000000 --- a/packages/in_app_purchase/in_app_purchase/analysis_options.yaml +++ /dev/null @@ -1 +0,0 @@ -include: ../../../analysis_options_legacy.yaml diff --git a/packages/in_app_purchase/in_app_purchase/example/lib/consumable_store.dart b/packages/in_app_purchase/in_app_purchase/example/lib/consumable_store.dart index 4d10a50e1ee8..448efcf40b51 100644 --- a/packages/in_app_purchase/in_app_purchase/example/lib/consumable_store.dart +++ b/packages/in_app_purchase/in_app_purchase/example/lib/consumable_store.dart @@ -5,13 +5,14 @@ import 'dart:async'; import 'package:shared_preferences/shared_preferences.dart'; +// ignore: avoid_classes_with_only_static_members /// A store of consumable items. /// /// This is a development prototype tha stores consumables in the shared /// preferences. Do not use this in real world apps. class ConsumableStore { static const String _kPrefKey = 'consumables'; - static Future _writes = Future.value(); + static Future _writes = Future.value(); /// Adds a consumable with ID `id` to the store. /// @@ -32,19 +33,19 @@ class ConsumableStore { /// Returns the list of consumables from the store. static Future> load() async { return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ?? - []; + []; } static Future _doSave(String id) async { - List cached = await load(); - SharedPreferences prefs = await SharedPreferences.getInstance(); + final List cached = await load(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.add(id); await prefs.setStringList(_kPrefKey, cached); } static Future _doConsume(String id) async { - List cached = await load(); - SharedPreferences prefs = await SharedPreferences.getInstance(); + final List cached = await load(); + final SharedPreferences prefs = await SharedPreferences.getInstance(); cached.remove(id); await prefs.setStringList(_kPrefKey, cached); } diff --git a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart index 5d2f04ce8ff0..651652b40c3a 100644 --- a/packages/in_app_purchase/in_app_purchase/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase/example/lib/main.dart @@ -39,10 +39,10 @@ class _MyApp extends StatefulWidget { class _MyAppState extends State<_MyApp> { final InAppPurchase _inAppPurchase = InAppPurchase.instance; late StreamSubscription> _subscription; - List _notFoundIds = []; - List _products = []; - List _purchases = []; - List _consumables = []; + List _notFoundIds = []; + List _products = []; + List _purchases = []; + List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; @@ -52,11 +52,12 @@ class _MyAppState extends State<_MyApp> { void initState() { final Stream> purchaseUpdated = _inAppPurchase.purchaseStream; - _subscription = purchaseUpdated.listen((purchaseDetailsList) { + _subscription = + purchaseUpdated.listen((List purchaseDetailsList) { _listenToPurchaseUpdated(purchaseDetailsList); }, onDone: () { _subscription.cancel(); - }, onError: (error) { + }, onError: (Object error) { // handle error here. }); initStoreInfo(); @@ -68,10 +69,10 @@ class _MyAppState extends State<_MyApp> { if (!isAvailable) { setState(() { _isAvailable = isAvailable; - _products = []; - _purchases = []; - _notFoundIds = []; - _consumables = []; + _products = []; + _purchases = []; + _notFoundIds = []; + _consumables = []; _purchasePending = false; _loading = false; }); @@ -79,21 +80,22 @@ class _MyAppState extends State<_MyApp> { } if (Platform.isIOS) { - var iosPlatformAddition = _inAppPurchase - .getPlatformAddition(); + final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = + _inAppPurchase + .getPlatformAddition(); await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate()); } - ProductDetailsResponse productDetailResponse = + final ProductDetailsResponse productDetailResponse = await _inAppPurchase.queryProductDetails(_kProductIds.toSet()); if (productDetailResponse.error != null) { setState(() { _queryProductError = productDetailResponse.error!.message; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; - _purchases = []; + _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; - _consumables = []; + _consumables = []; _purchasePending = false; _loading = false; }); @@ -105,16 +107,16 @@ class _MyAppState extends State<_MyApp> { _queryProductError = null; _isAvailable = isAvailable; _products = productDetailResponse.productDetails; - _purchases = []; + _purchases = []; _notFoundIds = productDetailResponse.notFoundIDs; - _consumables = []; + _consumables = []; _purchasePending = false; _loading = false; }); return; } - List consumables = await ConsumableStore.load(); + final List consumables = await ConsumableStore.load(); setState(() { _isAvailable = isAvailable; _products = productDetailResponse.productDetails; @@ -128,8 +130,9 @@ class _MyAppState extends State<_MyApp> { @override void dispose() { if (Platform.isIOS) { - var iosPlatformAddition = _inAppPurchase - .getPlatformAddition(); + final InAppPurchaseStoreKitPlatformAddition iosPlatformAddition = + _inAppPurchase + .getPlatformAddition(); iosPlatformAddition.setDelegate(null); } _subscription.cancel(); @@ -138,11 +141,11 @@ class _MyAppState extends State<_MyApp> { @override Widget build(BuildContext context) { - List stack = []; + final List stack = []; if (_queryProductError == null) { stack.add( ListView( - children: [ + children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), @@ -158,10 +161,10 @@ class _MyAppState extends State<_MyApp> { if (_purchasePending) { stack.add( Stack( - children: [ + children: const [ Opacity( opacity: 0.3, - child: const ModalBarrier(dismissible: false, color: Colors.grey), + child: ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), @@ -185,7 +188,7 @@ class _MyAppState extends State<_MyApp> { Card _buildConnectionCheckTile() { if (_loading) { - return Card(child: ListTile(title: const Text('Trying to connect...'))); + return const Card(child: ListTile(title: Text('Trying to connect...'))); } final Widget storeHeader = ListTile( leading: Icon(_isAvailable ? Icons.check : Icons.block, @@ -196,8 +199,8 @@ class _MyAppState extends State<_MyApp> { final List children = [storeHeader]; if (!_isAvailable) { - children.addAll([ - Divider(), + children.addAll([ + const Divider(), ListTile( title: Text('Not connected', style: TextStyle(color: ThemeData.light().errorColor)), @@ -211,29 +214,30 @@ class _MyAppState extends State<_MyApp> { Card _buildProductList() { if (_loading) { - return Card( - child: (ListTile( + return const Card( + child: ListTile( leading: CircularProgressIndicator(), - title: Text('Fetching products...')))); + title: Text('Fetching products...'))); } if (!_isAvailable) { - return Card(); + return const Card(); } - final ListTile productHeader = ListTile(title: Text('Products for Sale')); - List productList = []; + const ListTile productHeader = ListTile(title: Text('Products for Sale')); + final List productList = []; if (_notFoundIds.isNotEmpty) { productList.add(ListTile( title: Text('[${_notFoundIds.join(", ")}] not found', style: TextStyle(color: ThemeData.light().errorColor)), - subtitle: Text( + subtitle: const Text( 'This app needs special configuration to run. Please see example/README.md for instructions.'))); } // This loading previous purchases code is just a demo. Please do not use this as it is. // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. // We recommend that you use your own server to verify the purchase data. - Map purchases = - Map.fromEntries(_purchases.map((PurchaseDetails purchase) { + final Map purchases = + Map.fromEntries( + _purchases.map((PurchaseDetails purchase) { if (purchase.pendingCompletePurchase) { _inAppPurchase.completePurchase(purchase); } @@ -241,7 +245,7 @@ class _MyAppState extends State<_MyApp> { })); productList.addAll(_products.map( (ProductDetails productDetails) { - PurchaseDetails? previousPurchase = purchases[productDetails.id]; + final PurchaseDetails? previousPurchase = purchases[productDetails.id]; return ListTile( title: Text( productDetails.title, @@ -252,7 +256,7 @@ class _MyAppState extends State<_MyApp> { trailing: previousPurchase != null ? IconButton( onPressed: () => confirmPriceChange(context), - icon: Icon(Icons.upgrade)) + icon: const Icon(Icons.upgrade)) : TextButton( child: Text(productDetails.price), style: TextButton.styleFrom( @@ -267,7 +271,7 @@ class _MyAppState extends State<_MyApp> { // verify the latest status of you your subscription by using server side receipt validation // and update the UI accordingly. The subscription purchase status shown // inside the app may not be accurate. - final oldSubscription = + final GooglePlayPurchaseDetails? oldSubscription = _getOldSubscription(productDetails, purchases); purchaseParam = GooglePlayPurchaseParam( @@ -301,26 +305,26 @@ class _MyAppState extends State<_MyApp> { )); return Card( - child: - Column(children: [productHeader, Divider()] + productList)); + child: Column( + children: [productHeader, const Divider()] + productList)); } Card _buildConsumableBox() { if (_loading) { - return Card( - child: (ListTile( + return const Card( + child: ListTile( leading: CircularProgressIndicator(), - title: Text('Fetching consumables...')))); + title: Text('Fetching consumables...'))); } if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { - return Card(); + return const Card(); } - final ListTile consumableHeader = + const ListTile consumableHeader = ListTile(title: Text('Purchased consumables')); final List tokens = _consumables.map((String id) { return GridTile( child: IconButton( - icon: Icon( + icon: const Icon( Icons.stars, size: 42.0, color: Colors.orange, @@ -333,12 +337,12 @@ class _MyAppState extends State<_MyApp> { return Card( child: Column(children: [ consumableHeader, - Divider(), + const Divider(), GridView.count( crossAxisCount: 5, children: tokens, shrinkWrap: true, - padding: EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16.0), ) ])); } @@ -353,9 +357,9 @@ class _MyAppState extends State<_MyApp> { child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, - children: [ + children: [ TextButton( - child: Text('Restore purchases'), + child: const Text('Restore purchases'), style: TextButton.styleFrom( backgroundColor: Theme.of(context).primaryColor, primary: Colors.white, @@ -381,11 +385,11 @@ class _MyAppState extends State<_MyApp> { }); } - void deliverProduct(PurchaseDetails purchaseDetails) async { + Future deliverProduct(PurchaseDetails purchaseDetails) async { // IMPORTANT!! Always verify purchase details before delivering the product. if (purchaseDetails.productID == _kConsumableId) { await ConsumableStore.save(purchaseDetails.purchaseID!); - List consumables = await ConsumableStore.load(); + final List consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; @@ -414,8 +418,9 @@ class _MyAppState extends State<_MyApp> { // handle invalid purchase here if _verifyPurchase` failed. } - void _listenToPurchaseUpdated(List purchaseDetailsList) { - purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { + Future _listenToPurchaseUpdated( + List purchaseDetailsList) async { + for (final PurchaseDetails purchaseDetails in purchaseDetailsList) { if (purchaseDetails.status == PurchaseStatus.pending) { showPendingUI(); } else { @@ -423,7 +428,7 @@ class _MyAppState extends State<_MyApp> { handleError(purchaseDetails.error!); } else if (purchaseDetails.status == PurchaseStatus.purchased || purchaseDetails.status == PurchaseStatus.restored) { - bool valid = await _verifyPurchase(purchaseDetails); + final bool valid = await _verifyPurchase(purchaseDetails); if (valid) { deliverProduct(purchaseDetails); } else { @@ -443,7 +448,7 @@ class _MyAppState extends State<_MyApp> { await _inAppPurchase.completePurchase(purchaseDetails); } } - }); + } } Future confirmPriceChange(BuildContext context) async { @@ -451,26 +456,27 @@ class _MyAppState extends State<_MyApp> { final InAppPurchaseAndroidPlatformAddition androidAddition = _inAppPurchase .getPlatformAddition(); - var priceChangeConfirmationResult = + final BillingResultWrapper priceChangeConfirmationResult = await androidAddition.launchPriceChangeConfirmationFlow( sku: 'purchaseId', ); if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) { - ScaffoldMessenger.of(context).showSnackBar(SnackBar( + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( content: Text('Price change accepted'), )); } else { ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( priceChangeConfirmationResult.debugMessage ?? - "Price change failed with code ${priceChangeConfirmationResult.responseCode}", + 'Price change failed with code ${priceChangeConfirmationResult.responseCode}', ), )); } } if (Platform.isIOS) { - var iapStoreKitPlatformAddition = _inAppPurchase - .getPlatformAddition(); + final InAppPurchaseStoreKitPlatformAddition iapStoreKitPlatformAddition = + _inAppPurchase + .getPlatformAddition(); await iapStoreKitPlatformAddition.showPriceConsentIfNeeded(); } } @@ -488,11 +494,11 @@ class _MyAppState extends State<_MyApp> { if (productDetails.id == _kSilverSubscriptionId && purchases[_kGoldSubscriptionId] != null) { oldSubscription = - purchases[_kGoldSubscriptionId] as GooglePlayPurchaseDetails; + purchases[_kGoldSubscriptionId]! as GooglePlayPurchaseDetails; } else if (productDetails.id == _kGoldSubscriptionId && purchases[_kSilverSubscriptionId] != null) { oldSubscription = - purchases[_kSilverSubscriptionId] as GooglePlayPurchaseDetails; + purchases[_kSilverSubscriptionId]! as GooglePlayPurchaseDetails; } return oldSubscription; } diff --git a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml index a75aaa689eea..4a79b190bff9 100644 --- a/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/example/pubspec.yaml @@ -9,8 +9,6 @@ environment: dependencies: flutter: sdk: flutter - shared_preferences: ^2.0.0 - in_app_purchase: # When depending on this package from a real application you should use: # in_app_purchase: ^x.y.z @@ -18,6 +16,7 @@ dependencies: # The example app is bundled with the plugin so we use a path dependency on # the parent directory to use the current plugin's version. path: ../ + shared_preferences: ^2.0.0 dev_dependencies: flutter_driver: @@ -25,7 +24,6 @@ dev_dependencies: integration_test: sdk: flutter - pedantic: ^1.10.0 flutter: uses-material-design: true diff --git a/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart b/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart index 2a14f2b26ed7..df05d8ce86c3 100644 --- a/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart +++ b/packages/in_app_purchase/in_app_purchase/lib/in_app_purchase.dart @@ -4,8 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; export 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart' diff --git a/packages/in_app_purchase/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/in_app_purchase/pubspec.yaml index 80c150d3f9e2..139d58318780 100644 --- a/packages/in_app_purchase/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. repository: https://github.com/flutter/plugins/tree/main/packages/in_app_purchase/in_app_purchase issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 3.0.0 +version: 3.0.1 environment: sdk: ">=2.12.0 <3.0.0" @@ -19,8 +19,8 @@ flutter: dependencies: flutter: sdk: flutter - in_app_purchase_platform_interface: ^1.0.0 in_app_purchase_android: ^0.2.1 + in_app_purchase_platform_interface: ^1.0.0 in_app_purchase_storekit: ^0.3.0+1 dev_dependencies: @@ -30,6 +30,5 @@ dev_dependencies: sdk: flutter integration_test: sdk: flutter - pedantic: ^1.10.0 plugin_platform_interface: ^2.0.0 test: ^1.16.0 diff --git a/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart index b8c7bd89206b..644d26ed50ad 100644 --- a/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart +++ b/packages/in_app_purchase/in_app_purchase/test/in_app_purchase_test.dart @@ -65,7 +65,7 @@ void main() { test('queryProductDetails', () async { final ProductDetailsResponse response = - await inAppPurchase.queryProductDetails(Set()); + await inAppPurchase.queryProductDetails({}); expect(response.notFoundIDs.isEmpty, true); expect(response.productDetails.isEmpty, true); expect(fakePlatform.log, [ @@ -87,22 +87,24 @@ void main() { }); test('buyConsumable', () async { - final purchaseParam = PurchaseParam(productDetails: productDetails); + final PurchaseParam purchaseParam = + PurchaseParam(productDetails: productDetails); final bool result = await inAppPurchase.buyConsumable( purchaseParam: purchaseParam, ); expect(result, true); expect(fakePlatform.log, [ - isMethodCall('buyConsumable', arguments: { - "purchaseParam": purchaseParam, - "autoConsume": true, + isMethodCall('buyConsumable', arguments: { + 'purchaseParam': purchaseParam, + 'autoConsume': true, }), ]); }); test('buyConsumable with autoConsume=false', () async { - final purchaseParam = PurchaseParam(productDetails: productDetails); + final PurchaseParam purchaseParam = + PurchaseParam(productDetails: productDetails); final bool result = await inAppPurchase.buyConsumable( purchaseParam: purchaseParam, autoConsume: false, @@ -110,9 +112,9 @@ void main() { expect(result, true); expect(fakePlatform.log, [ - isMethodCall('buyConsumable', arguments: { - "purchaseParam": purchaseParam, - "autoConsume": false, + isMethodCall('buyConsumable', arguments: { + 'purchaseParam': purchaseParam, + 'autoConsume': false, }), ]); }); @@ -138,31 +140,33 @@ void main() { class MockInAppPurchasePlatform extends Fake with MockPlatformInterfaceMixin implements InAppPurchasePlatform { - final List log = []; + final List log = []; @override Future isAvailable() { - log.add(MethodCall('isAvailable')); - return Future.value(true); + log.add(const MethodCall('isAvailable')); + return Future.value(true); } @override Stream> get purchaseStream { - log.add(MethodCall('purchaseStream')); - return Stream.empty(); + log.add(const MethodCall('purchaseStream')); + return const Stream>.empty(); } @override Future queryProductDetails(Set identifiers) { - log.add(MethodCall('queryProductDetails')); - return Future.value( - ProductDetailsResponse(productDetails: [], notFoundIDs: [])); + log.add(const MethodCall('queryProductDetails')); + return Future.value(ProductDetailsResponse( + productDetails: [], + notFoundIDs: [], + )); } @override Future buyNonConsumable({required PurchaseParam purchaseParam}) { - log.add(MethodCall('buyNonConsumable')); - return Future.value(true); + log.add(const MethodCall('buyNonConsumable')); + return Future.value(true); } @override @@ -170,22 +174,22 @@ class MockInAppPurchasePlatform extends Fake required PurchaseParam purchaseParam, bool autoConsume = true, }) { - log.add(MethodCall('buyConsumable', { - "purchaseParam": purchaseParam, - "autoConsume": autoConsume, + log.add(MethodCall('buyConsumable', { + 'purchaseParam': purchaseParam, + 'autoConsume': autoConsume, })); - return Future.value(true); + return Future.value(true); } @override Future completePurchase(PurchaseDetails purchase) { - log.add(MethodCall('completePurchase')); - return Future.value(null); + log.add(const MethodCall('completePurchase')); + return Future.value(null); } @override Future restorePurchases({String? applicationUserName}) { - log.add(MethodCall('restorePurchases')); - return Future.value(null); + log.add(const MethodCall('restorePurchases')); + return Future.value(null); } } diff --git a/script/configs/custom_analysis.yaml b/script/configs/custom_analysis.yaml index e08024c22ecd..23b7f7bbc35b 100644 --- a/script/configs/custom_analysis.yaml +++ b/script/configs/custom_analysis.yaml @@ -13,4 +13,3 @@ # https://github.com/flutter/flutter/issues/76229 - google_maps_flutter/google_maps_flutter_platform_interface - google_maps_flutter/google_maps_flutter_web -- in_app_purchase/in_app_purchase