[flutter_tool] add a vmservice API for hot ui requests (#45649)
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 8dca5ea..928cf2a 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -60,6 +60,11 @@ bool isStatic, ); +typedef ReloadMethod = Future<void> Function({ + String classId, + String libraryId, +}); + Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async { Duration delay = const Duration(milliseconds: 100); int attempts = 0; @@ -102,6 +107,7 @@ ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, + ReloadMethod reloadMethod, io.CompressionOptions compression, Device device, }); @@ -117,6 +123,7 @@ Restart restart, CompileExpression compileExpression, Device device, + ReloadMethod reloadMethod, ) { _vm = VM._empty(this); _peer.listen().catchError(_connectionError.completeError); @@ -150,15 +157,21 @@ 'alias': 'Flutter Tools', }); + } + + if (reloadMethod != null) { // Register a special method for hot UI. while this is implemented // currently in the same way as hot reload, it leaves the tool free // to change to a more efficient implementation in the future. + // + // `library` should be the file URI of the updated code. + // `class` should be the name of the Widget subclass to be marked dirty. For example, + // if the build method of a StatelessWidget is updated, this is the name of class. + // If the build method of a StatefulWidget is updated, then this is the name + // of the Widget class that created the State object. _peer.registerMethod('reloadMethod', (rpc.Parameters params) async { - final String isolateId = params['isolateId'].value as String; final String libraryId = params['library'].value as String; final String classId = params['class'].value as String; - final String methodId = params['method'].value as String; - final String methodBody = params['methodBody'].value as String; if (libraryId.isEmpty) { throw rpc.RpcException.invalidParams('Invalid \'libraryId\': $libraryId'); @@ -166,17 +179,14 @@ if (classId.isEmpty) { throw rpc.RpcException.invalidParams('Invalid \'classId\': $classId'); } - if (methodId.isEmpty) { - throw rpc.RpcException.invalidParams('Invalid \'methodId\': $methodId'); - } - if (methodBody.isEmpty) { - throw rpc.RpcException.invalidParams('Invalid \'methodBody\': $methodBody'); - } printTrace('reloadMethod not yet supported, falling back to hot reload'); try { - await reloadSources(isolateId); + await reloadMethod( + libraryId: libraryId, + classId: classId, + ); return <String, String>{'type': 'Success'}; } on rpc.RpcException { rethrow; @@ -298,6 +308,7 @@ ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, + ReloadMethod reloadMethod, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, Device device, }) async { @@ -308,6 +319,7 @@ compileExpression: compileExpression, compression: compression, device: device, + reloadMethod: reloadMethod, ); } @@ -316,13 +328,23 @@ ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, + ReloadMethod reloadMethod, io.CompressionOptions compression = io.CompressionOptions.compressionDefault, Device device, }) async { final Uri wsUri = httpUri.replace(scheme: 'ws', path: fs.path.join(httpUri.path, 'ws')); final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression); final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError); - final VMService service = VMService(peer, httpUri, wsUri, reloadSources, restart, compileExpression, device); + final VMService service = VMService( + peer, + httpUri, + wsUri, + reloadSources, + restart, + compileExpression, + device, + reloadMethod, + ); // This call is to ensure we are able to establish a connection instead of // keeping on trucking and failing farther down the process. await service._sendRequest('getVersion', const <String, dynamic>{}); @@ -1336,6 +1358,12 @@ return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble'); } + Future<Map<String, dynamic>> flutterFastReassemble(String classId) { + return invokeFlutterExtensionRpcRaw('ext.flutter.fastReassemble', params: <String, Object>{ + 'class': classId, + }); + } + Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async { final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent'); // result might be null when the service extension is not initialized