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

[in_app_purchase] Migrate to NNBD #3555

Merged
merged 14 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 5 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
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.
stuartmorgan marked this conversation as resolved.
Show resolved Hide resolved

## 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is deprecated, so removing

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this use the new replacement instead of just removing the argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general testing flow actually doesn't need to include simulateAskToBuy. "Ask to buy" is only used to test the parental control popup, which prevents the general payment flow to be tested.
@LHLL is working on a patch to add a button to test "ask to buy" flow specifically and we will land it post NNBD

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 <flutter-dev@googlegroups.com>
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 <3.0.0"
flutter: ">=1.9.1+hotfix.2"
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright 2019, the Chromium project authors. Please see the AUTHORS file
// 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.10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you're using 2.10, rather than the 2.9 that's suggested in the unsound NNBD docs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the for details about it, but I remember @blasten mentioned that we should use 2.10 now.
@blasten Let me know if I remembered wrong :)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used 2.9 in the rest of the plugins, so I'm not sure if I suggested 2.10

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha thanks! I was probably dreaming then. Will fix.


import 'package:flutter_test/flutter_test.dart';
import 'package:in_app_purchase/in_app_purchase.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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for flutter/flutter#69225

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