Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

[in_app_purchase] platform interface improvement #3821

Merged
merged 10 commits into from
May 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/in_app_purchase/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> notFoundIDs;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 extends InAppPurchasePlatformAddition>();
T getPlatformAddition<T extends InAppPurchasePlatformAddition?>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> notFoundIDs;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 {
Expand All @@ -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<T extends InAppPurchasePlatformAddition?>() {
return InAppPurchasePlatformAddition.instance as T;
}
}

class ExtendsInAppPurchasePlatformAdditionIsPlatformInterface
extends InAppPurchasePlatform
implements ExtendsInAppPurchasePlatformAddition {}