diff --git a/packages/flutter/lib/src/foundation/licenses.dart b/packages/flutter/lib/src/foundation/licenses.dart index 6b794f754d..47c072ec47 100644 --- a/packages/flutter/lib/src/foundation/licenses.dart +++ b/packages/flutter/lib/src/foundation/licenses.dart @@ -2,6 +2,8 @@ // 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:meta/meta.dart' show visibleForTesting; /// Signature for callbacks passed to [LicenseRegistry.addLicense]. @@ -70,8 +72,8 @@ enum _LicenseEntryWithLineBreaksParserState { /// /// ```dart /// void initMyLibrary() { -/// LicenseRegistry.addLicense(() async* { -/// yield const LicenseEntryWithLineBreaks(['my_library'], ''' +/// LicenseRegistry.addLicense(() => Stream.value( +/// const LicenseEntryWithLineBreaks(['my_library'], ''' /// Copyright 2016 The Sample Authors. All rights reserved. /// /// Redistribution and use in source and binary forms, with or without @@ -98,8 +100,9 @@ enum _LicenseEntryWithLineBreaksParserState { /// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY /// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT /// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); -/// }); +/// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', +/// ), +/// )); /// } /// ``` /// {@end-tool} @@ -309,14 +312,19 @@ class LicenseRegistry { /// Returns the licenses that have been registered. /// /// Generating the list of licenses is expensive. - // TODO(dnfield): Refactor the license logic. - // https://github.com/flutter/flutter/issues/95043 - // flutter_ignore: no_sync_async_star - static Stream get licenses async* { + static Stream get licenses { if (_collectors == null) - return; - for (final LicenseEntryCollector collector in _collectors!) - yield* collector(); + return const Stream.empty(); + + late final StreamController controller; + controller = StreamController( + onListen: () async { + for (final LicenseEntryCollector collector in _collectors!) + await controller.addStream(collector()); + await controller.close(); + }, + ); + return controller.stream; } /// Resets the internal state of [LicenseRegistry]. Intended for use in diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index a56117a399..da2c25114f 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -144,45 +145,29 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { LicenseRegistry.addLicense(_addLicenses); } - // TODO(dnfield): Refactor the license logic. - // https://github.com/flutter/flutter/issues/95043 - // flutter_ignore: no_sync_async_star - Stream _addLicenses() async* { - // Using _something_ here to break - // this into two parts is important because isolates take a while to copy - // data at the moment, and if we receive the data in the same event loop - // iteration as we send the data to the next isolate, we are definitely - // going to miss frames. Another solution would be to have the work all - // happen in one isolate, and we may go there eventually, but first we are - // going to see if isolate communication can be made cheaper. - // See: https://github.com/dart-lang/sdk/issues/31959 - // https://github.com/dart-lang/sdk/issues/31960 - // TODO(ianh): Remove this complexity once these bugs are fixed. - final Completer rawLicenses = Completer(); - scheduleTask(() async { - rawLicenses.complete( - kIsWeb - // NOTICES for web isn't compressed since we don't have access to - // dart:io on the client side and it's already compressed between - // the server and client. - ? rootBundle.loadString('NOTICES', cache: false) - : () async { - // The compressed version doesn't have a more common .gz extension - // because gradle for Android non-transparently manipulates .gz files. - final ByteData licenseBytes = await rootBundle.load('NOTICES.Z'); - List bytes = licenseBytes.buffer.asUint8List(); - bytes = gzip.decode(bytes); - return utf8.decode(bytes); - }(), - ); - }, Priority.animation); - await rawLicenses.future; - final Completer> parsedLicenses = Completer>(); - scheduleTask(() async { - parsedLicenses.complete(compute>(_parseLicenses, await rawLicenses.future, debugLabel: 'parseLicenses')); - }, Priority.animation); - await parsedLicenses.future; - yield* Stream.fromIterable(await parsedLicenses.future); + Stream _addLicenses() { + late final StreamController controller; + controller = StreamController( + onListen: () async { + late final String rawLicenses; + if (kIsWeb) { + // NOTICES for web isn't compressed since we don't have access to + // dart:io on the client side and it's already compressed between + // the server and client. + rawLicenses = await rootBundle.loadString('NOTICES', cache: false); + } else { + // The compressed version doesn't have a more common .gz extension + // because gradle for Android non-transparently manipulates .gz files. + final ByteData licenseBytes = await rootBundle.load('NOTICES.Z'); + final List unzippedBytes = await compute, List>(gzip.decode, licenseBytes.buffer.asUint8List(), debugLabel: 'decompressLicenses'); + rawLicenses = await compute, String>(utf8.decode, unzippedBytes, debugLabel: 'utf8DecodeLicenses'); + } + final List licenses = await compute>(_parseLicenses, rawLicenses, debugLabel: 'parseLicenses'); + licenses.forEach(controller.add); + await controller.close(); + }, + ); + return controller.stream; } // This is run in another isolate created by _addLicenses above.