@@ -8,6 +8,7 @@ import 'package:devtools_app_shared/service.dart';
88import 'package:devtools_app_shared/ui.dart' ;
99import 'package:devtools_app_shared/utils.dart' ;
1010import 'package:flutter/material.dart' ;
11+ import 'package:logging/logging.dart' ;
1112
1213import '../../framework/framework_core.dart' ;
1314import '../../service/connected_app/connection_info.dart' ;
@@ -18,6 +19,8 @@ import '../../shared/framework/routing.dart';
1819import '../../shared/globals.dart' ;
1920import '../../shared/primitives/query_parameters.dart' ;
2021
22+ final _log = Logger ('disconnect_observer' );
23+
2124class DisconnectObserver extends StatefulWidget {
2225 const DisconnectObserver ({
2326 super .key,
@@ -131,6 +134,40 @@ class DisconnectObserverState extends State<DisconnectObserver>
131134 widget.routerDelegate.navigate (snapshotScreenId, args);
132135 }
133136
137+ Widget _buildReconnectActions (ThemeData theme) {
138+ final reconnectButton = ElevatedButton (
139+ onPressed: _attemptReconnect,
140+ child: const Text ('Reconnect' ),
141+ );
142+
143+ if (! isEmbedded ()) {
144+ return Row (
145+ mainAxisAlignment: MainAxisAlignment .center,
146+ children: [
147+ reconnectButton,
148+ const SizedBox (width: defaultSpacing),
149+ ConnectToNewAppButton (
150+ routerDelegate: widget.routerDelegate,
151+ onPressed: hideDisconnectedOverlay,
152+ gaScreen: gac.devToolsMain,
153+ ),
154+ ],
155+ );
156+ }
157+
158+ return Column (
159+ mainAxisSize: MainAxisSize .min,
160+ children: [
161+ reconnectButton,
162+ const SizedBox (height: defaultSpacing),
163+ Text (
164+ 'Or run a new debug session to connect to it.' ,
165+ style: theme.textTheme.bodyMedium,
166+ ),
167+ ],
168+ );
169+ }
170+
134171 OverlayEntry _createDisconnectedOverlay () {
135172 final theme = Theme .of (context);
136173 currentDisconnectedOverlay = OverlayEntry (
@@ -153,37 +190,8 @@ class DisconnectObserverState extends State<DisconnectObserver>
153190 const SizedBox (height: defaultSpacing),
154191 if (isReconnecting)
155192 const CircularProgressIndicator ()
156- else if (! isEmbedded ())
157- Row (
158- mainAxisAlignment: MainAxisAlignment .center,
159- children: [
160- ElevatedButton (
161- onPressed: _attemptReconnect,
162- child: const Text ('Reconnect' ),
163- ),
164- const SizedBox (width: defaultSpacing),
165- ConnectToNewAppButton (
166- routerDelegate: widget.routerDelegate,
167- onPressed: hideDisconnectedOverlay,
168- gaScreen: gac.devToolsMain,
169- ),
170- ],
171- )
172193 else
173- Column (
174- mainAxisSize: MainAxisSize .min,
175- children: [
176- ElevatedButton (
177- onPressed: _attemptReconnect,
178- child: const Text ('Reconnect' ),
179- ),
180- const SizedBox (height: defaultSpacing),
181- Text (
182- 'Or run a new debug session to connect to it.' ,
183- style: theme.textTheme.bodyMedium,
184- ),
185- ],
186- ),
194+ _buildReconnectActions (theme),
187195 if (reconnectErrorText case final error? ) ...[
188196 const SizedBox (height: denseSpacing),
189197 Text (
@@ -219,48 +227,42 @@ class DisconnectObserverState extends State<DisconnectObserver>
219227 _isReconnecting.value = true ;
220228 _reconnectErrorText.value = null ;
221229
222- var reconnectionSuccess = false ;
223-
224230 try {
225231 await dtdManager.reconnect ();
232+ } catch (error, stackTrace) {
233+ _log.warning ('Failed to reconnect DTD.' , error, stackTrace);
234+ }
226235
227- final uri = _lastVmServiceUri;
228- if (uri != null &&
229- ! serviceConnection.serviceManager.connectedState.value.connected) {
230- // Call initVmService directly — do NOT use routerDelegate.navigate()
231- // because that goes through _replaceStack which calls manuallyDisconnect
232- // when clearing the URI, causing the disconnect observer to suppress
233- // the overlay (userInitiatedConnectionState = true).
234- reconnectionSuccess = await FrameworkCore .initVmService (
235- serviceUriAsString: uri,
236- logException: false ,
237- errorReporter: (title, error) {
238- _reconnectErrorText.value = '$title , $error ' ;
239- },
240- );
241- } else {
242- reconnectionSuccess =
243- serviceConnection.serviceManager.connectedState.value.connected;
244- }
245- } catch (e) {
246- _reconnectErrorText.value = e.toString ();
247- } finally {
248- _isReconnecting.value = false ;
249-
250- if (reconnectionSuccess ||
251- serviceConnection.serviceManager.connectedState.value.connected) {
252- // Success — also update the router so the URI is reflected in the URL.
253- unawaited (
254- widget.routerDelegate.updateArgsIfChanged ({
255- DevToolsQueryParams .vmServiceUriKey: _lastVmServiceUri,
256- }),
257- );
258- _reconnectErrorText.value = null ;
259- hideDisconnectedOverlay ();
260- } else {
261- // Failed (stale URI, VM dead, etc.) — restore the overlay with buttons.
262- showDisconnectedOverlay ();
263- }
236+ var reconnectionSuccess =
237+ serviceConnection.serviceManager.connectedState.value.connected;
238+
239+ final uri = _lastVmServiceUri;
240+ if (! reconnectionSuccess && uri != null ) {
241+ // Call initVmService directly — do NOT use routerDelegate.navigate()
242+ // because that goes through _replaceStack which calls manuallyDisconnect
243+ // when clearing the URI, causing the disconnect observer to suppress
244+ // the overlay (userInitiatedConnectionState = true).
245+ reconnectionSuccess = await FrameworkCore .initVmService (
246+ serviceUriAsString: uri,
247+ logException: false ,
248+ errorReporter: (title, error) {
249+ _reconnectErrorText.value = '$title , $error ' ;
250+ },
251+ );
252+ }
253+
254+ _isReconnecting.value = false ;
255+
256+ if (reconnectionSuccess) {
257+ unawaited (
258+ widget.routerDelegate.updateArgsIfChanged ({
259+ DevToolsQueryParams .vmServiceUriKey: _lastVmServiceUri,
260+ }),
261+ );
262+ _reconnectErrorText.value = null ;
263+ hideDisconnectedOverlay ();
264+ } else {
265+ showDisconnectedOverlay ();
264266 }
265267 }
266268}
0 commit comments