Revert "[flutter_tools] remove flutter view cache (#56223)" (#56385)

This reverts commit 209bdcb6695b6bffa7b21ce6fc8fcf2310f5ecd0.
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
index a2370bd..282e3de 100644
--- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
+++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -191,11 +191,6 @@
   }
 
   @override
-  Future<List<FlutterView>> listFlutterViews() async {
-    return <FlutterView>[];
-  }
-
-  @override
   Future<void> debugDumpApp() async {
     try {
       await _vmService
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index 43ea0f4..4e89ffa 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -226,13 +226,38 @@
     return completer.future;
   }
 
+  Future<void> refreshViews() async {
+    if (vmService == null) {
+      return;
+    }
+    final List<FlutterView> updatedViews = await vmService.getFlutterViews();
+    _views
+      ..clear()
+      ..addAll(updatedViews);
+  }
+  final List<FlutterView> _views = <FlutterView>[];
+
+  List<FlutterView> get views {
+    if (vmService == null) {
+      return <FlutterView>[];
+    }
+    if (viewFilter != null) {
+      return <FlutterView>[
+        for (final FlutterView flutterView in _views)
+          if (flutterView.uiIsolate.name.contains(viewFilter))
+            flutterView
+      ];
+    }
+    return _views;
+  }
+
   Future<void> exitApps({
     @visibleForTesting Duration timeoutDelay = const Duration(seconds: 10),
   }) async {
     if (!device.supportsFlutterExit) {
       return device.stopApp(package);
     }
-    final List<FlutterView> views = await vmService.getFlutterViews();
+    await refreshViews();
     if (views == null || views.isEmpty) {
       return device.stopApp(package);
     }
@@ -308,7 +333,6 @@
     final Uri deviceAssetsDirectoryUri = devFS.baseUri.resolveUri(
         globals.fs.path.toUri(getAssetBuildDirectory()));
     assert(deviceAssetsDirectoryUri != null);
-    final List<FlutterView> views = await vmService.getFlutterViews();
     await Future.wait<void>(views.map<Future<void>>(
       (FlutterView view) => vmService.setAssetDirectory(
         assetsDirectory: deviceAssetsDirectoryUri,
@@ -319,7 +343,6 @@
   }
 
   Future<void> debugDumpApp() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterDebugDumpApp(
         isolateId: view.uiIsolate.id,
@@ -328,7 +351,6 @@
   }
 
   Future<void> debugDumpRenderTree() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterDebugDumpRenderTree(
         isolateId: view.uiIsolate.id,
@@ -337,7 +359,6 @@
   }
 
   Future<void> debugDumpLayerTree() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterDebugDumpLayerTree(
         isolateId: view.uiIsolate.id,
@@ -346,7 +367,6 @@
   }
 
   Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterDebugDumpSemanticsTreeInTraversalOrder(
         isolateId: view.uiIsolate.id,
@@ -355,7 +375,6 @@
   }
 
   Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
         isolateId: view.uiIsolate.id,
@@ -364,7 +383,6 @@
   }
 
   Future<void> toggleDebugPaintSizeEnabled() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterToggleDebugPaintSizeEnabled(
         isolateId: view.uiIsolate.id,
@@ -373,7 +391,6 @@
   }
 
   Future<void> toggleDebugCheckElevationsEnabled() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterToggleDebugCheckElevationsEnabled(
         isolateId: view.uiIsolate.id,
@@ -382,7 +399,6 @@
   }
 
   Future<void> debugTogglePerformanceOverlayOverride() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterTogglePerformanceOverlayOverride(
         isolateId: view.uiIsolate.id,
@@ -391,7 +407,6 @@
   }
 
   Future<void> toggleWidgetInspector() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterToggleWidgetInspector(
         isolateId: view.uiIsolate.id,
@@ -400,7 +415,6 @@
   }
 
   Future<void> toggleProfileWidgetBuilds() async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     for (final FlutterView view in views) {
       await vmService.flutterToggleProfileWidgetBuilds(
         isolateId: view.uiIsolate.id,
@@ -409,7 +423,6 @@
   }
 
   Future<String> togglePlatform({ String from }) async {
-    final List<FlutterView> views = await vmService.getFlutterViews();
     final String to = nextPlatform(from, featureFlags);
     for (final FlutterView view in views) {
       await vmService.flutterPlatformOverride(
@@ -751,17 +764,15 @@
   Future<Map<String, dynamic>> invokeFlutterExtensionRpcRawOnFirstIsolate(
     String method, {
     Map<String, dynamic> params,
-  }) async {
-    final List<FlutterView> views = await flutterDevices
-      .first
-      .vmService.getFlutterViews();
+  }) {
     return flutterDevices
       .first
       .vmService
       .invokeFlutterExtensionRpcRaw(
         method,
         args: params,
-        isolateId: views
+        isolateId: flutterDevices
+          .first.views
           .first.uiIsolate.id
       );
   }
@@ -800,23 +811,13 @@
     throw Exception('Canvaskit not supported by this runner.');
   }
 
-  /// List the attached flutter views.
-  Future<List<FlutterView>> listFlutterViews() async {
-    return (await Future.wait(
-      flutterDevices.map((FlutterDevice d) => d.vmService.getFlutterViews()))
-    ).expand((List<FlutterView> views) => views).toList();
-  }
-
   /// Write the SkSL shaders to a zip file in build directory.
   Future<void> writeSkSL() async {
     if (!supportsWriteSkSL) {
       throw Exception('writeSkSL is not supported by this runner.');
     }
-    final List<FlutterView> views = await flutterDevices
-      .first
-      .vmService.getFlutterViews();
     final Map<String, Object> data = await flutterDevices.first.vmService.getSkSLs(
-      viewId: views.first.id,
+      viewId: flutterDevices.first.views.first.id,
     );
     if (data.isEmpty) {
       globals.logger.printStatus(
@@ -901,61 +902,78 @@
     appFinished();
   }
 
+  Future<void> refreshViews() async {
+    final List<Future<void>> futures = <Future<void>>[
+      for (final FlutterDevice device in flutterDevices) device.refreshViews(),
+    ];
+    await Future.wait(futures);
+  }
+
   Future<void> debugDumpApp() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugDumpApp();
     }
   }
 
   Future<void> debugDumpRenderTree() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugDumpRenderTree();
     }
   }
 
   Future<void> debugDumpLayerTree() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugDumpLayerTree();
     }
   }
 
   Future<void> debugDumpSemanticsTreeInTraversalOrder() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugDumpSemanticsTreeInTraversalOrder();
     }
   }
 
   Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugDumpSemanticsTreeInInverseHitTestOrder();
     }
   }
 
   Future<void> debugToggleDebugPaintSizeEnabled() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.toggleDebugPaintSizeEnabled();
     }
   }
 
   Future<void> debugToggleDebugCheckElevationsEnabled() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.toggleDebugCheckElevationsEnabled();
     }
   }
 
   Future<void> debugTogglePerformanceOverlayOverride() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.debugTogglePerformanceOverlayOverride();
     }
   }
 
   Future<void> debugToggleWidgetInspector() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.toggleWidgetInspector();
     }
   }
 
   Future<void> debugToggleProfileWidgetBuilds() async {
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       await device.toggleProfileWidgetBuilds();
     }
@@ -979,12 +997,11 @@
       'flutter',
       'png',
     );
-    final List<FlutterView> views = await device
-      .vmService.getFlutterViews();
     try {
       if (supportsServiceProtocol && isRunningDebug) {
+        await device.refreshViews();
         try {
-          for (final FlutterView view in views) {
+          for (final FlutterView view in device.views) {
             await device.vmService.flutterDebugAllowBanner(
               false,
               isolateId: view.uiIsolate.id,
@@ -1001,7 +1018,7 @@
       } finally {
         if (supportsServiceProtocol && isRunningDebug) {
           try {
-            for (final FlutterView view in views) {
+            for (final FlutterView view in device.views) {
               await device.vmService.flutterDebugAllowBanner(
                 true,
                 isolateId: view.uiIsolate.id,
@@ -1026,10 +1043,9 @@
   }
 
   Future<void> debugTogglePlatform() async {
-    final List<FlutterView> views = await flutterDevices
-      .first
-      .vmService.getFlutterViews();
-    final String isolateId = views.first.uiIsolate.id;
+    await refreshViews();
+    final String isolateId = flutterDevices
+      .first.views.first.uiIsolate.id;
     final String from = await flutterDevices
       .first.vmService.flutterPlatformOverride(
         isolateId: isolateId,
@@ -1062,8 +1078,10 @@
     if (!debuggingOptions.debuggingEnabled) {
       throw 'The service protocol is not enabled.';
     }
+
     _finished = Completer<int>();
-    // Listen for service protocol connection to close.
+
+    bool viewFound = false;
     for (final FlutterDevice device in flutterDevices) {
       await device.connect(
         reloadSources: reloadSources,
@@ -1071,16 +1089,21 @@
         compileExpression: compileExpression,
         reloadMethod: reloadMethod,
       );
-      // This will wait for at least one flutter view before returning.
-      final Status status = globals.logger.startProgress(
-        'Waiting for ${device.device.name} to report its views...',
-        timeout: const Duration(milliseconds: 200),
-      );
-      try {
-        await device.vmService.getFlutterViews();
-      } finally {
-        status.stop();
+      await device.refreshViews();
+      if (device.views.isNotEmpty) {
+        viewFound = true;
       }
+    }
+    if (!viewFound) {
+      if (flutterDevices.length == 1) {
+        throw 'No Flutter view is available on ${flutterDevices.first.device.name}.';
+      }
+      throw 'No Flutter view is available on any device '
+            '(${flutterDevices.map<String>((FlutterDevice device) => device.device.name).join(', ')}).';
+    }
+
+    // Listen for service protocol connection to close.
+    for (final FlutterDevice device in flutterDevices) {
       // This hooks up callbacks for when the connection stops in the future.
       // We don't want to wait for them. We don't handle errors in those callbacks'
       // futures either because they just print to logger and is not critical.
@@ -1349,7 +1372,8 @@
         }
         return false;
       case 'l':
-        final List<FlutterView> views = await residentRunner.listFlutterViews();
+        final List<FlutterView> views = residentRunner.flutterDevices
+            .expand((FlutterDevice d) => d.views).toList();
         globals.printStatus('Connected ${pluralize('view', views.length)}:');
         for (final FlutterView v in views) {
           globals.printStatus('${v.uiIsolate.name} (${v.uiIsolate.id})', indent: 2);
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart
index 8bfd230..1779426 100644
--- a/packages/flutter_tools/lib/src/run_cold.dart
+++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -95,6 +95,7 @@
         continue;
       }
       await device.initLogReader();
+      await device.refreshViews();
       globals.printTrace('Connected to ${device.device.name}');
     }
 
@@ -144,9 +145,9 @@
     for (final FlutterDevice device in flutterDevices) {
       await device.initLogReader();
     }
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         globals.printTrace('Connected to $view.');
       }
     }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 3d8b312..38b36fb 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -198,8 +198,7 @@
     }
 
     for (final FlutterDevice device in flutterDevices) {
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         await device.vmService.flutterFastReassemble(
           classId,
           isolateId: view.uiIsolate.id,
@@ -278,14 +277,14 @@
       return 3;
     }
 
+    await refreshViews();
     for (final FlutterDevice device in flutterDevices) {
       // VM must have accepted the kernel binary, there will be no reload
       // report, so we let incremental compiler know that source code was accepted.
       if (device.generator != null) {
         device.generator.accept();
       }
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         globals.printTrace('Connected to $view.');
       }
     }
@@ -475,15 +474,15 @@
     Uri main,
     Uri assetsDirectory,
   ) async {
-    final List<FlutterView> views = await device.vmService.getFlutterViews();
     await Future.wait(<Future<void>>[
-      for (final FlutterView view in views)
+      for (final FlutterView view in device.views)
         device.vmService.runInView(
           viewId: view.id,
           main: main,
           assetsDirectory: assetsDirectory,
         ),
     ]);
+    await device.refreshViews();
   }
 
   Future<void> _launchFromDevFS(String mainScript) async {
@@ -502,8 +501,7 @@
     if (benchmarkMode) {
       futures.clear();
       for (final FlutterDevice device in flutterDevices) {
-        final List<FlutterView> views = await device.vmService.getFlutterViews();
-        for (final FlutterView view in views) {
+        for (final FlutterView view in device.views) {
           futures.add(device.vmService
             .flushUIThreadTasks(uiIsolateId: view.uiIsolate.id));
         }
@@ -516,6 +514,9 @@
     String reason,
     bool benchmarkMode = false,
   }) async {
+    globals.printTrace('Refreshing active FlutterViews before restarting.');
+    await refreshViews();
+
     final Stopwatch restartTimer = Stopwatch()..start();
     // TODO(aam): Add generator reset logic once we switch to using incremental
     // compiler for full application recompilation on restart.
@@ -540,8 +541,7 @@
     final List<Future<void>> operations = <Future<void>>[];
     for (final FlutterDevice device in flutterDevices) {
       final Set<String> uiIsolatesIds = <String>{};
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         if (view.uiIsolate == null) {
           continue;
         }
@@ -810,8 +810,7 @@
     void Function(String message) onSlow,
   }) async {
     for (final FlutterDevice device in flutterDevices) {
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         if (view.uiIsolate == null) {
           return OperationResult(2, 'Application isolate not found', fatal: true);
         }
@@ -819,6 +818,10 @@
     }
 
     final Stopwatch reloadTimer = Stopwatch()..start();
+
+    globals.printTrace('Refreshing active FlutterViews before reloading.');
+    await refreshViews();
+
     final Stopwatch devFSTimer = Stopwatch()..start();
     final UpdateFSReport updatedDevFS = await _updateDevFS();
     // Record time it took to synchronize to DevFS.
@@ -909,6 +912,14 @@
     // Record time it took for the VM to reload the sources.
     _addBenchmarkData('hotReloadVMReloadMilliseconds', vmReloadTimer.elapsed.inMilliseconds);
     final Stopwatch reassembleTimer = Stopwatch()..start();
+
+    // Reload the isolate data.
+    await Future.wait(<Future<void>>[
+      for (final FlutterDevice device in flutterDevices)
+        device.refreshViews()
+    ]);
+
+    globals.printTrace('Evicting dirty assets');
     await _evictDirtyAssets();
 
     // Check if any isolates are paused and reassemble those
@@ -919,8 +930,7 @@
     int pausedIsolatesFound = 0;
     bool failedReassemble = false;
     for (final FlutterDevice device in flutterDevices) {
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      for (final FlutterView view in views) {
+      for (final FlutterView view in device.views) {
         // Check if the isolate is paused, and if so, don't reassemble. Ignore the
         // PostPauseEvent event - the client requesting the pause will resume the app.
         final vm_service.Isolate isolate = await device.vmService
@@ -1045,7 +1055,11 @@
     final StringBuffer message = StringBuffer();
     bool plural;
     if (pausedIsolatesFound == 1) {
-      message.write('The application is ');
+      if (flutterDevices.length == 1 && flutterDevices.single.views.length == 1) {
+        message.write('The application is ');
+      } else {
+        message.write('An isolate is ');
+      }
       plural = false;
     } else {
       message.write('$pausedIsolatesFound isolates are ');
@@ -1107,14 +1121,13 @@
     }
   }
 
-  Future<void> _evictDirtyAssets() async {
+  Future<void> _evictDirtyAssets() {
     final List<Future<Map<String, dynamic>>> futures = <Future<Map<String, dynamic>>>[];
     for (final FlutterDevice device in flutterDevices) {
       if (device.devFS.assetPathsToEvict.isEmpty) {
         continue;
       }
-      final List<FlutterView> views = await device.vmService.getFlutterViews();
-      if (views.first.uiIsolate == null) {
+      if (device.views.first.uiIsolate == null) {
         globals.printError('Application isolate not found for $device');
         continue;
       }
@@ -1123,7 +1136,7 @@
           device.vmService
             .flutterEvictAsset(
               assetPath,
-              isolateId: views.first.uiIsolate.id,
+              isolateId: device.views.first.uiIsolate.id,
             )
         );
       }
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart
index 14bc858..9b34f08 100644
--- a/packages/flutter_tools/lib/src/vmservice.dart
+++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -684,29 +684,15 @@
   }
 
   /// List all [FlutterView]s attached to the current VM.
-  ///
-  /// If this returns an empty list, it will poll forever unless [returnEarly]
-  /// is set to true.
-  ///
-  /// By default, the poll duration is 50 milliseconds.
-  Future<List<FlutterView>> getFlutterViews({
-    bool returnEarly = false,
-    Duration delay = const Duration(milliseconds: 50),
-  }) async {
-    while (true) {
-      final vm_service.Response response = await callMethod(
-        kListViewsMethod,
-      );
-      final List<Object> rawViews = response.json['views'] as List<Object>;
-      final List<FlutterView> views = <FlutterView>[
-        for (final Object rawView in rawViews)
-          FlutterView.parse(rawView as Map<String, Object>)
-      ];
-      if (views.isNotEmpty || returnEarly) {
-        return views;
-      }
-      await Future<void>.delayed(delay);
-    }
+  Future<List<FlutterView>> getFlutterViews() async {
+    final vm_service.Response response = await callMethod(
+      kListViewsMethod,
+    );
+    final List<Object> rawViews = response.json['views'] as List<Object>;
+    return <FlutterView>[
+      for (final Object rawView in rawViews)
+        FlutterView.parse(rawView as Map<String, Object>)
+    ];
   }
 
   /// Attempt to retrieve the isolate with id [isolateId], or `null` if it has
diff --git a/packages/flutter_tools/lib/src/web/web_device.dart b/packages/flutter_tools/lib/src/web/web_device.dart
index f984858..fbb39c7 100644
--- a/packages/flutter_tools/lib/src/web/web_device.dart
+++ b/packages/flutter_tools/lib/src/web/web_device.dart
@@ -66,7 +66,7 @@
   bool get supportsStartPaused => true;
 
   @override
-  bool get supportsFlutterExit => false;
+  bool get supportsFlutterExit => true;
 
   @override
   bool get supportsScreenshot => false;
@@ -344,9 +344,6 @@
   Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;
 
   @override
-  bool get supportsFlutterExit => false;
-
-  @override
   Future<bool> get isLocalEmulator async => false;
 
   @override
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
index bdf8dff..b79f653 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -685,6 +685,7 @@
     final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
       requests: <VmServiceExpectation>[
         FakeVmServiceRequest(
+          id: '1',
           method: kListViewsMethod,
           args: null,
           jsonResponse: <String, Object>{
@@ -697,12 +698,14 @@
           },
         ),
         FakeVmServiceRequest(
+          id: '2',
           method: 'getVM',
           args: null,
           jsonResponse: vm_service.VM.parse(<String, Object>{})
             .toJson(),
         ),
         FakeVmServiceRequest(
+          id: '3',
           method: '_createDevFS',
           args: <String, Object>{
             'fsName': globals.fs.currentDirectory.absolute.path,
@@ -712,6 +715,7 @@
           },
         ),
         FakeVmServiceRequest(
+          id: '4',
           method: kListViewsMethod,
           args: null,
           jsonResponse: <String, Object>{
diff --git a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart
index 9c0bccd..4be5eda 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/devices_test.dart
@@ -65,7 +65,7 @@
             'hotRestart': true,
             'screenshot': false,
             'fastStart': false,
-            'flutterExit': false,
+            'flutterExit': true,
             'hardwareRendering': false,
             'startPaused': true
           }
diff --git a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart
index 5648feb..fe7f4ca 100644
--- a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart
+++ b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart
@@ -12,6 +12,7 @@
     final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
       requests: <VmServiceExpectation>[
         FakeVmServiceRequest(
+          id: '1',
           method: 'getVM',
           jsonResponse: (vm_service.VM.parse(<String, Object>{})
             ..isolates = <vm_service.IsolateRef>[
@@ -22,6 +23,7 @@
           ).toJson(),
         ),
         const FakeVmServiceRequest(
+          id: '2',
           method: 'getScripts',
           args: <String, Object>{
             'isolateId': '1',
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart
index 2a631ee..80e9963 100644
--- a/packages/flutter_tools/test/general.shard/devfs_test.dart
+++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -129,6 +129,7 @@
         final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
           requests: <VmServiceExpectation>[
             FakeVmServiceRequest(
+              id: '1',
               method: '_createDevFS',
               args: <String, Object>{
                 'fsName': 'test',
@@ -201,6 +202,7 @@
       final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
         requests: <VmServiceExpectation>[
           FakeVmServiceRequest(
+            id: '1',
             method: '_createDevFS',
             args: <String, Object>{
               'fsName': 'test',
diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
index dd91b10..4f0bd41 100644
--- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
+++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart
@@ -637,6 +637,7 @@
       final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
         requests: <VmServiceExpectation>[
           FakeVmServiceRequest(
+            id: '1',
             method: kListViewsMethod,
             jsonResponse: <String, Object>{
               'views': <Object>[
diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart
index 9c18f56..dae17c3 100644
--- a/packages/flutter_tools/test/general.shard/hot_test.dart
+++ b/packages/flutter_tools/test/general.shard/hot_test.dart
@@ -21,36 +21,6 @@
 import '../src/context.dart';
 import '../src/mocks.dart';
 
-final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
-  id: '1',
-  pauseEvent: vm_service.Event(
-    kind: vm_service.EventKind.kResume,
-    timestamp: 0
-  ),
-  breakpoints: <vm_service.Breakpoint>[],
-  exceptionPauseMode: null,
-  libraries: <vm_service.LibraryRef>[],
-  livePorts: 0,
-  name: 'test',
-  number: '1',
-  pauseOnExit: false,
-  runnable: true,
-  startTime: 0,
-);
-
-final FlutterView fakeFlutterView = FlutterView(
-  id: 'a',
-  uiIsolate: fakeUnpausedIsolate,
-);
-
-final FakeVmServiceRequest listViews = FakeVmServiceRequest(
-  method: kListViewsMethod,
-  jsonResponse: <String, Object>{
-    'views': <Object>[
-      fakeFlutterView.toJson(),
-    ],
-  },
-);
 void main() {
   group('validateReloadReport', () {
     testUsingContext('invalid', () async {
@@ -202,72 +172,42 @@
 
     testUsingContext('Does hot restarts when all devices support it', () async {
       final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-        listViews,
-        FakeVmServiceRequest(
-          method: 'getIsolate',
-          args: <String, Object>{
-            'isolateId': fakeUnpausedIsolate.id,
-          },
-          jsonResponse: fakeUnpausedIsolate.toJson(),
+        const FakeVmServiceRequest(
+          id: '1',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
+          }
+        ),
+         const FakeVmServiceRequest(
+          id: '2',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
+          }
         ),
         FakeVmServiceRequest(
+          id: '3',
           method: 'getVM',
           jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson()
         ),
-        listViews,
         FakeVmServiceRequest(
-          method: 'getIsolate',
-          args: <String, Object>{
-            'isolateId': fakeUnpausedIsolate.id,
-          },
-          jsonResponse: fakeUnpausedIsolate.toJson(),
-        ),
-        FakeVmServiceRequest(
+          id: '4',
           method: 'getVM',
           jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson()
         ),
-        listViews,
-        listViews,
         const FakeVmServiceRequest(
-          method: 'streamListen',
-          args: <String, Object>{
-            'streamId': 'Isolate',
+          id: '5',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
           }
         ),
         const FakeVmServiceRequest(
-          method: 'streamListen',
-          args: <String, Object>{
-            'streamId': 'Isolate',
-          }
-        ),
-        FakeVmServiceStreamResponse(
-          streamId: 'Isolate',
-          event: vm_service.Event(
-            timestamp: 0,
-            kind: vm_service.EventKind.kIsolateRunnable,
-          )
-        ),
-        FakeVmServiceStreamResponse(
-          streamId: 'Isolate',
-          event: vm_service.Event(
-            timestamp: 0,
-            kind: vm_service.EventKind.kIsolateRunnable,
-          )
-        ),
-        FakeVmServiceRequest(
-          method: kRunInViewMethod,
-          args: <String, Object>{
-            'viewId': fakeFlutterView.id,
-            'mainScript': 'lib/main.dart.dill',
-            'assetDirectory': 'build/flutter_assets',
-          }
-        ),
-        FakeVmServiceRequest(
-          method: kRunInViewMethod,
-          args: <String, Object>{
-            'viewId': fakeFlutterView.id,
-            'mainScript': 'lib/main.dart.dill',
-            'assetDirectory': 'build/flutter_assets',
+          id: '6',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
           }
         ),
       ]);
@@ -317,39 +257,24 @@
     testUsingContext('hot restart supported', () async {
       // Setup mocks
       final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-        listViews,
-        FakeVmServiceRequest(
-          method: 'getIsolate',
-          args: <String, Object>{
-            'isolateId': fakeUnpausedIsolate.id,
-          },
-          jsonResponse: fakeUnpausedIsolate.toJson(),
-        ),
-        FakeVmServiceRequest(
-          method: 'getVM',
-          jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
-        ),
-        listViews,
         const FakeVmServiceRequest(
-          method: 'streamListen',
-          args: <String, Object>{
-            'streamId': 'Isolate',
+          id: '1',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
           }
         ),
         FakeVmServiceRequest(
-          method: kRunInViewMethod,
-          args: <String, Object>{
-            'viewId': fakeFlutterView.id,
-            'mainScript': 'lib/main.dart.dill',
-            'assetDirectory': 'build/flutter_assets',
-          }
+          id: '2',
+          method: 'getVM',
+          jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson()
         ),
-        FakeVmServiceStreamResponse(
-          streamId: 'Isolate',
-          event: vm_service.Event(
-            timestamp: 0,
-            kind: vm_service.EventKind.kIsolateRunnable,
-          )
+        const FakeVmServiceRequest(
+          id: '3',
+          method: kListViewsMethod,
+          jsonResponse: <String, Object>{
+            'views': <Object>[],
+          }
         ),
       ]);
       final MockDevice mockDevice = MockDevice();
diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
index 59be8f7..1ce0659 100644
--- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -70,15 +70,6 @@
   uiIsolate: fakeUnpausedIsolate,
 );
 
-final FakeVmServiceRequest listViews = FakeVmServiceRequest(
-  method: kListViewsMethod,
-  jsonResponse: <String, Object>{
-    'views': <Object>[
-      fakeFlutterView.toJson(),
-    ],
-  },
-);
-
 void main() {
   final Uri testUri = Uri.parse('foo://bar');
   Testbed testbed;
@@ -136,6 +127,10 @@
       );
     });
     when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
+    when(mockFlutterDevice.views).thenReturn(<FlutterView>[
+      // NB: still using mock FlutterDevice.
+      fakeFlutterView,
+    ]);
     when(mockFlutterDevice.device).thenReturn(mockDevice);
     when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
     when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri));
@@ -151,6 +146,7 @@
     when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) {
       return fakeVmServiceHost.vmService;
     });
+    when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { });
     when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenAnswer((Invocation invocation) async {
       return <Future<vm_service.ReloadReport>>[
         Future<vm_service.ReloadReport>.value(vm_service.ReloadReport.parse(<String, dynamic>{
@@ -170,7 +166,15 @@
 
   test('FlutterDevice can list views with a filter', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
+      FakeVmServiceRequest(
+        id: '1',
+        method: kListViewsMethod,
+        jsonResponse: <String, Object>{
+          'views': <Object>[
+            fakeFlutterView.toJson(),
+          ],
+        },
+      ),
     ]);
     final MockDevice mockDevice = MockDevice();
     final FlutterDevice flutterDevice = FlutterDevice(
@@ -180,13 +184,14 @@
     );
 
     flutterDevice.vmService = fakeVmServiceHost.vmService;
+
+    await flutterDevice.refreshViews();
+
+    expect(flutterDevice.views, isEmpty);
   }));
 
   test('ResidentRunner can attach to device successfully', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync();
     final Completer<void> onAppStart = Completer<void>.sync();
     final Future<int> result = residentRunner.attach(
@@ -202,15 +207,12 @@
     expect(onConnectionInfo.isCompleted, true);
     expect((await connectionInfo).baseUri, 'foo://bar');
     expect(onAppStart.isCompleted, true);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -218,17 +220,19 @@
         jsonResponse: fakeUnpausedIsolate.toJson(),
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'getVM',
         jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
       ),
-      listViews,
       const FakeVmServiceRequest(
+        id: '3',
         method: 'streamListen',
         args: <String, Object>{
           'streamId': 'Isolate',
         }
       ),
       FakeVmServiceRequest(
+        id: '4',
         method: kRunInViewMethod,
         args: <String, Object>{
           'viewId': fakeFlutterView.id,
@@ -280,15 +284,10 @@
     expect(onConnectionInfo.isCompleted, true);
     expect((await connectionInfo).baseUri, 'foo://bar');
     expect(onAppStart.isCompleted, true);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -330,18 +329,15 @@
       cdKey(CustomDimensions.hotEventEmulator): 'false',
       cdKey(CustomDimensions.hotEventFullRestart): 'false',
     })).called(1);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Usage: () => MockUsage(),
   }));
 
   test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-      listViews,
-      listViews,
+      // Not all requests are present due to existing mocks
       FakeVmServiceRequest(
+        id: '1',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': '1',
@@ -349,6 +345,7 @@
         jsonResponse: fakeUnpausedIsolate.toJson(),
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'ext.flutter.reassemble',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -384,11 +381,10 @@
   }));
 
   test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
+     // Not all requests are present due to existing mocks
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -396,17 +392,19 @@
         jsonResponse: fakeUnpausedIsolate.toJson(),
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'getVM',
         jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
       ),
-      listViews,
       const FakeVmServiceRequest(
+        id: '3',
         method: 'streamListen',
         args: <String, Object>{
           'streamId': 'Isolate',
         },
       ),
       FakeVmServiceRequest(
+        id: '4',
         method: kRunInViewMethod,
         args: <String, Object>{
           'viewId': fakeFlutterView.id,
@@ -447,16 +445,12 @@
       containsPair(cdKey(CustomDimensions.hotEventTargetPlatform),
                    getNameForTargetPlatform(TargetPlatform.android_arm)),
     );
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Usage: () => MockUsage(),
   }));
 
   test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async {
       return 'Example';
     });
@@ -499,7 +493,6 @@
       cdKey(CustomDimensions.hotEventEmulator): 'false',
       cdKey(CustomDimensions.hotEventFullRestart): 'true',
     })).called(1);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     Usage: () => MockUsage(),
   }));
@@ -585,15 +578,14 @@
 
   test('ResidentRunner does support CanvasKit', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
-
     expect(() => residentRunner.toggleCanvaskit(),
       throwsA(isA<Exception>()));
   }));
 
   test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      FakeVmServiceRequest(
+       FakeVmServiceRequest(
+        id: '1',
         method: kGetSkSLsMethod,
         args: <String, Object>{
           'viewId': fakeFlutterView.id,
@@ -601,18 +593,17 @@
         jsonResponse: <String, Object>{
           'SkSLs': <String, Object>{}
         }
-      ),
+      )
     ]);
     await residentRunner.writeSkSL();
 
     expect(testLogger.statusText, contains('No data was receieved'));
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: kGetSkSLsMethod,
         args: <String, Object>{
           'viewId': fakeFlutterView.id,
@@ -643,8 +634,8 @@
 
   test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -652,6 +643,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -684,8 +676,8 @@
 
   test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -704,8 +696,8 @@
 
   test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -713,6 +705,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -726,13 +719,12 @@
     await residentRunner.screenshot(mockFlutterDevice);
 
     expect(testLogger.errorText, contains('Error'));
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
       FakeVmServiceRequest(
+        id: '1',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -740,6 +732,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'ext.flutter.debugAllowBanner',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -761,13 +754,10 @@
 
     expect(() => residentRunner.screenshot(mockFlutterDevice),
         throwsAssertionError);
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     residentRunner = HotRunner(
       <FlutterDevice>[
         mockFlutterDevice,
@@ -791,6 +781,7 @@
   test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       FakeVmServiceRequest(
+        id: '1',
         method: '_flutter.listViews',
         jsonResponse: <String, Object>{
           'views': <Object>[
@@ -799,6 +790,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -808,6 +800,7 @@
     ]);
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
+      <FlutterView>[ fakeFlutterView ],
     );
     flutterDevice.vmService = fakeVmServiceHost.vmService;
     when(mockDevice.supportsFlutterExit).thenReturn(true);
@@ -821,6 +814,7 @@
   test('FlutterDevice will call stopApp if the exit request times out', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       FakeVmServiceRequest(
+        id: '1',
         method: '_flutter.listViews',
         jsonResponse: <String, Object>{
           'views': <Object>[
@@ -829,6 +823,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -836,6 +831,7 @@
         jsonResponse: fakeUnpausedIsolate.toJson(),
       ),
       FakeVmServiceRequest(
+        id: '3',
         method: 'ext.flutter.exit',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -846,6 +842,7 @@
     ]);
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
+      <FlutterView>[ fakeFlutterView ],
     );
     flutterDevice.vmService = fakeVmServiceHost.vmService;
     when(mockDevice.supportsFlutterExit).thenReturn(true);
@@ -861,6 +858,7 @@
   test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       FakeVmServiceRequest(
+        id: '1',
         method: kListViewsMethod,
         jsonResponse: <String, Object>{
           'views': <Object>[
@@ -869,6 +867,7 @@
         },
       ),
       FakeVmServiceRequest(
+        id: '2',
         method: 'getIsolate',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id
@@ -876,6 +875,7 @@
         jsonResponse: fakeUnpausedIsolate.toJson(),
       ),
       FakeVmServiceRequest(
+        id: '3',
         method: 'ext.flutter.exit',
         args: <String, Object>{
           'isolateId': fakeUnpausedIsolate.id,
@@ -885,6 +885,7 @@
     ]);
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
+      <FlutterView> [fakeFlutterView ],
     );
     flutterDevice.vmService = fakeVmServiceHost.vmService;
 
@@ -896,10 +897,18 @@
     expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
+  test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async {
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
+    await residentRunner.refreshViews();
+
+    verify(mockFlutterDevice.refreshViews()).called(1);
+  }));
+
   test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async {
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpApp();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugDumpApp()).called(1);
   }));
 
@@ -907,6 +916,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpRenderTree();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugDumpRenderTree()).called(1);
   }));
 
@@ -914,6 +924,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpLayerTree();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugDumpLayerTree()).called(1);
   }));
 
@@ -921,6 +932,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpSemanticsTreeInTraversalOrder();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugDumpSemanticsTreeInTraversalOrder()).called(1);
   }));
 
@@ -928,6 +940,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
   }));
 
@@ -935,6 +948,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleDebugPaintSizeEnabled();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.toggleDebugPaintSizeEnabled()).called(1);
   }));
 
@@ -942,6 +956,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleDebugCheckElevationsEnabled();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1);
   }));
 
@@ -949,6 +964,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugTogglePerformanceOverlayOverride();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.debugTogglePerformanceOverlayOverride()).called(1);
   }));
 
@@ -956,6 +972,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleWidgetInspector();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.toggleWidgetInspector()).called(1);
   }));
 
@@ -963,14 +980,12 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     await residentRunner.debugToggleProfileWidgetBuilds();
 
+    verify(mockFlutterDevice.refreshViews()).called(1);
     verify(mockFlutterDevice.toggleProfileWidgetBuilds()).called(1);
   }));
 
   test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     setWsAddress(testUri, fakeVmServiceHost.vmService);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     residentRunner = HotRunner(
@@ -992,10 +1007,7 @@
   }));
 
   test('HotRunner unforwards device ports', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder();
     when(mockDevice.portForwarder).thenReturn(mockPortForwarder);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
@@ -1023,10 +1035,7 @@
   }));
 
   test('HotRunner handles failure to write vmservice file', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     residentRunner = HotRunner(
       <FlutterDevice>[
@@ -1044,16 +1053,13 @@
     await residentRunner.run();
 
     expect(testLogger.errorText, contains('Failed to write vmservice-out-file at foo'));
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }, overrides: <Type, Generator>{
     FileSystem: () => ThrowingForwardingFileSystem(MemoryFileSystem()),
   }));
 
 
   test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async {
-    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
-      listViews,
-    ]);
+    fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
     globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true);
     setWsAddress(testUri, fakeVmServiceHost.vmService);
     residentRunner = ColdRunner(
@@ -1072,7 +1078,6 @@
     await residentRunner.run();
 
     expect(await globals.fs.file('foo').readAsString(), testUri.toString());
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
   }));
 
   test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async {
@@ -1112,6 +1117,7 @@
 
     final TestFlutterDevice flutterDevice = TestFlutterDevice(
       mockDevice,
+      <FlutterView>[],
       observatoryUris: Stream<Uri>.value(testUri),
     );
 
@@ -1147,12 +1153,15 @@
 class MockProcessManager extends Mock implements ProcessManager {}
 
 class TestFlutterDevice extends FlutterDevice {
-  TestFlutterDevice(Device device, { Stream<Uri> observatoryUris })
+  TestFlutterDevice(Device device, this.views, { Stream<Uri> observatoryUris })
     : super(device, buildInfo: BuildInfo.debug) {
     _observatoryUris = observatoryUris;
   }
 
   @override
+  final List<FlutterView> views;
+
+  @override
   Stream<Uri> get observatoryUris => _observatoryUris;
   Stream<Uri> _observatoryUris;
 }
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
index eaaa55a..7392b91 100644
--- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
+++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
@@ -36,12 +36,14 @@
 
 const List<VmServiceExpectation> kAttachLogExpectations = <VmServiceExpectation>[
   FakeVmServiceRequest(
+    id: '1',
     method: 'streamListen',
     args: <String, Object>{
       'streamId': 'Stdout',
     },
   ),
   FakeVmServiceRequest(
+    id: '2',
     method: 'streamListen',
     args: <String, Object>{
       'streamId': 'Stderr',
@@ -51,12 +53,14 @@
 
 const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectation>[
   FakeVmServiceRequest(
+    id: '3',
     method: 'streamListen',
     args: <String, Object>{
       'streamId': 'Isolate'
     }
   ),
   FakeVmServiceRequest(
+    id: '4',
     method: 'registerService',
     args: <String, Object>{
       'service': 'reloadSources',
@@ -358,6 +362,7 @@
       ...kAttachExpectations,
       const FakeVmServiceRequest(
         method: 'hotRestart',
+        id: '5',
         jsonResponse: <String, Object>{
           'type': 'Success',
         }
@@ -434,6 +439,7 @@
       ...kAttachExpectations,
       const FakeVmServiceRequest(
         method: 'hotRestart',
+        id: '5',
         jsonResponse: <String, Object>{
           'type': 'Success',
         }
@@ -664,6 +670,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'hotRestart',
         jsonResponse: <String, Object>{
           'type': 'Failed',
@@ -686,6 +693,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'hotRestart',
         // Failed response,
         errorCode: RPCErrorCodes.kInternalError,
@@ -717,6 +725,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugDumpApp',
         args: <String, Object>{
           'isolateId': null,
@@ -738,6 +747,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugDumpLayerTree',
         args: <String, Object>{
           'isolateId': null,
@@ -759,6 +769,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugDumpRenderTree',
         args: <String, Object>{
           'isolateId': null,
@@ -780,6 +791,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
         args: <String, Object>{
           'isolateId': null,
@@ -801,6 +813,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
         args: <String, Object>{
           'isolateId': null,
@@ -823,6 +836,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.debugPaint',
         args: <String, Object>{
           'isolateId': null,
@@ -832,6 +846,7 @@
         },
       ),
       const FakeVmServiceRequest(
+        id: '6',
         method: 'ext.flutter.debugPaint',
         args: <String, Object>{
           'isolateId': null,
@@ -859,6 +874,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.showPerformanceOverlay',
         args: <String, Object>{
           'isolateId': null,
@@ -868,6 +884,7 @@
         },
       ),
       const FakeVmServiceRequest(
+        id: '6',
         method: 'ext.flutter.showPerformanceOverlay',
         args: <String, Object>{
           'isolateId': null,
@@ -894,6 +911,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.inspector.show',
         args: <String, Object>{
           'isolateId': null,
@@ -903,6 +921,7 @@
         },
       ),
       const FakeVmServiceRequest(
+        id: '6',
         method: 'ext.flutter.inspector.show',
         args: <String, Object>{
           'isolateId': null,
@@ -929,6 +948,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.profileWidgetBuilds',
         args: <String, Object>{
           'isolateId': null,
@@ -938,6 +958,7 @@
         },
       ),
       const FakeVmServiceRequest(
+        id: '6',
         method: 'ext.flutter.profileWidgetBuilds',
         args: <String, Object>{
           'isolateId': null,
@@ -964,6 +985,7 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachExpectations,
       const FakeVmServiceRequest(
+        id: '5',
         method: 'ext.flutter.platformOverride',
         args: <String, Object>{
           'isolateId': null,
@@ -973,6 +995,7 @@
         },
       ),
       const FakeVmServiceRequest(
+        id: '6',
         method: 'ext.flutter.platformOverride',
         args: <String, Object>{
           'isolateId': null,
@@ -1066,12 +1089,14 @@
     fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
       ...kAttachLogExpectations,
       const FakeVmServiceRequest(
+        id: '3',
         method: 'streamListen',
         args: <String, Object>{
           'streamId': 'Isolate'
         }
       ),
       const FakeVmServiceRequest(
+        id: '4',
         method: 'registerService',
         args: <String, Object>{
           'service': 'reloadSources',
diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
index 3ed50fd..4c289b9 100644
--- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
+++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart
@@ -139,9 +139,7 @@
       final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
       when(mockResidentRunner.isRunningDebug).thenReturn(true);
       when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]);
-      when(mockResidentRunner.listFlutterViews()).thenAnswer((Invocation invocation) async {
-        return <FlutterView>[];
-      });
+      when(mockFlutterDevice.views).thenReturn(<FlutterView>[]);
 
       await terminalHandler.processTerminalInput('l');
 
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart
index 3ae4062..61007fa 100644
--- a/packages/flutter_tools/test/general.shard/vmservice_test.dart
+++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -231,10 +231,10 @@
   testWithoutContext('runInView forwards arguments correctly', () async {
     final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
       requests: <VmServiceExpectation>[
-        const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{
+        const FakeVmServiceRequest(method: 'streamListen', id: '1', args: <String, Object>{
           'streamId': 'Isolate'
         }),
-        const FakeVmServiceRequest(method: kRunInViewMethod, args: <String, Object>{
+        const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', args: <String, Object>{
           'viewId': '1234',
           'mainScript': 'main.dart',
           'assetDirectory': 'flutter_assets/',
@@ -256,65 +256,6 @@
     );
     expect(fakeVmServiceHost.hasRemainingExpectations, false);
   });
-
-  testWithoutContext('getFlutterViews polls until a view is returned', () async {
-    final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
-      requests: <VmServiceExpectation>[
-        const FakeVmServiceRequest(
-          method: kListViewsMethod,
-          jsonResponse: <String, Object>{
-            'views': <Object>[],
-          },
-        ),
-        const FakeVmServiceRequest(
-          method: kListViewsMethod,
-          jsonResponse: <String, Object>{
-            'views': <Object>[],
-          },
-        ),
-        const FakeVmServiceRequest(
-          method: kListViewsMethod,
-          jsonResponse: <String, Object>{
-            'views': <Object>[
-              <String, Object>{
-                'id': 'a',
-                'isolate': <String, Object>{},
-              },
-            ],
-          },
-        ),
-      ]
-    );
-
-    expect(
-      await fakeVmServiceHost.vmService.getFlutterViews(
-        delay: Duration.zero,
-      ),
-      isNotEmpty,
-    );
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
-  });
-
-  testWithoutContext('getFlutterViews does not poll if returnEarly is true', () async {
-    final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
-      requests: <VmServiceExpectation>[
-        const FakeVmServiceRequest(
-          method: kListViewsMethod,
-          jsonResponse: <String, Object>{
-            'views': <Object>[],
-          },
-        ),
-      ]
-    );
-
-    expect(
-      await fakeVmServiceHost.vmService.getFlutterViews(
-        returnEarly: true,
-      ),
-      isEmpty,
-    );
-    expect(fakeVmServiceHost.hasRemainingExpectations, false);
-  });
 }
 
 class MockDevice extends Mock implements Device {}
diff --git a/packages/flutter_tools/test/general.shard/web/devices_test.dart b/packages/flutter_tools/test/general.shard/web/devices_test.dart
index e5dd330..c068fd4 100644
--- a/packages/flutter_tools/test/general.shard/web/devices_test.dart
+++ b/packages/flutter_tools/test/general.shard/web/devices_test.dart
@@ -76,7 +76,7 @@
     expect(chromeDevice.supportsHotReload, true);
     expect(chromeDevice.supportsHotRestart, true);
     expect(chromeDevice.supportsStartPaused, true);
-    expect(chromeDevice.supportsFlutterExit, false);
+    expect(chromeDevice.supportsFlutterExit, true);
     expect(chromeDevice.supportsScreenshot, false);
     expect(await chromeDevice.isLocalEmulator, false);
     expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
@@ -96,7 +96,7 @@
     expect(chromeDevice.supportsHotReload, true);
     expect(chromeDevice.supportsHotRestart, true);
     expect(chromeDevice.supportsStartPaused, true);
-    expect(chromeDevice.supportsFlutterExit, false);
+    expect(chromeDevice.supportsFlutterExit, true);
     expect(chromeDevice.supportsScreenshot, false);
     expect(await chromeDevice.isLocalEmulator, false);
     expect(chromeDevice.getLogReader(), isA<NoOpDeviceLogReader>());
@@ -114,7 +114,7 @@
     expect(device.supportsHotReload, true);
     expect(device.supportsHotRestart, true);
     expect(device.supportsStartPaused, true);
-    expect(device.supportsFlutterExit, false);
+    expect(device.supportsFlutterExit, true);
     expect(device.supportsScreenshot, false);
     expect(await device.isLocalEmulator, false);
     expect(device.getLogReader(), isA<NoOpDeviceLogReader>());
diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart
index b29ea3e..c22c3a6 100644
--- a/packages/flutter_tools/test/src/common.dart
+++ b/packages/flutter_tools/test/src/common.dart
@@ -239,6 +239,7 @@
       final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest;
       expect(request, isA<Map<String, Object>>()
         .having((Map<String, Object> request) => request['method'], 'method', fakeRequest.method)
+        .having((Map<String, Object> request) => request['id'], 'id', fakeRequest.id)
         .having((Map<String, Object> request) => request['params'], 'args', fakeRequest.args)
       );
       if (fakeRequest.close) {
@@ -249,13 +250,13 @@
       if (fakeRequest.errorCode == null) {
         _input.add(json.encode(<String, Object>{
           'jsonrpc': '2.0',
-          'id': request['id'],
+          'id': fakeRequest.id,
           'result': fakeRequest.jsonResponse ?? <String, Object>{'type': 'Success'},
         }));
       } else {
         _input.add(json.encode(<String, Object>{
           'jsonrpc': '2.0',
-          'id': request['id'],
+          'id': fakeRequest.id,
           'error': <String, Object>{
             'code': fakeRequest.errorCode,
           }
@@ -298,6 +299,7 @@
 class FakeVmServiceRequest implements VmServiceExpectation {
   const FakeVmServiceRequest({
     @required this.method,
+    @required this.id,
     this.args = const <String, Object>{},
     this.jsonResponse,
     this.errorCode,
@@ -305,6 +307,7 @@
   });
 
   final String method;
+  final String id;
 
   /// When true, the vm service is automatically closed.
   final bool close;