diff --git a/lib/service_manager.dart b/lib/service_manager.dart index 52ea35bd66a..c5099e29016 100644 --- a/lib/service_manager.dart +++ b/lib/service_manager.dart @@ -29,6 +29,7 @@ class ServiceConnectionManager { new StreamController.broadcast(); final StreamController _connectionClosedController = new StreamController.broadcast(); + final Map> methodsForService = {}; IsolateManager _isolateManager; ServiceExtensionManager _serviceExtensionManager; @@ -51,6 +52,33 @@ class ServiceConnectionManager { Stream get onConnectionClosed => _connectionClosedController.stream; + /// Call a service that is registered by exactly one client. + Future callService(String name, {String isolateId, Map args}) async { + final registered = methodsForService[name] ?? const []; + if (registered.length != 1) { + throw Exception('Expected one registered service for "$name" but found ' + '${registered.length}'); + } + return service.callMethod(registered.first, + isolateId: isolateId, args: args); + } + + /// Call a service that may have been registered by multiple clients. + /// + /// For example, a service to navigate a code editor to a specific line and + /// column might be registered by multiple code editors. + Future> callMulticastService(String name, + {String isolateId, Map args}) async { + final registered = methodsForService[name] ?? const []; + if (registered.isNotEmpty) { + return Future.wait(registered.map((String method) { + return service.callMethod(method, isolateId: isolateId, args: args); + })); + } else { + throw Exception('There are no registered methods for service "$name"'); + } + } + Future vmServiceOpened( VmServiceWrapper service, Future onClosed) async { try { @@ -62,6 +90,13 @@ class ServiceConnectionManager { } this.service = service; + + service.onServiceEvent.listen((e) { + if (e.kind == EventKind.kServiceRegistered) { + methodsForService.putIfAbsent(e.service, () => []).add(e.method); + } + }); + _isolateManager._service = service; _serviceExtensionManager._service = service; @@ -85,7 +120,8 @@ class ServiceConnectionManager { 'Timeline', 'Extension', '_Graph', - '_Logging' + '_Logging', + '_Service', ]; await Future.wait(streamIds.map((id) => service.streamListen(id))); } catch (e) { @@ -102,6 +138,19 @@ class ServiceConnectionManager { _stateController.add(null); _connectionClosedController.add(null); } + + // TODO(kenzie): add hot restart method, register method in flutter_tools. + + Future performHotReload() async { + try { + await callMulticastService('reloadSources', + isolateId: _isolateManager.selectedIsolate.id); + } catch (e) { + // TODO: improve general error handling. + print('Error during hot reload: "$e."'); + rethrow; + } + } } class IsolateManager { diff --git a/lib/ui/ui_utils.dart b/lib/ui/ui_utils.dart index 561641daed2..88509baa816 100644 --- a/lib/ui/ui_utils.dart +++ b/lib/ui/ui_utils.dart @@ -69,3 +69,16 @@ CoreElement createExtensionCheckBox(String extensionName) { text, ])); } + +// TODO(kenzie): add hotRestart button. + +// TODO(kenzie): move method to more specific library. +CoreElement createHotReloadButton() { + final PButton button = new PButton('Hot Reload')..small(); + button.click(() async { + button.disabled = true; + await serviceManager.performHotReload(); + button.disabled = false; + }); + return button; +} diff --git a/test/service_manager_test.dart b/test/service_manager_test.dart index c282caceedb..918fa20eb29 100644 --- a/test/service_manager_test.dart +++ b/test/service_manager_test.dart @@ -61,8 +61,11 @@ void main() { await _verifyInitialExtensionStateInServiceManager(extensionName); // Enable the service extension via ServiceExtensionManager. - await serviceManager.serviceExtensionManager - .setServiceExtensionState('ext.flutter.debugPaint', true, true); + await serviceManager.serviceExtensionManager.setServiceExtensionState( + 'ext.flutter.debugPaint', + true, + true, + ); await _verifyExtensionStateOnTestDevice(evalExpression, 'true', library); await _verifyExtensionStateInServiceManager(extensionName, true, true); @@ -77,15 +80,24 @@ void main() { ); await _verifyExtensionStateOnTestDevice( - evalExpression, 'TargetPlatform.android', library); + evalExpression, + 'TargetPlatform.android', + library, + ); await _verifyInitialExtensionStateInServiceManager(extensionName); // Enable the service extension via ServiceExtensionManager. await serviceManager.serviceExtensionManager.setServiceExtensionState( - 'ext.flutter.platformOverride', true, 'iOS'); + 'ext.flutter.platformOverride', + true, + 'iOS', + ); await _verifyExtensionStateOnTestDevice( - evalExpression, 'TargetPlatform.iOS', library); + evalExpression, + 'TargetPlatform.iOS', + library, + ); await _verifyExtensionStateInServiceManager(extensionName, true, 'iOS'); }); @@ -101,13 +113,56 @@ void main() { await _verifyInitialExtensionStateInServiceManager(extensionName); // Enable the service extension via ServiceExtensionManager. - await serviceManager.serviceExtensionManager - .setServiceExtensionState(extensionName, true, 0.5); + await serviceManager.serviceExtensionManager.setServiceExtensionState( + extensionName, + true, + 0.5, + ); await _verifyExtensionStateOnTestDevice(evalExpression, '0.5', library); await _verifyExtensionStateInServiceManager(extensionName, true, 0.5); }); + test('callService', () async { + final registeredService = + serviceManager.methodsForService['reloadSources'] ?? const []; + expect(registeredService, isNotEmpty); + + await serviceManager.callService( + 'reloadSources', + isolateId: serviceManager.isolateManager.selectedIsolate.id, + ); + }); + + test('callService throws exception', () async { + // Service with less than 1 registration. + expect(serviceManager.callService('fakeMethod'), throwsException); + + // Service with more than 1 registration. + serviceManager.methodsForService.putIfAbsent('fakeMethod', + () => ['registration1.fakeMethod', 'registration2.fakeMethod']); + expect(serviceManager.callService('fakeMethod'), throwsException); + }); + + test('callMulticastService', () async { + final registeredService = + serviceManager.methodsForService['reloadSources'] ?? const []; + expect(registeredService, isNotEmpty); + + await serviceManager.callMulticastService( + 'reloadSources', + isolateId: serviceManager.isolateManager.selectedIsolate.id, + ); + }); + + test('callMulticastService throws exception', () async { + expect(serviceManager.callService('fakeMethod'), throwsException); + }); + + test('hotReload', () async { + await serviceManager.performHotReload(); + }); + // TODO(kenzie): add hot restart test case. }, tags: 'useFlutterSdk'); }