Skip to content

Conversation

@adazh
Copy link
Contributor

@adazh adazh commented Aug 2, 2019

Description

Moved the [default BinaryMessenger] instance (

const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._();
) to ServicesBinding.

By this change, we are capable of registering a different default BinaryMessenger under testing environment, by creating a ServicesBinding subclass for testing. With that, we can track # of pending platform messages for synchronization purposes (see the related issue below).

Related Issues

#37409

Tests

  • Unit/Widget tests updated.
  • Manually tested on the example platform_channel app.

Checklist

Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes ([x]). This will ensure a smooth and quick review process.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

Breaking Change

Does your PR require Flutter developers to manually update their apps to accommodate your change?

This is a breaking change:

  • It deprecated the original defaultBinaryMessenger instance. The usage of defaultBinaryMessenger is mostly within the framework, but since it's publicly accessible, so still could possibly impact users.
  • Bindings need to be properly initialized before using any platform channels.

@fluttergithubbot
Copy link
Contributor

It looks like this pull request may not have tests. Please make sure to add tests before merging. While there are exceptions to this rule, if this patch modifies code it is probably not an exception.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@fluttergithubbot fluttergithubbot added a: tests "flutter test", flutter_test, or one of our tests d: examples Sample code and demos framework flutter/packages/flutter repository. See also f: labels. c: contributor-productivity Team-specific productivity, code health, technical debt. labels Aug 2, 2019
@adazh adazh requested review from Hixie, goderbauer and tvolkert August 2, 2019 16:56
@adazh
Copy link
Contributor Author

adazh commented Aug 2, 2019

@Hixie @tvolkert @goderbauer
I haven't done cleanup/fixing tests, but I would like to check in with you guys first to make sure I'm on the right track. PTAL. Thanks!

@adazh adazh changed the title Moved the default BinaryMessenger to the ServicesBinding Moved the default BinaryMessenger instance to ServicesBinding Aug 2, 2019
}

/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger binaryMessenger;
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 final

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to use the _binaryMessenger getter.

/// A default [BinaryMessenger] instance will be used if [binaryMessenger] is
/// null.
PlatformAssetBundle({this.binaryMessenger}) {
binaryMessenger ??= ServicesBinding.instance.provideBinaryMessenger();
Copy link
Contributor

Choose a reason for hiding this comment

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

this can be an initializer, which allows the field to be final.

that said, see later comments for an alternative strategy that would be consistent with what we'll have to do below.

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.

assert(name != null);
assert(codec != null);
binaryMessenger ??= ServicesBinding.instance.provideBinaryMessenger();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

we need to keep this class const.

Easiest way to do this, I think, is to have the default value of the binaryMessenger argument be null, have it set a private field _binaryMessenger, and then have BinaryMessenger get binaryMessenger => _binaryMessenger ?? ServicesBinding.instance.defaultBinaryMessenger, where the latter is a getter that returns a precached value created during initInstances.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks! I'll try this approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated accordingly. PTAL.

BinaryMessenger provideBinaryMessenger() {
return defaultBinaryMessenger;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

I think you want to flip these around. The getter should be the way to access this, to make it clear that we're not initializing a new value each time. The method should be protected, and should be called during initInstances to configure a field that defaultBinaryMessenger returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. I was wrong by treating the binaryMessenger field as private member (which is public accessible), and had this provideBinaryMessenger as the getter for it.

final VoidCallback onCleared;

/// The messenger which sends the bytes for this channel, not null.
BinaryMessenger binaryMessenger;
Copy link
Contributor

Choose a reason for hiding this comment

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

should be final

Copy link
Contributor

Choose a reason for hiding this comment

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

or just use the one from the services binding directly.

in fact here and various other places, i think it's better to do ServicesBinding.instance.defaultBinaryMessenger directly than to use a local copy. Alternatively, use a private _binaryMessenger getter that just returns ServicesBinding.instance.defaultBinaryMessenger. This avoids the local copy.

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 like the latter approach. It's generally a good practice to keep external dependencies obvious. Also, easier for injecting mocks for testing.

/// This is used to send messages from the application to the platform, and
/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._();
Copy link
Contributor

Choose a reason for hiding this comment

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

rather than removing this (a hard breaking change), let's just deprecate it and turn it into a getter than returns ServicesBinding.instance.defaultBinaryMessenger.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a @deprecated annotation and updated all the tests. PTAL.

@adazh adazh added the c: API break Backwards-incompatible API changes label Aug 2, 2019
@adazh
Copy link
Contributor Author

adazh commented Aug 3, 2019

@Hixie This app under test reveals a case that MessageChannel could possibly be called before ServicesBinding is initialized. See the app's main.

It can be fixed by switching the two lines in main:
_kReloadChannel.setMessageHandler(run); run(ui.window.defaultRouteName);.
But would like to double check whether this is a legit assumption that calling MessageChannel should usually happen after runApp.

@Hixie
Copy link
Contributor

Hixie commented Aug 4, 2019

Generally we want people to call the binding initialization logic before they do anything anyway.

@Hixie
Copy link
Contributor

Hixie commented Aug 4, 2019

(which doesn't have to be runApp, you can also do it directly)

@adazh
Copy link
Contributor Author

adazh commented Aug 5, 2019

@Hixie @tvolkert @goderbauer Tests all fixed. PTAL.

@adazh adazh requested a review from Hixie August 6, 2019 18:37
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._();
@Deprecated('Use ServicesBinding.instance.defaultBinaryMessenger instead.')
Copy link
Member

Choose a reason for hiding this comment

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

Can you update the doc comment above as well to reflect that this is deprecated and link to the new thing that should be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated. PTAL.


/// Initializes a default [BinaryMessenger] instance that can be used for
/// sending platform messages.
@protected
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason this can't be private? Do we expect subclasses to override it to return a different binary messenger than the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya, exactly. We'll want to set a different binary messenger for the ServicesBinding used in tests.

/// This is used to send messages from the application to the platform, and
/// keeps track of which handlers have been registered on each channel so
/// it may dispatch incoming messages to the registered handler.
BinaryMessenger defaultBinaryMessenger;
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 public and mutable. We should probably make it private with a public getter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm... seems that won't work, since we'll need to set it in subclasses.
Thus, the setter has to be visible in its subclasses, otherwise we don't have a way to modify it in a subclass.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then what about changing the signature of initBinaryMessenger() to return the messenger rather than setting it? then the instance variable can be private (with a public getter), and the base class' initInstances() method can set the instance variable to the value it gets back from initBinaryMessenger().

Along with this change, maybe initBinaryMessenger() should be renamed createBinaryMessenger() or something similar?

Copy link
Contributor Author

@adazh adazh Aug 8, 2019

Choose a reason for hiding this comment

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

I was dumb... Ya, that would totally work. Updated defaultBinaryMessenger to be a private field. PTAL.

@adazh adazh requested a review from tvolkert August 8, 2019 22:37
@adazh adazh merged commit 92ef2b9 into flutter:master Aug 9, 2019
@adazh adazh deleted the binary_messenger branch August 9, 2019 17:14
@adazh
Copy link
Contributor Author

adazh commented Aug 9, 2019

Included this breaking change in the Changelog.

@amirh
Copy link
Contributor

amirh commented Aug 9, 2019

This forces plugins to choose whether to work with master or stable (sample breakage.

Can we land this in phases instead?

@amirh
Copy link
Contributor

amirh commented Aug 9, 2019

Offline comment by ada - we can call TestWidgetsFlutterBinding.ensureInitialized(); both on stable and master which will fix the breakage.

@tvolkert
Copy link
Contributor

tvolkert commented Aug 9, 2019

@amirh does adding TestWidgetsFlutterBinding.ensureInitialized(); to the tests not fix the problem in both master and stable? Also, this should only affect tests, not actual production code.

@mklim
Copy link
Contributor

mklim commented Aug 9, 2019

Discussion offline. This requires either runApp() or WidgetsFlutterBinding.ensureInitialized() to have been called before using any MethodChannels. Plugins broadly rely on MethodChannels and right now there's no general requirements that they be constructed within the runApp call, so the ecosystem breakages from this are broader than originally thought. Reverting this for now.

mklim pushed a commit that referenced this pull request Aug 9, 2019
…#37489)" (#37983)

This reverts commit 92ef2b9.

This requires either runApp() or
WidgetsFlutterBinding.ensureInitialized() to have been called before
using any MethodChannels. Plugins broadly rely on MethodChannels and
right now there's no general requirements that they be constructed
within the runApp call, so the ecosystem breakages from this are broader
than originally thought. Reverting for now.
@tvolkert
Copy link
Contributor

For when this is re-applied: it also broke the image_list_jit_reported_duration and the image_list_reported_duration devicelab tests.

@adazh
Copy link
Contributor Author

adazh commented Aug 12, 2019

@tvolkert Is there a way I can run these two tests before check-in?

@goderbauer
Copy link
Member

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

a: tests "flutter test", flutter_test, or one of our tests c: API break Backwards-incompatible API changes c: contributor-productivity Team-specific productivity, code health, technical debt. customer: espresso d: examples Sample code and demos framework flutter/packages/flutter repository. See also f: labels. t: flutter driver "flutter driver", flutter_drive, or a driver test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants