Skip to content

Commit e11179d

Browse files
author
Chris Yang
authored
[in_app_purchase] platform interface improvement (flutter#3821)
Adds some improvements to the platform interface. 1. Make `InAppPurchasePlatformAddition` a subclass of `PlatformInterface` to take advantage of the existing token checking logic. (I also consider `InAppPurchasePlatformAddition` a `PlatformInterface` because it works in a similar way. 2. Make the `instance` variable `late` as we should never access it before setter. 3. Add tests for `InAppPurchasePlatformAddition` part of flutter#78525
1 parent fcbb0ce commit e11179d

File tree

7 files changed

+114
-24
lines changed

7 files changed

+114
-24
lines changed

packages/in_app_purchase/in_app_purchase/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ Beta release.
354354

355355
* Ability to list products, load previous purchases, and make purchases.
356356
* Simplified Dart API that's been unified for ease of use.
357-
* Platform specific APIs more directly exposing `StoreKit` and `BillingClient`.
357+
* Platform-specific APIs more directly exposing `StoreKit` and `BillingClient`.
358358

359359
Includes:
360360

packages/in_app_purchase/in_app_purchase/lib/src/in_app_purchase/product_details.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class ProductDetailsResponse {
9090

9191
/// The list of identifiers that are in the `identifiers` of [InAppPurchaseConnection.queryProductDetails] but failed to be fetched.
9292
///
93-
/// There's multiple platform specific reasons that product information could fail to be fetched,
93+
/// There's multiple platform-specific reasons that product information could fail to be fetched,
9494
/// ranging from products not being correctly configured in the storefront to the queried IDs not existing.
9595
final List<String> notFoundIDs;
9696

packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,28 @@ abstract class InAppPurchasePlatform extends PlatformInterface {
2323

2424
/// The instance of [InAppPurchasePlatform] to use.
2525
///
26-
/// Defaults to `null`.
27-
static InAppPurchasePlatform? get instance => _instance;
28-
29-
static InAppPurchasePlatform? _instance;
26+
/// Must be set before accessing.
27+
static InAppPurchasePlatform get instance => _instance;
3028

3129
/// Platform-specific plugins should set this with their own platform-specific
3230
/// class that extends [InAppPurchasePlatform] when they register themselves.
3331
// TODO(amirh): Extract common platform interface logic.
3432
// https://github.com/flutter/flutter/issues/43368
35-
static void setInstance(InAppPurchasePlatform instance) {
33+
static set instance(InAppPurchasePlatform instance) {
3634
PlatformInterface.verifyToken(instance, _token);
3735
_instance = instance;
3836
}
3937

38+
// Should only be accessed after setter is called.
39+
static late InAppPurchasePlatform _instance;
40+
4041
/// Listen to this broadcast stream to get real time update for purchases.
4142
///
4243
/// This stream will never close as long as the app is active.
4344
///
4445
/// Purchase updates can happen in several situations:
4546
/// * When a purchase is triggered by user in the app.
46-
/// * When a purchase is triggered by user from the platform specific store front.
47+
/// * When a purchase is triggered by user from the platform-specific store front.
4748
/// * When a purchase is restored on the device by the user in the app.
4849
/// * If a purchase is not completed ([completePurchase] is not called on the
4950
/// purchase object) from the last app session. Purchase updates will happen

packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,33 @@
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 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
6+
57
// ignore: avoid_classes_with_only_static_members
68
/// The interface that platform implementations must implement when they want to
7-
/// provide platform specific in_app_purchase features.
9+
/// provide platform-specific in_app_purchase features.
10+
///
11+
/// Platforms that wants to introduce platform-specific public APIs should create
12+
/// a class that either extend or implements [InAppPurchasePlatformAddition]. Then set
13+
/// the [InAppPurchasePlatformAddition.instance] to an instance of that class.
14+
///
15+
/// All the APIs added by [InAppPurchasePlatformAddition] implementations will be accessed from
16+
/// [InAppPurchasePlatformAdditionProvider.getPlatformAddition] by the client APPs.
17+
/// To avoid clients directly calling [InAppPurchasePlatform] APIs,
18+
/// an [InAppPurchasePlatformAddition] implementation should not be a type of [InAppPurchasePlatform].
819
abstract class InAppPurchasePlatformAddition {
20+
static InAppPurchasePlatformAddition? _instance;
21+
922
/// The instance containing the platform-specific in_app_purchase
1023
/// functionality.
1124
///
25+
/// Returns `null` by default.
26+
///
1227
/// To implement additional functionality extend
1328
/// [`InAppPurchasePlatformAddition`][3] with the platform-specific
1429
/// functionality, and when the plugin is registered, set the
1530
/// `InAppPurchasePlatformAddition.instance` with the new addition
16-
/// implementationinstance.
31+
/// implementation instance.
1732
///
1833
/// Example implementation might look like this:
1934
/// ```dart
@@ -22,7 +37,7 @@ abstract class InAppPurchasePlatformAddition {
2237
/// }
2338
/// ```
2439
///
25-
/// The following snippit shows how to register the `InAppPurchaseMyPlatformAddition`:
40+
/// The following snippet shows how to register the `InAppPurchaseMyPlatformAddition`:
2641
/// ```dart
2742
/// class InAppPurchaseMyPlatformPlugin {
2843
/// static void registerWith(Registrar registrar) {
@@ -36,5 +51,13 @@ abstract class InAppPurchasePlatformAddition {
3651
/// }
3752
/// }
3853
/// ```
39-
static InAppPurchasePlatformAddition? instance;
54+
static InAppPurchasePlatformAddition? get instance => _instance;
55+
56+
/// Sets the instance to a desired [InAppPurchasePlatformAddition] implementation.
57+
///
58+
/// The `instance` should not be a type of [InAppPurchasePlatform].
59+
static set instance(InAppPurchasePlatformAddition? instance) {
60+
assert(instance is! InAppPurchasePlatform);
61+
_instance = instance;
62+
}
4063
}

packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart';
66

77
/// The [InAppPurchasePlatformAdditionProvider] is responsible for providing
8-
/// a platform specific [InAppPurchasePlatformAddition].
8+
/// a platform-specific [InAppPurchasePlatformAddition].
99
///
10-
/// [InAppPurchasePlatformAddition] implementation contain platform specific
10+
/// [InAppPurchasePlatformAddition] implementation contain platform-specific
1111
/// features that are not available from the platform idiomatic
1212
/// [InAppPurchasePlatform] API.
1313
abstract class InAppPurchasePlatformAdditionProvider {
14-
/// Provides a platform specific implementation of the [InAppPurchasePlatformAddition]
14+
/// Provides a platform-specific implementation of the [InAppPurchasePlatformAddition]
1515
/// class.
16-
T getPlatformAddition<T extends InAppPurchasePlatformAddition>();
16+
T getPlatformAddition<T extends InAppPurchasePlatformAddition?>();
1717
}

packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details_response.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ProductDetailsResponse {
1818

1919
/// The list of identifiers that are in the `identifiers` of [InAppPurchasePlatform.queryProductDetails] but failed to be fetched.
2020
///
21-
/// There's multiple platform specific reasons that product information could fail to be fetched,
21+
/// There are multiple platform-specific reasons that product information could fail to be fetched,
2222
/// ranging from products not being correctly configured in the storefront to the queried IDs not existing.
2323
final List<String> notFoundIDs;
2424

packages/in_app_purchase/in_app_purchase_platform_interface/test/in_app_purchase_platform_test.dart

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,18 @@ void main() {
1111
TestWidgetsFlutterBinding.ensureInitialized();
1212

1313
group('$InAppPurchasePlatform', () {
14-
test('Default instance should return null', () {
15-
expect(InAppPurchasePlatform.instance, null);
16-
});
17-
1814
test('Cannot be implemented with `implements`', () {
1915
expect(() {
20-
InAppPurchasePlatform.setInstance(ImplementsInAppPurchasePlatform());
16+
InAppPurchasePlatform.instance = ImplementsInAppPurchasePlatform();
2117
}, throwsNoSuchMethodError);
2218
});
2319

2420
test('Can be extended', () {
25-
InAppPurchasePlatform.setInstance(ExtendsInAppPurchasePlatform());
21+
InAppPurchasePlatform.instance = ExtendsInAppPurchasePlatform();
2622
});
2723

2824
test('Can be mocked with `implements`', () {
29-
InAppPurchasePlatform.setInstance(MockInAppPurchasePlatform());
25+
InAppPurchasePlatform.instance = MockInAppPurchasePlatform();
3026
});
3127

3228
test(
@@ -124,6 +120,50 @@ void main() {
124120
);
125121
});
126122
});
123+
124+
group('$InAppPurchasePlatformAddition', () {
125+
setUp(() {
126+
InAppPurchasePlatformAddition.instance = null;
127+
});
128+
129+
test('Cannot be implemented with `implements`', () {
130+
expect(InAppPurchasePlatformAddition.instance, isNull);
131+
});
132+
133+
test('Can be implemented.', () {
134+
InAppPurchasePlatformAddition.instance =
135+
ImplementsInAppPurchasePlatformAddition();
136+
});
137+
138+
test('InAppPurchasePlatformAddition Can be extended', () {
139+
InAppPurchasePlatformAddition.instance =
140+
ExtendsInAppPurchasePlatformAddition();
141+
});
142+
143+
test('Can not be a `InAppPurchasePlatform`', () {
144+
expect(
145+
() => InAppPurchasePlatformAddition.instance =
146+
ExtendsInAppPurchasePlatformAdditionIsPlatformInterface(),
147+
throwsAssertionError);
148+
});
149+
150+
test('Provider can provide', () {
151+
ImplementsInAppPurchasePlatformAdditionProvider.register();
152+
final ImplementsInAppPurchasePlatformAdditionProvider provider =
153+
ImplementsInAppPurchasePlatformAdditionProvider();
154+
final InAppPurchasePlatformAddition? addition =
155+
provider.getPlatformAddition();
156+
expect(addition.runtimeType, ExtendsInAppPurchasePlatformAddition);
157+
});
158+
159+
test('Provider can provide `null`', () {
160+
final ImplementsInAppPurchasePlatformAdditionProvider provider =
161+
ImplementsInAppPurchasePlatformAdditionProvider();
162+
final InAppPurchasePlatformAddition? addition =
163+
provider.getPlatformAddition();
164+
expect(addition, isNull);
165+
});
166+
});
127167
}
128168

129169
class ImplementsInAppPurchasePlatform implements InAppPurchasePlatform {
@@ -143,3 +183,29 @@ class ExtendsInAppPurchasePlatform extends InAppPurchasePlatform {}
143183
class MockPurchaseParam extends Mock implements PurchaseParam {}
144184

145185
class MockPurchaseDetails extends Mock implements PurchaseDetails {}
186+
187+
class ImplementsInAppPurchasePlatformAddition
188+
implements InAppPurchasePlatformAddition {
189+
@override
190+
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
191+
}
192+
193+
class ExtendsInAppPurchasePlatformAddition
194+
extends InAppPurchasePlatformAddition {}
195+
196+
class ImplementsInAppPurchasePlatformAdditionProvider
197+
implements InAppPurchasePlatformAdditionProvider {
198+
static void register() {
199+
InAppPurchasePlatformAddition.instance =
200+
ExtendsInAppPurchasePlatformAddition();
201+
}
202+
203+
@override
204+
T getPlatformAddition<T extends InAppPurchasePlatformAddition?>() {
205+
return InAppPurchasePlatformAddition.instance as T;
206+
}
207+
}
208+
209+
class ExtendsInAppPurchasePlatformAdditionIsPlatformInterface
210+
extends InAppPurchasePlatform
211+
implements ExtendsInAppPurchasePlatformAddition {}

0 commit comments

Comments
 (0)