Hide "Disconnected" overlay if DevTools is reconnected to a VM (#2099)

* Hide "Disconnected" overlay if DevTools is reconnected to a VM

* Add subscription to autoDispose
diff --git a/packages/devtools_app/lib/src/initializer.dart b/packages/devtools_app/lib/src/initializer.dart
index 6063282..e69a6c7 100644
--- a/packages/devtools_app/lib/src/initializer.dart
+++ b/packages/devtools_app/lib/src/initializer.dart
@@ -60,6 +60,9 @@
 
   bool _dependenciesLoaded = false;
 
+  OverlayEntry currentDisconnectedOverlay;
+  StreamSubscription<bool> disconnectedOverlayReconnectSubscription;
+
   @override
   void initState() {
     super.initState();
@@ -113,16 +116,30 @@
   /// Shows a "disconnected" overlay if the [service.serviceManager] is not currently connected.
   void _handleNoConnection() {
     WidgetsBinding.instance.addPostFrameCallback((_) {
-      if (!_checkLoaded() && ModalRoute.of(context).isCurrent) {
+      if (!_checkLoaded() &&
+          ModalRoute.of(context).isCurrent &&
+          currentDisconnectedOverlay == null) {
         Overlay.of(context).insert(_createDisconnectedOverlay());
+
+        // Set up a subscription to hide the overlay if we become reconnected.
+        disconnectedOverlayReconnectSubscription = serviceManager.onStateChange
+            .where((connected) => connected)
+            .listen((_) => hideDisconnectedOverlay());
+        autoDispose(disconnectedOverlayReconnectSubscription);
       }
     });
   }
 
+  void hideDisconnectedOverlay() {
+    currentDisconnectedOverlay?.remove();
+    currentDisconnectedOverlay = null;
+    disconnectedOverlayReconnectSubscription?.cancel();
+    disconnectedOverlayReconnectSubscription = null;
+  }
+
   OverlayEntry _createDisconnectedOverlay() {
     final theme = Theme.of(context);
-    OverlayEntry overlay;
-    overlay = OverlayEntry(
+    currentDisconnectedOverlay = OverlayEntry(
       builder: (context) => Container(
         // TODO(dantup): Change this to a theme colour and ensure it works in both dart/light themes
         color: const Color.fromRGBO(128, 128, 128, 0.5),
@@ -134,7 +151,7 @@
               if (widget.allowConnectionScreenOnDisconnect)
                 RaisedButton(
                     onPressed: () {
-                      overlay.remove();
+                      hideDisconnectedOverlay();
                       Navigator.of(context).popAndPushNamed(homeRoute);
                     },
                     child: const Text('Connect to Another App'))
@@ -145,9 +162,7 @@
                 ),
               const Spacer(),
               RaisedButton(
-                onPressed: () {
-                  overlay.remove();
-                },
+                onPressed: hideDisconnectedOverlay,
                 child: const Text('Review History'),
               ),
             ],
@@ -155,7 +170,7 @@
         ),
       ),
     );
-    return overlay;
+    return currentDisconnectedOverlay;
   }
 
   @override
diff --git a/packages/devtools_app/test/initializer_test.dart b/packages/devtools_app/test/initializer_test.dart
index 70ab10c..bd9b67d 100644
--- a/packages/devtools_app/test/initializer_test.dart
+++ b/packages/devtools_app/test/initializer_test.dart
@@ -67,6 +67,25 @@
       expect(find.text('Disconnected'), findsOneWidget);
     });
 
+    testWidgets('closes disconnected overlay upon reconnect',
+        (WidgetTester tester) async {
+      final serviceManager = FakeServiceManager(useFakeService: true);
+      setGlobal(ServiceConnectionManager, serviceManager);
+
+      // Trigger a disconnect and ensure the overlay appears.
+      await tester.pumpFrames(app, const Duration(milliseconds: 100));
+      serviceManager.changeState(false);
+      await tester.pumpFrames(app, const Duration(milliseconds: 100));
+      expect(find.text('Disconnected'), findsOneWidget);
+
+      // Trigger a reconnect
+      serviceManager.changeState(true);
+
+      // Expect no overlay.
+      await tester.pumpFrames(app, const Duration(milliseconds: 100));
+      expect(find.text('Disconnected'), findsNothing);
+    });
+
     testWidgets('builds contents when initialized',
         (WidgetTester tester) async {
       await tester.pumpWidget(app);
diff --git a/packages/devtools_app/test/support/mocks.dart b/packages/devtools_app/test/support/mocks.dart
index 06826e3..715baf5 100644
--- a/packages/devtools_app/test/support/mocks.dart
+++ b/packages/devtools_app/test/support/mocks.dart
@@ -110,7 +110,7 @@
   @override
   Stream<bool> get onStateChange => stateChangeStream.stream;
 
-  StreamController<bool> stateChangeStream = StreamController();
+  StreamController<bool> stateChangeStream = StreamController.broadcast();
 
   void changeState(bool value) {
     hasConnection = value;