diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 6dee7b7af60..d21224ce39b 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -166,6 +166,11 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C 'Generated the widget preview environment scaffolding at a given location ' 'for testing purposes.', hide: !verbose, + ) + ..addFlag( + kDisableDtdServiceUuid, + help: 'Disables the addition of a UUID to the widget preview DTD service and stream.', + hide: !verbose, ); } @@ -175,9 +180,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C static const kHeadless = 'headless'; static const kWebServer = 'web-server'; static const kWidgetPreviewScaffoldOutputDir = 'scaffold-output-dir'; - - /// Environment variable used to pass the DTD URI to the widget preview scaffold. - static const kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI'; + static const kDisableDtdServiceUuid = 'disable-dtd-service-uuid'; @visibleForTesting static const kBrowserNotFoundErrorMessage = @@ -258,6 +261,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C onHotRestartPreviewerRequest: onHotRestartRequest, dtdLauncher: DtdLauncher(logger: logger, artifacts: artifacts, processManager: processManager), project: rootProject.widgetPreviewScaffoldProject, + addUuidToServiceName: !boolArg(kDisableDtdServiceUuid), ); /// The currently running instance of the widget preview scaffold. @@ -401,7 +405,11 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C logger.printTrace('Connecting to existing DTD instance at: $existingDtdUri...'); await _dtdService.connect(dtdWsUri: existingDtdUri); } - _previewCodeGenerator.populateDtdConnectionInfo(_dtdService.dtdUri!); + _previewCodeGenerator.populateDtdConnectionInfo( + dtdUri: _dtdService.dtdUri!, + widgetPreviewServiceName: _dtdService.widgetPreviewService, + widgetPreviewScaffoldStreamName: _dtdService.widgetPreviewScaffoldStream, + ); } Future runPreviewEnvironment({required FlutterProject widgetPreviewScaffoldProject}) async { diff --git a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart b/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart index 83d417f13db..c22c0f70e85 100644 --- a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart +++ b/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart @@ -10,6 +10,7 @@ import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; import 'package:process/process.dart'; +import 'package:uuid/uuid.dart'; import '../artifacts.dart'; import '../base/common.dart'; @@ -37,6 +38,7 @@ class WidgetPreviewDtdServices { required this.dtdLauncher, required this.onHotRestartPreviewerRequest, required this.project, + required this.addUuidToServiceName, }) { shutdownHooks.addShutdownHook(() async { await _dtd?.close(); @@ -44,12 +46,35 @@ class WidgetPreviewDtdServices { }); } + /// The name of the widget preview service, without a UUID. + @visibleForTesting + static const kWidgetPreviewServiceRoot = 'widget-preview'; + + /// The actual name of the registered widget preview service. + late final String widgetPreviewService = _withUuid(kWidgetPreviewServiceRoot); + + /// The name of the widget preview stream, without a UUID. + @visibleForTesting + static const kWidgetPreviewScaffoldStreamRoot = 'WidgetPreviewScaffold'; + + /// The actual name of the widget preview stream. + late final String widgetPreviewScaffoldStream = _withUuid(kWidgetPreviewScaffoldStreamRoot); + + /// The unique identifier added to registered service and stream names if [addUuidToServiceName] + /// is true. + late final String serviceUuid = const Uuid().v4(); + + /// Adds a unique identifier to the service and stream registered by the widget previewer to + /// avoid conflicts with other widget previewer instances connected to DTD. + /// + /// If false, no UUID is added to the registered service and stream names. + final bool addUuidToServiceName; + // WARNING: Keep these constants and services in sync with those defined in the widget preview // scaffold's dtd_services.dart. // // START KEEP SYNCED - static const kWidgetPreviewService = 'widget-preview'; static const kIsWindows = 'isWindows'; static const kHotRestartPreviewer = 'hotRestartPreviewer'; static const kResolveUri = 'resolveUri'; @@ -57,7 +82,6 @@ class WidgetPreviewDtdServices { static const kGetPreference = 'getPreference'; static const kGetDevToolsUri = 'getDevToolsUri'; - static const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; static const kWidgetPreviewConnectedEvent = 'Connected'; /// Error code for RpcException thrown when attempting to load a key from @@ -144,21 +168,20 @@ class WidgetPreviewDtdServices { ); } + String _withUuid(String name) => addUuidToServiceName ? '$name-$serviceUuid' : name; + Future _registerServices() async { final DartToolingDaemon dtd = _dtd!; - dtd.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) { - if (event case DTDEvent( - stream: kWidgetPreviewScaffoldStream, - kind: kWidgetPreviewConnectedEvent, - )) { + dtd.onEvent(widgetPreviewScaffoldStream).listen((DTDEvent event) { + if (event.kind == kWidgetPreviewConnectedEvent) { previewAnalytics.reportPreviewerConnected(); } }); await Future.wait(>[ - dtd.streamListen(kWidgetPreviewScaffoldStream), + dtd.streamListen(widgetPreviewScaffoldStream), for (final (String method, DTDServiceCallback callback) in services) dtd - .registerService(kWidgetPreviewService, method, callback) + .registerService(widgetPreviewService, method, callback) .then((_) => logger.printTrace('Registered DTD method: $method')), ]); } diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart index 84b2b1d6c30..2a0db21d2a5 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart @@ -55,7 +55,11 @@ class PreviewCodeGenerator { static String getGeneratedDtdConnectionInfoFilePath(FileSystem fs) => fs.path.join('lib', 'src', 'dtd', 'dtd_connection_info.dart'); - void populateDtdConnectionInfo(Uri dtdUri) { + void populateDtdConnectionInfo({ + required Uri dtdUri, + required String widgetPreviewServiceName, + required String widgetPreviewScaffoldStreamName, + }) { final emitter = cb.DartEmitter.scoped(useNullSafetySyntax: true); final lib = cb.Library( (cb.LibraryBuilder b) => b @@ -68,6 +72,20 @@ class PreviewCodeGenerator { ..type = cb.refer('String') ..assignment = cb.literalString(dtdUri.toString()).code; }), + cb.Field((b) { + b + ..name = 'kWidgetPreviewService' + ..modifier = cb.FieldModifier.constant + ..type = cb.refer('String') + ..assignment = cb.literalString(widgetPreviewServiceName).code; + }), + cb.Field((b) { + b + ..name = 'kWidgetPreviewScaffoldStream' + ..modifier = cb.FieldModifier.constant + ..type = cb.refer('String') + ..assignment = cb.literalString(widgetPreviewScaffoldStreamName).code; + }), ]), ); final File generatedDtdConnectionInfoFile = fs.file( diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl index 092d71d51a4..def881bd73a 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart.tmpl @@ -3,3 +3,5 @@ // found in the LICENSE file. const String kWidgetPreviewDtdUri = ''; +const String kWidgetPreviewService = 'widget-preview'; +const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl index 82d5a824dee..37551f5ff3f 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl @@ -17,7 +17,6 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { // // START KEEP SYNCED - static const kWidgetPreviewService = 'widget-preview'; static const kIsWindows = 'isWindows'; static const kHotRestartPreviewer = 'hotRestartPreviewer'; static const kResolveUri = 'resolveUri'; @@ -40,7 +39,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { dtd = await DartToolingDaemon.connect(dtdWsUri); unawaited( dtd.postEvent( - 'WidgetPreviewScaffold', + kWidgetPreviewScaffoldStream, 'Connected', const {}, ), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart index c379fe3f147..cda8927e874 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart @@ -418,13 +418,19 @@ List<_i1.WidgetPreview> previews() => []; // Populate the DTD connection info. final Uri dtdUri = Uri.parse('ws://localhost:1234'); - codeGenerator.populateDtdConnectionInfo(dtdUri); + codeGenerator.populateDtdConnectionInfo( + dtdUri: dtdUri, + widgetPreviewServiceName: 'widget-preview-service', + widgetPreviewScaffoldStreamName: 'widget-preview-stream', + ); final expectedDtdConnectionInfo = ''' // ignore_for_file: implementation_imports const String kWidgetPreviewDtdUri = '$dtdUri'; +const String kWidgetPreviewService = 'widget-preview-service'; +const String kWidgetPreviewScaffoldStream = 'widget-preview-stream'; '''; expect(generatedDtdConnectionInfoFile.readAsStringSync(), expectedDtdConnectionInfo); }, diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart index 33289a8c8eb..4d75a6df3a9 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart @@ -45,6 +45,13 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake implements WidgetPreview @override Uri? get dtdUri => Uri(); + @override + final String widgetPreviewService = WidgetPreviewDtdServices.kWidgetPreviewServiceRoot; + + @override + final String widgetPreviewScaffoldStream = + WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot; + @override Future launchAndConnect() async {} } diff --git a/packages/flutter_tools/test/integration.shard/widget_preview_test.dart b/packages/flutter_tools/test/integration.shard/widget_preview_test.dart index 0dafb71350f..f1e4ec01f65 100644 --- a/packages/flutter_tools/test/integration.shard/widget_preview_test.dart +++ b/packages/flutter_tools/test/integration.shard/widget_preview_test.dart @@ -79,6 +79,7 @@ void main() { 'start', '--verbose', '--${WidgetPreviewStartCommand.kHeadless}', + '--${WidgetPreviewStartCommand.kDisableDtdServiceUuid}', if (useWebServer) '--${WidgetPreviewStartCommand.kWebServer}', if (dtdUri != null) '--${WidgetPreviewStartCommand.kDtdUrl}=$dtdUri', if (devToolsServerAddress != null) @@ -192,14 +193,15 @@ void main() { // The preview scaffold will send a 'Connected' event on this stream once it has initialized // and is ready. final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri); - const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; final completer = Completer(); - dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) { - expect(event.stream, kWidgetPreviewScaffoldStream); + dtdConnection.onEvent(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot).listen(( + DTDEvent event, + ) { + expect(event.stream, WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot); expect(event.kind, 'Connected'); completer.complete(); }); - await dtdConnection.streamListen(kWidgetPreviewScaffoldStream); + await dtdConnection.streamListen(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot); // Start the widget preview and wait for the 'Connected' event. await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, dtdUri: dtdUri); @@ -242,17 +244,18 @@ void main() { // The preview scaffold will send a 'Connected' event on this stream once it has initialized // and is ready. final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri); - const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; final completer = Completer(); var firstConnection = true; - dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) { - expect(event.stream, kWidgetPreviewScaffoldStream); + dtdConnection.onEvent(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot).listen(( + DTDEvent event, + ) { + expect(event.stream, WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot); expect(event.kind, 'Connected'); if (firstConnection) { firstConnection = false; runFlutterClean(); dtdConnection.call( - WidgetPreviewDtdServices.kWidgetPreviewService, + WidgetPreviewDtdServices.kWidgetPreviewServiceRoot, WidgetPreviewDtdServices.kHotRestartPreviewer, ); return; @@ -262,7 +265,7 @@ void main() { // crashed. completer.complete(); }); - await dtdConnection.streamListen(kWidgetPreviewScaffoldStream); + await dtdConnection.streamListen(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot); // Start the widget preview and wait for the 'Connected' event. await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, dtdUri: dtdUri); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart index 092d71d51a4..def881bd73a 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_connection_info.dart @@ -3,3 +3,5 @@ // found in the LICENSE file. const String kWidgetPreviewDtdUri = ''; +const String kWidgetPreviewService = 'widget-preview'; +const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold'; diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart index 82d5a824dee..37551f5ff3f 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart @@ -17,7 +17,6 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { // // START KEEP SYNCED - static const kWidgetPreviewService = 'widget-preview'; static const kIsWindows = 'isWindows'; static const kHotRestartPreviewer = 'hotRestartPreviewer'; static const kResolveUri = 'resolveUri'; @@ -40,7 +39,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { dtd = await DartToolingDaemon.connect(dtdWsUri); unawaited( dtd.postEvent( - 'WidgetPreviewScaffold', + kWidgetPreviewScaffoldStream, 'Connected', const {}, ), diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart index c3286275df0..7f9c3ca907e 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart @@ -62,6 +62,7 @@ void main() { ), onHotRestartPreviewerRequest: onHotRestartPreviewerRequest ?? () {}, project: FakeFlutterProject(), + addUuidToServiceName: false, ); await server.launchAndConnect(); return server;