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

Commit

Permalink
[in_app_purchase] Migrate to NNBD (#3555)
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Yang authored Feb 19, 2021
1 parent 2315531 commit 0638189
Show file tree
Hide file tree
Showing 43 changed files with 1,134 additions and 709 deletions.
7 changes: 7 additions & 0 deletions packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 0.4.0

* Migrate to nullsafety.
* Deprecate `sandboxTesting`, introduce `simulatesAskToBuyInSandbox`.
* **Breaking Change:**
* Removed `callbackChannel` in `channels.dart`, see https://github.com/flutter/flutter/issues/69225.

## 0.3.5+2

* Migrate deprecated references.
Expand Down
1 change: 0 additions & 1 deletion packages/in_app_purchase/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ targets:
options:
any_map: true
create_to_json: true
nullable: false
19 changes: 9 additions & 10 deletions packages/in_app_purchase/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ class _MyApp extends StatefulWidget {

class _MyAppState extends State<_MyApp> {
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
StreamSubscription<List<PurchaseDetails>> _subscription;
late StreamSubscription<List<PurchaseDetails>> _subscription;
List<String> _notFoundIds = [];
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
List<String> _consumables = [];
bool _isAvailable = false;
bool _purchasePending = false;
bool _loading = true;
String _queryProductError;
String? _queryProductError;

@override
void initState() {
Stream purchaseUpdated =
final Stream<List<PurchaseDetails>> purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
Expand Down Expand Up @@ -76,7 +76,7 @@ class _MyAppState extends State<_MyApp> {
await _connection.queryProductDetails(_kProductIds.toSet());
if (productDetailResponse.error != null) {
setState(() {
_queryProductError = productDetailResponse.error.message;
_queryProductError = productDetailResponse.error!.message;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
Expand Down Expand Up @@ -146,7 +146,7 @@ class _MyAppState extends State<_MyApp> {
);
} else {
stack.add(Center(
child: Text(_queryProductError),
child: Text(_queryProductError!),
));
}
if (_purchasePending) {
Expand Down Expand Up @@ -235,7 +235,7 @@ class _MyAppState extends State<_MyApp> {
}));
productList.addAll(_products.map(
(ProductDetails productDetails) {
PurchaseDetails previousPurchase = purchases[productDetails.id];
PurchaseDetails? previousPurchase = purchases[productDetails.id];
return ListTile(
title: Text(
productDetails.title,
Expand All @@ -254,8 +254,7 @@ class _MyAppState extends State<_MyApp> {
onPressed: () {
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
applicationUserName: null,
sandboxTesting: true);
applicationUserName: null);
if (productDetails.id == _kConsumableId) {
_connection.buyConsumable(
purchaseParam: purchaseParam,
Expand Down Expand Up @@ -329,7 +328,7 @@ class _MyAppState extends State<_MyApp> {
void deliverProduct(PurchaseDetails purchaseDetails) async {
// IMPORTANT!! Always verify a purchase purchase details before delivering the product.
if (purchaseDetails.productID == _kConsumableId) {
await ConsumableStore.save(purchaseDetails.purchaseID);
await ConsumableStore.save(purchaseDetails.purchaseID!);
List<String> consumables = await ConsumableStore.load();
setState(() {
_purchasePending = false;
Expand Down Expand Up @@ -365,7 +364,7 @@ class _MyAppState extends State<_MyApp> {
showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
handleError(purchaseDetails.error);
handleError(purchaseDetails.error!);
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
Expand Down
8 changes: 3 additions & 5 deletions packages/in_app_purchase/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ author: Flutter Team <[email protected]>
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
shared_preferences: ^0.5.2
shared_preferences: ^2.0.0-nullsafety.1

dev_dependencies:
test: ^1.5.2
flutter_driver:
sdk: flutter
in_app_purchase:
Expand All @@ -21,11 +19,11 @@ dev_dependencies:
path: ../
integration_test:
path: ../../integration_test
pedantic: ^1.8.0
pedantic: ^1.10.0

flutter:
uses-material-design: true

environment:
sdk: ">=2.3.0 <3.0.0"
sdk: ">=2.12.0-259.9.beta <3.0.0"
flutter: ">=1.9.1+hotfix.2"
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// @dart = 2.9
import 'dart:async';
import 'dart:convert';
import 'dart:io';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// @dart = 2.9
import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:integration_test/integration_test.dart';
Expand Down
7 changes: 0 additions & 7 deletions packages/in_app_purchase/ios/Classes/FIAPReceiptManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

//
// FIAPReceiptManager.m
// in_app_purchase
//
// Created by Chris Yang on 3/2/19.
//

#import "FIAPReceiptManager.h"
#import <Flutter/Flutter.h>

Expand Down
4 changes: 2 additions & 2 deletions packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar
}];
[_paymentQueueHandler startObservingPaymentQueue];
_callbackChannel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase_callback"
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase"
binaryMessenger:[registrar messenger]];
return self;
}
Expand Down Expand Up @@ -290,7 +290,7 @@ - (void)refreshReceipt:(FlutterMethodCall *)call result:(FlutterResult)result {
}];
}

#pragma mark - delegates
#pragma mark - delegates:

- (void)handleTransactionsUpdated:(NSArray<SKPaymentTransaction *> *)transactions {
NSMutableArray *maps = [NSMutableArray new];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ class BillingClient {
bool _enablePendingPurchases = false;

/// Creates a billing client.
///
/// The `onPurchasesUpdated` parameter must not be null.
BillingClient(PurchasesUpdatedListener onPurchasesUpdated) {
assert(onPurchasesUpdated != null);
channel.setMethodCallHandler(callHandler);
_callbacks[kOnPurchasesUpdated] = [onPurchasesUpdated];
}
Expand All @@ -74,8 +71,11 @@ class BillingClient {
/// Calls
/// [`BillingClient#isReady()`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#isReady())
/// to get the ready status of the BillingClient instance.
Future<bool> isReady() async =>
await channel.invokeMethod<bool>('BillingClient#isReady()');
Future<bool> isReady() async {
final bool? ready =
await channel.invokeMethod<bool>('BillingClient#isReady()');
return ready ?? false;
}

/// Enable the [BillingClientWrapper] to handle pending purchases.
///
Expand All @@ -100,20 +100,21 @@ class BillingClient {
/// This triggers the creation of a new `BillingClient` instance in Java if
/// one doesn't already exist.
Future<BillingResultWrapper> startConnection(
{@required
OnBillingServiceDisconnected onBillingServiceDisconnected}) async {
{required OnBillingServiceDisconnected
onBillingServiceDisconnected}) async {
assert(_enablePendingPurchases,
'enablePendingPurchases() must be called before calling startConnection');
List<Function> disconnectCallbacks =
_callbacks[_kOnBillingServiceDisconnected] ??= [];
disconnectCallbacks.add(onBillingServiceDisconnected);
return BillingResultWrapper.fromJson(await channel
.invokeMapMethod<String, dynamic>(
"BillingClient#startConnection(BillingClientStateListener)",
<String, dynamic>{
'handle': disconnectCallbacks.length - 1,
'enablePendingPurchases': _enablePendingPurchases
}));
return BillingResultWrapper.fromJson((await channel
.invokeMapMethod<String, dynamic>(
"BillingClient#startConnection(BillingClientStateListener)",
<String, dynamic>{
'handle': disconnectCallbacks.length - 1,
'enablePendingPurchases': _enablePendingPurchases
})) ??
<String, dynamic>{});
}

/// Calls
Expand All @@ -137,15 +138,16 @@ class BillingClient {
/// `SkuDetailsParams` as direct arguments instead of requiring it constructed
/// and passed in as a class.
Future<SkuDetailsResponseWrapper> querySkuDetails(
{@required SkuType skuType, @required List<String> skusList}) async {
{required SkuType skuType, required List<String> skusList}) async {
final Map<String, dynamic> arguments = <String, dynamic>{
'skuType': SkuTypeConverter().toJson(skuType),
'skusList': skusList
};
return SkuDetailsResponseWrapper.fromJson(await channel.invokeMapMethod<
String, dynamic>(
'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)',
arguments));
return SkuDetailsResponseWrapper.fromJson((await channel.invokeMapMethod<
String, dynamic>(
'BillingClient#querySkuDetailsAsync(SkuDetailsParams, SkuDetailsResponseListener)',
arguments)) ??
<String, dynamic>{});
}

/// Attempt to launch the Play Billing Flow for a given [skuDetails].
Expand All @@ -172,16 +174,17 @@ class BillingClient {
/// and [the given
/// accountId](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder.html#setAccountId(java.lang.String)).
Future<BillingResultWrapper> launchBillingFlow(
{@required String sku, String accountId}) async {
{required String sku, String? accountId}) async {
assert(sku != null);
final Map<String, dynamic> arguments = <String, dynamic>{
'sku': sku,
'accountId': accountId,
};
return BillingResultWrapper.fromJson(
await channel.invokeMapMethod<String, dynamic>(
'BillingClient#launchBillingFlow(Activity, BillingFlowParams)',
arguments));
(await channel.invokeMapMethod<String, dynamic>(
'BillingClient#launchBillingFlow(Activity, BillingFlowParams)',
arguments)) ??
<String, dynamic>{});
}

/// Fetches recent purchases for the given [SkuType].
Expand All @@ -197,10 +200,12 @@ class BillingClient {
/// skutype)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchases).
Future<PurchasesResultWrapper> queryPurchases(SkuType skuType) async {
assert(skuType != null);
return PurchasesResultWrapper.fromJson(await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#queryPurchases(String)',
<String, dynamic>{'skuType': SkuTypeConverter().toJson(skuType)}));
return PurchasesResultWrapper.fromJson((await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#queryPurchases(String)', <String, dynamic>{
'skuType': SkuTypeConverter().toJson(skuType)
})) ??
<String, dynamic>{});
}

/// Fetches purchase history for the given [SkuType].
Expand All @@ -218,31 +223,34 @@ class BillingClient {
/// listener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#querypurchasehistoryasync).
Future<PurchasesHistoryResult> queryPurchaseHistory(SkuType skuType) async {
assert(skuType != null);
return PurchasesHistoryResult.fromJson(await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)',
<String, dynamic>{'skuType': SkuTypeConverter().toJson(skuType)}));
return PurchasesHistoryResult.fromJson((await channel.invokeMapMethod<
String, dynamic>(
'BillingClient#queryPurchaseHistoryAsync(String, PurchaseHistoryResponseListener)',
<String, dynamic>{
'skuType': SkuTypeConverter().toJson(skuType)
})) ??
<String, dynamic>{});
}

/// Consumes a given in-app product.
///
/// Consuming can only be done on an item that's owned, and as a result of consumption, the user will no longer own it.
/// Consumption is done asynchronously. The method returns a Future containing a [BillingResultWrapper].
///
/// The `purchaseToken` must not be null.
/// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
///
/// This wraps [`BillingClient#consumeAsync(String, ConsumeResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#consumeAsync(java.lang.String,%20com.android.billingclient.api.ConsumeResponseListener))
Future<BillingResultWrapper> consumeAsync(String purchaseToken,
{String developerPayload}) async {
{String? developerPayload}) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson(await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#consumeAsync(String, ConsumeResponseListener)',
<String, String>{
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
}));
return BillingResultWrapper.fromJson((await channel
.invokeMapMethod<String, dynamic>(
'BillingClient#consumeAsync(String, ConsumeResponseListener)',
<String, dynamic>{
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
})) ??
<String, dynamic>{});
}

/// Acknowledge an in-app purchase.
Expand All @@ -261,20 +269,20 @@ class BillingClient {
/// Please refer to [acknowledge](https://developer.android.com/google/play/billing/billing_library_overview#acknowledge) for more
/// details.
///
/// The `purchaseToken` must not be null.
/// The `developerPayload` is the developer data associated with the purchase to be consumed, it defaults to null.
///
/// This wraps [`BillingClient#acknowledgePurchase(String, AcknowledgePurchaseResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#acknowledgePurchase(com.android.billingclient.api.AcknowledgePurchaseParams,%20com.android.billingclient.api.AcknowledgePurchaseResponseListener))
Future<BillingResultWrapper> acknowledgePurchase(String purchaseToken,
{String developerPayload}) async {
{String? developerPayload}) async {
assert(purchaseToken != null);
return BillingResultWrapper.fromJson(await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
<String, String>{
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
}));
return BillingResultWrapper.fromJson((await channel.invokeMapMethod<String,
dynamic>(
'BillingClient#(AcknowledgePurchaseParams params, (AcknowledgePurchaseParams, AcknowledgePurchaseResponseListener)',
<String, dynamic>{
'purchaseToken': purchaseToken,
'developerPayload': developerPayload,
})) ??
<String, dynamic>{});
}

/// The method call handler for [channel].
Expand All @@ -283,15 +291,15 @@ class BillingClient {
switch (call.method) {
case kOnPurchasesUpdated:
// The purchases updated listener is a singleton.
assert(_callbacks[kOnPurchasesUpdated].length == 1);
assert(_callbacks[kOnPurchasesUpdated]!.length == 1);
final PurchasesUpdatedListener listener =
_callbacks[kOnPurchasesUpdated].first;
_callbacks[kOnPurchasesUpdated]!.first as PurchasesUpdatedListener;
listener(PurchasesResultWrapper.fromJson(
call.arguments.cast<String, dynamic>()));
break;
case _kOnBillingServiceDisconnected:
final int handle = call.arguments['handle'];
await _callbacks[_kOnBillingServiceDisconnected][handle]();
await _callbacks[_kOnBillingServiceDisconnected]![handle]();
break;
}
}
Expand Down
Loading

0 comments on commit 0638189

Please sign in to comment.