This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[in_app_purchase_android] Implement BillingClient connection management and introduce BillingClientManager
#6309
Closed
SynSzakala
wants to merge
12
commits into
flutter:main
from
Utopia-USS:fix_in_app_purchase_android_billing_client_reconnect
Closed
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
4a89c13
[in_app_purchases_android_platform] Add BillingClient reconnecting logic
SynSzakala c12ca18
[in_app_purchases_android_platform] Extract BillingClientManager
SynSzakala 45f2e1c
Merge branch 'main' into fix_in_app_purchase_android_billing_client_r…
SynSzakala 9f921df
[in_app_purchases_android] Update changelog
SynSzakala 732cda1
[in_app_purchases_android] Update pubspec.yaml
SynSzakala 39e5ccf
Merge branch 'main' into fix_in_app_purchase_android_billing_client_r…
SynSzakala 213ec8a
[in_app_purchases_android_platform] Enhance documentation, move HasBi…
SynSzakala 1b74c6a
Merge branch 'main' into fix_in_app_purchase_android_billing_client_r…
SynSzakala a2f3dc4
Merge branch 'main' into fix_in_app_purchase_android_billing_client_r…
SynSzakala 48ddee7
[image_picker_android] Fix analyzer warnings, remove unnecessary chan…
SynSzakala 6bc41e0
Merge remote-tracking branch 'origin/main' into fix_in_app_purchase_a…
SynSzakala 876530f
Merge branch 'main' into fix_in_app_purchase_android_billing_client_r…
SynSzakala File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
5 changes: 5 additions & 0 deletions
5
packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
156 changes: 156 additions & 0 deletions
156
...chase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| // Copyright 2013 The Flutter Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'dart:async'; | ||
|
|
||
| import 'package:flutter/widgets.dart'; | ||
|
|
||
| import 'billing_client_wrapper.dart'; | ||
| import 'purchase_wrapper.dart'; | ||
|
|
||
| /// Abstraction of result of [BillingClient] operation that includes | ||
| /// a [BillingResponse]. | ||
| abstract class HasBillingResponse { | ||
| /// The status of the operation. | ||
| abstract final BillingResponse responseCode; | ||
| } | ||
|
|
||
| /// Utility class that manages a [BillingClient] connection. | ||
| /// | ||
| /// Connection is initialized on creation of [BillingClientManager]. | ||
| /// If [BillingClient] sends `onBillingServiceDisconnected` event or any | ||
| /// operation returns [BillingResponse.serviceDisconnected], connection is | ||
| /// re-initialized. | ||
| /// | ||
| /// [BillingClient] instance is not exposed directly. It can be accessed via | ||
| /// [run] and [runRaw] methods that handle the connection management. | ||
| /// | ||
| /// Consider calling [dispose] after the [BillingClient] is no longer needed. | ||
| class BillingClientManager { | ||
| /// Creates the [BillingClientManager]. | ||
| /// | ||
| /// Immediately initializes connection to the underlying [BillingClient]. | ||
| BillingClientManager() { | ||
| _connect(); | ||
| } | ||
|
|
||
| /// Stream of `onPurchasesUpdated` events from the [BillingClient]. | ||
| /// | ||
| /// This is a broadcast stream, so it can be listened to multiple times. | ||
| /// A "done" event will be sent after [dispose] is called. | ||
| late final Stream<PurchasesResultWrapper> purchasesUpdatedStream = | ||
| _purchasesUpdatedController.stream; | ||
|
|
||
| /// [BillingClient] instance managed by this [BillingClientManager]. | ||
| /// | ||
| /// In order to access the [BillingClient], consider using [run] and [runRaw] | ||
| /// methods. | ||
| @visibleForTesting | ||
| late final BillingClient client = BillingClient(_onPurchasesUpdated); | ||
|
|
||
| final StreamController<PurchasesResultWrapper> _purchasesUpdatedController = | ||
| StreamController<PurchasesResultWrapper>.broadcast(); | ||
|
|
||
| bool _isConnecting = false; | ||
| bool _isDisposed = false; | ||
|
|
||
| // Initialized immediately in the constructor, so it's always safe to access. | ||
| late Future<void> _readyFuture; | ||
|
|
||
| /// Executes the given [block] with access to the underlying [BillingClient]. | ||
| /// | ||
| /// If necessary, waits for the underlying [BillingClient] to connect. | ||
| /// If given [block] returns [BillingResponse.serviceDisconnected], it will | ||
| /// be transparently retried after the connection is restored. Because | ||
| /// of this, [block] may be called multiple times. | ||
| /// | ||
| /// A response with [BillingResponse.serviceDisconnected] may be returned | ||
GaryQian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// in case of [dispose] being called during the operation. | ||
| /// | ||
| /// See [runRaw] for operations that do not return a subclass | ||
| /// of [HasBillingResponse]. | ||
| Future<R> run<R extends HasBillingResponse>( | ||
| Future<R> Function(BillingClient client) block, | ||
| ) async { | ||
| assert(_debugAssertNotDisposed()); | ||
| await _readyFuture; | ||
| final R result = await block(client); | ||
| if (result.responseCode == BillingResponse.serviceDisconnected && | ||
| !_isDisposed) { | ||
| await _connect(); | ||
| return run(block); | ||
| } else { | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| /// Executes the given [block] with access to the underlying [BillingClient]. | ||
| /// | ||
| /// If necessary, waits for the underlying [BillingClient] to connect. | ||
| /// Designed only for operations that do not return a subclass | ||
| /// of [HasBillingResponse] (e.g. [BillingClient.isReady], | ||
| /// [BillingClient.isFeatureSupported]). | ||
| /// | ||
| /// See [runRaw] for operations that return a subclass | ||
| /// of [HasBillingResponse]. | ||
| Future<R> runRaw<R>(Future<R> Function(BillingClient client) block) async { | ||
| assert(_debugAssertNotDisposed()); | ||
| await _readyFuture; | ||
| return block(client); | ||
| } | ||
|
|
||
| /// Ends connection to the [BillingClient]. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Document when/under what conditions dispose should be called. |
||
| /// | ||
| /// Consider calling [dispose] after you no longer need the [BillingClient] | ||
| /// API to free up the resources. | ||
| /// | ||
| /// After calling [dispose] : | ||
| /// - Further connection attempts will not be made; | ||
| /// - [purchasesUpdatedStream] will be closed; | ||
| /// - Calls to [run] and [runRaw] will throw. | ||
| void dispose() { | ||
| assert(_debugAssertNotDisposed()); | ||
| _isDisposed = true; | ||
| client.endConnection(); | ||
| _purchasesUpdatedController.close(); | ||
| } | ||
|
|
||
| // If disposed, does nothing. | ||
| // If currently connecting, waits for it to complete. | ||
| // Otherwise, starts a new connection. | ||
| Future<void> _connect() { | ||
| if (_isDisposed) { | ||
| return Future<void>.value(); | ||
| } | ||
| if (_isConnecting) { | ||
| return _readyFuture; | ||
| } | ||
| _isConnecting = true; | ||
| _readyFuture = Future<void>.sync(() async { | ||
| await client.startConnection(onBillingServiceDisconnected: _connect); | ||
| _isConnecting = false; | ||
| }); | ||
| return _readyFuture; | ||
| } | ||
|
|
||
| void _onPurchasesUpdated(PurchasesResultWrapper event) { | ||
| if (_isDisposed) { | ||
| return; | ||
| } | ||
| _purchasesUpdatedController.add(event); | ||
| } | ||
|
|
||
| bool _debugAssertNotDisposed() { | ||
| assert(() { | ||
| if (_isDisposed) { | ||
| throw FlutterError( | ||
| 'A BillingClientManager was used after being disposed.\n' | ||
| 'Once you have called dispose() on a BillingClientManager, it can no longer be used.', | ||
| ); | ||
| } | ||
| return true; | ||
| }()); | ||
| return true; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should document in BillingClient the drawbacks of using the BillingClient class directly and point them to the newly recommended way of handling operations using this new BillingClientManager class.