[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