Skip to content
This repository has been 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

Conversation

cyanglaz
Copy link
Contributor

@cyanglaz cyanglaz commented Apr 22, 2021

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/flutter#78525

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides. (Note that unlike the flutter/flutter repo, the flutter/plugins repo does use dart format. See plugin_tool format)
  • I signed the CLA.
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy.
  • I updated CHANGELOG.md to add a description of the change.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

/// (using `extends`) ensures that the subclass will get the default implementation, while
/// platform implementations that `implements` this interface will be broken by newly added
/// [InAppPurchasePlatformAddition] methods.
abstract class InAppPurchasePlatformAddition extends PlatformInterface {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@stuartmorgan

I'm making the InAppPurchasePlatformAddition also a PlatformInterface.
We didn't talk about this offline but I think this makes sense. Please share your thoughts.

Copy link
Contributor

Choose a reason for hiding this comment

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

What's the advantage of doing this? It is not at all clear to me that if we added new methods to PlatformInterface they would be applicable to these addition classes.

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 mainly to take advantage of using the token verification of the PlatformInterface.
I couldn't think of a scenario where we need to add some methods in the "PlatformAddition" that will not be applicable to all addition classes.

Also, Now thinking about the usage of other "PlatformInterface"s, I'm not confident with InAppPurchasePlatformAddition being a "PlatformInterface". Maybe for this plugin i'll just duplicate the token verification code instead of extending PlatformInterface.

WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't the verifyToken meant to enforce users extend the class instead of implementing it?

Is this a concern for the InAppPurchasePlatformAddition class?

I don't think we will need to be afraid of the InAppPurchasePlatformAddition class to have any methods. Meaning I don't expect any methods to be added in the future that would break implementing classes.

Copy link
Contributor Author

@cyanglaz cyanglaz Apr 23, 2021

Choose a reason for hiding this comment

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

We do need some sort of enforcement to make sure the addition is not a type of InAppPurchasePlatform.

Make sure an addition class to extend InAppPurchasePlatformAddition is one of the solutions.

Copy link
Contributor

Choose a reason for hiding this comment

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

We do need some sort of enforcement to make sure the addition is not a type of InAppPurchasePlatform.

Why do we need to enforce that? The concern is purely around pubspec dependency cycles with endorsement IIRC, in which case it's the pubspec cycle, not the classes, that are the issue. This seems like a very indirect way of trying to enforce a lack of cycles (and is both not sufficient to prevent a cycle, and not necessary in the case of an in-package implementation if this is something we adopt as a general solution).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the addition is a type of InAppPurchasePlatform, the plugin user would easily make a mistake to access the InAppPurchasePlatform object (e.g. InAppPurchasePlatformAndroid) and call the unified API there.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I'm not following. Can you give a snippet of what that mistake would look like in code?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's say we allow an InAppPurchasePlatform to also be a type of InAppPurchasePlatformAddition, the implementer could potentially create a class like this:

class InAppPurchasePlatformGooglePlay extends InAppPurchasePlatform implements InAppPurchasePlatformAddition {
   
    @override
    Future<void> buyConsumable(...){...}

    Future<void> someAdditionMethod(...){...}
}

Then the user could potentially use this class to do something like:

InAppPurchasePlatformGooglePlay googlePlayAddition = InAppPurchaseConnection.instance.getPlatformAddition<InAppPurchasePlatformGooglePlay>();
googlePlayAddition.buyConsumable(...);

In this case, the user is accessing the platform interface API directly, which we want to avoid.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need to go out of our way to forbid that construction. It's a bad idea, but only in the same way that, say, making a bunch of implementation details of a InAppPurchaseGoogleplayAdditions public instead of private would be. I.e., it's the implementor of the addition class designing their own extension API badly.

What I wanted to avoid was the design where we forced extensions to use that, so that every extension would be required to have that poor design. If we make it so that it's easy to do the right thing, and model the right thing in our plugins, that should be enough.

Copy link
Contributor

@mvanbeusekom mvanbeusekom left a comment

Choose a reason for hiding this comment

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

LGTM, only thing I am thinking about is adding another test to verify that accessing instance throws a LateInitializationError when accessing it before it is initialized.

@cyanglaz
Copy link
Contributor Author

LateInitializationError

Sounds good, weird tho the code wasn't able to find LateInitializationError

@mvanbeusekom
Copy link
Contributor

LateInitializationError

Sounds good, weird tho the code wasn't able to find LateInitializationError

I tested it on dartpad.dev since I wasn't sure what would happen ;)

@cyanglaz
Copy link
Contributor Author

@mvanbeusekom Seems like the class LateInitializationError isn't accessible. I think it's ok to not test a default NNBD behavior.

@@ -24,15 +24,16 @@ abstract class InAppPurchasePlatform extends PlatformInterface {
/// The instance of [InAppPurchasePlatform] to use.
///
/// Defaults to `null`.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is no longer correct. Instead, you should document that it must be set before using.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


static InAppPurchasePlatform? _instance;
// Should only be accessed after setter is called.
static late InAppPurchasePlatform _instance;
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be either above or below the getter and setter, rather than between them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// ignore: avoid_classes_with_only_static_members
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

/// The interface that platform implementations must implement when they want to
/// provide platform specific in_app_purchase features.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: platform-specific

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. Also fixed some other appearance in the plugin.

/// (using `extends`) ensures that the subclass will get the default implementation, while
/// platform implementations that `implements` this interface will be broken by newly added
/// [InAppPurchasePlatformAddition] methods.
abstract class InAppPurchasePlatformAddition extends PlatformInterface {
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the advantage of doing this? It is not at all clear to me that if we added new methods to PlatformInterface they would be applicable to these addition classes.

@@ -36,5 +52,13 @@ abstract class InAppPurchasePlatformAddition {
/// }
/// }
/// ```
static InAppPurchasePlatformAddition? instance;
static InAppPurchasePlatformAddition get instance => _instance;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why have you made this non-nullable? Why should a platform with no additions need to set this up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah makes sense! Made this nullable.

Copy link
Contributor Author

@cyanglaz cyanglaz left a comment

Choose a reason for hiding this comment

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

@stuartmorgan Updated per your comments. PTAL :)

@@ -24,15 +24,16 @@ abstract class InAppPurchasePlatform extends PlatformInterface {
/// The instance of [InAppPurchasePlatform] to use.
///
/// Defaults to `null`.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


static InAppPurchasePlatform? _instance;
// Should only be accessed after setter is called.
static late InAppPurchasePlatform _instance;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// ignore: avoid_classes_with_only_static_members
import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';

/// The interface that platform implementations must implement when they want to
/// provide platform specific in_app_purchase features.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done. Also fixed some other appearance in the plugin.

/// (using `extends`) ensures that the subclass will get the default implementation, while
/// platform implementations that `implements` this interface will be broken by newly added
/// [InAppPurchasePlatformAddition] methods.
abstract class InAppPurchasePlatformAddition extends PlatformInterface {
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 mainly to take advantage of using the token verification of the PlatformInterface.
I couldn't think of a scenario where we need to add some methods in the "PlatformAddition" that will not be applicable to all addition classes.

Also, Now thinking about the usage of other "PlatformInterface"s, I'm not confident with InAppPurchasePlatformAddition being a "PlatformInterface". Maybe for this plugin i'll just duplicate the token verification code instead of extending PlatformInterface.

WDYT?

@@ -36,5 +52,13 @@ abstract class InAppPurchasePlatformAddition {
/// }
/// }
/// ```
static InAppPurchasePlatformAddition? instance;
static InAppPurchasePlatformAddition get instance => _instance;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah makes sense! Made this nullable.

Copy link
Contributor Author

@cyanglaz cyanglaz left a comment

Choose a reason for hiding this comment

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

@stuartmorgan Updated with review. comments about not making InAppPurchasePlatformAddition a platform interface.

Copy link
Contributor

@stuartmorgan stuartmorgan left a comment

Choose a reason for hiding this comment

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

LGTM with nits.

/// 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 replace
Copy link
Contributor

Choose a reason for hiding this comment

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

s/replace ... with/set ... to/ (by default there's no instance, so there's nothing to replace)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

/// a class that either extend or implements [InAppPurchasePlatformAddition]. Then replace
/// the [InAppPurchasePlatformAddition.instance] with an instance of that class.
///
/// All the APIs added by [InAppPurchasePlatformAddition] implementers will be accessed from
Copy link
Contributor

Choose a reason for hiding this comment

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

s/implementers/implementations/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

///
/// All the APIs added by [InAppPurchasePlatformAddition] implementers will be accessed from
/// [InAppPurchasePlatformAdditionProvider.getPlatformAddition] by the client APPs.
/// To avoid client APPs to directly call the [InAppPurchasePlatform] APIs, an [InAppPurchasePlatformAddition] implementation must not
Copy link
Contributor

Choose a reason for hiding this comment

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

"To avoid clients directly calling [InAppPurchasePlatform] APIs, an [InAppPurchasePlatformAddition] implementation should not be"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

static InAppPurchasePlatformAddition? instance;
static InAppPurchasePlatformAddition? get instance => _instance;

/// Set the instance to a desired [InAppPurchasePlatformAddition] implementation.
Copy link
Contributor

Choose a reason for hiding this comment

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

Sets

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


/// Set the instance to a desired [InAppPurchasePlatformAddition] implementation.
///
/// The `instance` must not be a type of [InAppPurchasePlatform].
Copy link
Contributor

Choose a reason for hiding this comment

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

s/must/should/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -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's multiple platform-specific reasons that product information could fail to be fetched,
Copy link
Contributor

Choose a reason for hiding this comment

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

s/There's/There are/

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

class MockInAppPurchasePlatformAddition extends Mock
with
// ignore: prefer_mixin
MockPlatformInterfaceMixin
Copy link
Contributor

Choose a reason for hiding this comment

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

This shouldn't still be needed, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor

@mvanbeusekom mvanbeusekom left a comment

Choose a reason for hiding this comment

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

LGTM

@cyanglaz cyanglaz merged commit e11179d into flutter:master May 10, 2021
@cyanglaz cyanglaz deleted the iap_platform_interface branch May 10, 2021 21:32
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 10, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 11, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 12, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 12, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 12, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 19, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 19, 2021
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request May 19, 2021
fotiDim pushed a commit to fotiDim/plugins that referenced this pull request Sep 13, 2021
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/flutter#78525
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
3 participants