diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md index 5e7f54560b8c..45b8b7661203 100644 --- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md @@ -354,7 +354,7 @@ Beta release. * Ability to list products, load previous purchases, and make purchases. * Simplified Dart API that's been unified for ease of use. -* Platform specific APIs more directly exposing `StoreKit` and `BillingClient`. +* Platform-specific APIs more directly exposing `StoreKit` and `BillingClient`. Includes: diff --git a/packages/in_app_purchase/in_app_purchase/lib/src/in_app_purchase/product_details.dart b/packages/in_app_purchase/in_app_purchase/lib/src/in_app_purchase/product_details.dart index ccdec42b7303..4ba61305e445 100644 --- a/packages/in_app_purchase/in_app_purchase/lib/src/in_app_purchase/product_details.dart +++ b/packages/in_app_purchase/in_app_purchase/lib/src/in_app_purchase/product_details.dart @@ -90,7 +90,7 @@ class ProductDetailsResponse { /// The list of identifiers that are in the `identifiers` of [InAppPurchaseConnection.queryProductDetails] but failed to be fetched. /// - /// There's multiple platform specific reasons that product information could fail to be fetched, + /// There's multiple platform-specific reasons that product information could fail to be fetched, /// ranging from products not being correctly configured in the storefront to the queried IDs not existing. final List notFoundIDs; diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart index f8dc4c998494..eac4a0712078 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart @@ -23,27 +23,28 @@ abstract class InAppPurchasePlatform extends PlatformInterface { /// The instance of [InAppPurchasePlatform] to use. /// - /// Defaults to `null`. - static InAppPurchasePlatform? get instance => _instance; - - static InAppPurchasePlatform? _instance; + /// Must be set before accessing. + static InAppPurchasePlatform get instance => _instance; /// Platform-specific plugins should set this with their own platform-specific /// class that extends [InAppPurchasePlatform] when they register themselves. // TODO(amirh): Extract common platform interface logic. // https://github.com/flutter/flutter/issues/43368 - static void setInstance(InAppPurchasePlatform instance) { + static set instance(InAppPurchasePlatform instance) { PlatformInterface.verifyToken(instance, _token); _instance = instance; } + // Should only be accessed after setter is called. + static late InAppPurchasePlatform _instance; + /// Listen to this broadcast stream to get real time update for purchases. /// /// This stream will never close as long as the app is active. /// /// Purchase updates can happen in several situations: /// * When a purchase is triggered by user in the app. - /// * When a purchase is triggered by user from the platform specific store front. + /// * When a purchase is triggered by user from the platform-specific store front. /// * When a purchase is restored on the device by the user in the app. /// * If a purchase is not completed ([completePurchase] is not called on the /// purchase object) from the last app session. Purchase updates will happen diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart index 5c41f138ecea..746675549295 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart @@ -2,18 +2,33 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + // ignore: avoid_classes_with_only_static_members /// The interface that platform implementations must implement when they want to -/// provide platform specific in_app_purchase features. +/// provide platform-specific in_app_purchase features. +/// +/// Platforms that wants to introduce platform-specific public APIs should create +/// a class that either extend or implements [InAppPurchasePlatformAddition]. Then set +/// the [InAppPurchasePlatformAddition.instance] to an instance of that class. +/// +/// All the APIs added by [InAppPurchasePlatformAddition] implementations will be accessed from +/// [InAppPurchasePlatformAdditionProvider.getPlatformAddition] by the client APPs. +/// To avoid clients directly calling [InAppPurchasePlatform] APIs, +/// an [InAppPurchasePlatformAddition] implementation should not be a type of [InAppPurchasePlatform]. abstract class InAppPurchasePlatformAddition { + static InAppPurchasePlatformAddition? _instance; + /// The instance containing the platform-specific in_app_purchase /// functionality. /// + /// Returns `null` by default. + /// /// To implement additional functionality extend /// [`InAppPurchasePlatformAddition`][3] with the platform-specific /// functionality, and when the plugin is registered, set the /// `InAppPurchasePlatformAddition.instance` with the new addition - /// implementationinstance. + /// implementation instance. /// /// Example implementation might look like this: /// ```dart @@ -22,7 +37,7 @@ abstract class InAppPurchasePlatformAddition { /// } /// ``` /// - /// The following snippit shows how to register the `InAppPurchaseMyPlatformAddition`: + /// The following snippet shows how to register the `InAppPurchaseMyPlatformAddition`: /// ```dart /// class InAppPurchaseMyPlatformPlugin { /// static void registerWith(Registrar registrar) { @@ -36,5 +51,13 @@ abstract class InAppPurchasePlatformAddition { /// } /// } /// ``` - static InAppPurchasePlatformAddition? instance; + static InAppPurchasePlatformAddition? get instance => _instance; + + /// Sets the instance to a desired [InAppPurchasePlatformAddition] implementation. + /// + /// The `instance` should not be a type of [InAppPurchasePlatform]. + static set instance(InAppPurchasePlatformAddition? instance) { + assert(instance is! InAppPurchasePlatform); + _instance = instance; + } } diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart index d981f73b4019..642bbb419c6e 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart @@ -5,13 +5,13 @@ import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart'; /// The [InAppPurchasePlatformAdditionProvider] is responsible for providing -/// a platform specific [InAppPurchasePlatformAddition]. +/// a platform-specific [InAppPurchasePlatformAddition]. /// -/// [InAppPurchasePlatformAddition] implementation contain platform specific +/// [InAppPurchasePlatformAddition] implementation contain platform-specific /// features that are not available from the platform idiomatic /// [InAppPurchasePlatform] API. abstract class InAppPurchasePlatformAdditionProvider { - /// Provides a platform specific implementation of the [InAppPurchasePlatformAddition] + /// Provides a platform-specific implementation of the [InAppPurchasePlatformAddition] /// class. - T getPlatformAddition(); + T getPlatformAddition(); } diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart index 868f9428add2..11b244a84ae3 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart @@ -18,7 +18,7 @@ class ProductDetailsResponse { /// The list of identifiers that are in the `identifiers` of [InAppPurchasePlatform.queryProductDetails] but failed to be fetched. /// - /// There's multiple platform specific reasons that product information could fail to be fetched, + /// There are multiple platform-specific reasons that product information could fail to be fetched, /// ranging from products not being correctly configured in the storefront to the queried IDs not existing. final List notFoundIDs; diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart index d5c1ae5fc127..9c0f2dc00020 100644 --- a/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart @@ -11,22 +11,18 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('$InAppPurchasePlatform', () { - test('Default instance should return null', () { - expect(InAppPurchasePlatform.instance, null); - }); - test('Cannot be implemented with `implements`', () { expect(() { - InAppPurchasePlatform.setInstance(ImplementsInAppPurchasePlatform()); + InAppPurchasePlatform.instance = ImplementsInAppPurchasePlatform(); }, throwsNoSuchMethodError); }); test('Can be extended', () { - InAppPurchasePlatform.setInstance(ExtendsInAppPurchasePlatform()); + InAppPurchasePlatform.instance = ExtendsInAppPurchasePlatform(); }); test('Can be mocked with `implements`', () { - InAppPurchasePlatform.setInstance(MockInAppPurchasePlatform()); + InAppPurchasePlatform.instance = MockInAppPurchasePlatform(); }); test( @@ -124,6 +120,50 @@ void main() { ); }); }); + + group('$InAppPurchasePlatformAddition', () { + setUp(() { + InAppPurchasePlatformAddition.instance = null; + }); + + test('Cannot be implemented with `implements`', () { + expect(InAppPurchasePlatformAddition.instance, isNull); + }); + + test('Can be implemented.', () { + InAppPurchasePlatformAddition.instance = + ImplementsInAppPurchasePlatformAddition(); + }); + + test('InAppPurchasePlatformAddition Can be extended', () { + InAppPurchasePlatformAddition.instance = + ExtendsInAppPurchasePlatformAddition(); + }); + + test('Can not be a `InAppPurchasePlatform`', () { + expect( + () => InAppPurchasePlatformAddition.instance = + ExtendsInAppPurchasePlatformAdditionIsPlatformInterface(), + throwsAssertionError); + }); + + test('Provider can provide', () { + ImplementsInAppPurchasePlatformAdditionProvider.register(); + final ImplementsInAppPurchasePlatformAdditionProvider provider = + ImplementsInAppPurchasePlatformAdditionProvider(); + final InAppPurchasePlatformAddition? addition = + provider.getPlatformAddition(); + expect(addition.runtimeType, ExtendsInAppPurchasePlatformAddition); + }); + + test('Provider can provide `null`', () { + final ImplementsInAppPurchasePlatformAdditionProvider provider = + ImplementsInAppPurchasePlatformAdditionProvider(); + final InAppPurchasePlatformAddition? addition = + provider.getPlatformAddition(); + expect(addition, isNull); + }); + }); } class ImplementsInAppPurchasePlatform implements InAppPurchasePlatform { @@ -143,3 +183,29 @@ class ExtendsInAppPurchasePlatform extends InAppPurchasePlatform {} class MockPurchaseParam extends Mock implements PurchaseParam {} class MockPurchaseDetails extends Mock implements PurchaseDetails {} + +class ImplementsInAppPurchasePlatformAddition + implements InAppPurchasePlatformAddition { + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +class ExtendsInAppPurchasePlatformAddition + extends InAppPurchasePlatformAddition {} + +class ImplementsInAppPurchasePlatformAdditionProvider + implements InAppPurchasePlatformAdditionProvider { + static void register() { + InAppPurchasePlatformAddition.instance = + ExtendsInAppPurchasePlatformAddition(); + } + + @override + T getPlatformAddition() { + return InAppPurchasePlatformAddition.instance as T; + } +} + +class ExtendsInAppPurchasePlatformAdditionIsPlatformInterface + extends InAppPurchasePlatform + implements ExtendsInAppPurchasePlatformAddition {}