Skip to content

Commit d1a662e

Browse files
committed
Attempt to reconnect to DTD automatically if the connection drops
Take 2
1 parent 8005ab7 commit d1a662e

File tree

20 files changed

+495
-242
lines changed

20 files changed

+495
-242
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
// Set current working directory to devtools_app.
33
"terminal.integrated.cwd": "packages/devtools_app",
44
"dart.showTodos": false,
5+
"dart.analysisExcludedFolders": [
6+
"tool/flutter-sdk"
7+
]
58
}

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"label": "Start DTD on Port 8500",
66
"detail": "Starts a DTD instance running on port 8500",
77
"type": "shell",
8-
"command": "${workspaceFolder}/../tool/flutter-sdk/bin/cache/dart-sdk/bin/dart",
8+
"command": "${workspaceFolder}/tool/flutter-sdk/bin/cache/dart-sdk/bin/dart",
99
"args": [
1010
"tooling-daemon",
1111
"--disable-service-auth-codes",

packages/devtools_app/lib/src/shared/editor/editor_client.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ class EditorClient extends DisposableController
2929

3030
String get gaId => EditorSidebar.id;
3131

32-
bool get isDtdClosed => _dtd.isClosed;
33-
3432
Future<void> _initialize() async {
3533
autoDisposeStreamSubscription(
3634
_dtd.onEvent(CoreDtdServiceConstants.servicesStreamId).listen((data) {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2025 The Flutter Authors
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
4+
5+
import 'package:devtools_app_shared/service.dart';
6+
import 'package:devtools_app_shared/ui.dart';
7+
import 'package:flutter/material.dart';
8+
9+
import '../../shared/globals.dart';
10+
import '../../shared/ui/common_widgets.dart';
11+
12+
/// An overlay to show when we are not connected to DTD based on the
13+
/// [DTDConnectionState] classes.
14+
class NotConnectedOverlay extends StatefulWidget {
15+
const NotConnectedOverlay(this.connectionState, {super.key});
16+
17+
final DTDConnectionState connectionState;
18+
19+
@override
20+
State<NotConnectedOverlay> createState() => _NotConnectedOverlayState();
21+
}
22+
23+
class _NotConnectedOverlayState extends State<NotConnectedOverlay> {
24+
@override
25+
Widget build(BuildContext context) {
26+
final connectionState = widget.connectionState;
27+
final theme = Theme.of(context);
28+
29+
final showSpinner = connectionState is! ConnectionFailedDTDState;
30+
final showReconnectButton = connectionState is ConnectionFailedDTDState;
31+
final stateLabel = switch (connectionState) {
32+
NotConnectedDTDState() => 'Waiting to connect...',
33+
ConnectingDTDState() => 'Connecting...',
34+
WaitingToRetryDTDState(seconds: final seconds) =>
35+
'Reconnecting in $seconds...',
36+
ConnectionFailedDTDState() => 'Connection Failed',
37+
// We should never present this widget when connected, but provide a label
38+
// for debugging if it happens.
39+
ConnectedDTDState() => 'Connected',
40+
};
41+
42+
return DevToolsOverlay(
43+
fullScreen: true,
44+
content: Column(
45+
mainAxisAlignment: MainAxisAlignment.center,
46+
children: [
47+
if (showSpinner) ...const [
48+
CircularProgressIndicator(),
49+
SizedBox(height: defaultSpacing),
50+
],
51+
Text(stateLabel, style: theme.textTheme.headlineMedium),
52+
if (showReconnectButton)
53+
ElevatedButton(
54+
onPressed: () => dtdManager.reconnect(),
55+
child: const Text('Retry'),
56+
),
57+
],
58+
),
59+
);
60+
}
61+
}

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_controller.dart

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,13 @@ class PropertyEditorController extends DisposableController
5555
String? get fileUri => _editableWidgetData.value?.fileUri;
5656
EditorRange? get widgetRange => _editableWidgetData.value?.range;
5757

58-
ValueListenable<bool> get shouldReconnect => _shouldReconnect;
59-
final _shouldReconnect = ValueNotifier<bool>(false);
60-
6158
bool get waitingForFirstEvent => _waitingForFirstEvent;
6259
bool _waitingForFirstEvent = true;
6360

6461
late final Debouncer _requestDebouncer;
6562

66-
late final Timer _checkConnectionTimer;
67-
6863
static const _requestDebounceDuration = Duration(milliseconds: 600);
6964

70-
static const _checkConnectionInterval = Duration(minutes: 1);
71-
7265
static const _setPropertiesFilterId = 'set-properties-filter';
7366

7467
@visibleForTesting
@@ -85,9 +78,6 @@ class PropertyEditorController extends DisposableController
8578
void init() {
8679
super.init();
8780
_requestDebouncer = Debouncer(duration: _requestDebounceDuration);
88-
_checkConnectionTimer = _periodicallyCheckConnection(
89-
_checkConnectionInterval,
90-
);
9181

9282
// Update in response to ActiveLocationChanged events.
9383
autoDisposeStreamSubscription(
@@ -134,7 +124,6 @@ class PropertyEditorController extends DisposableController
134124
@override
135125
void dispose() {
136126
_requestDebouncer.dispose();
137-
_checkConnectionTimer.cancel();
138127
super.dispose();
139128
}
140129

@@ -268,16 +257,6 @@ class PropertyEditorController extends DisposableController
268257
List<CodeActionCommand> _extractRefactors(CodeActionResult? result) =>
269258
(result?.actions ?? <CodeActionCommand>[]).toList();
270259

271-
Timer _periodicallyCheckConnection(Duration interval) {
272-
return Timer.periodic(interval, (timer) {
273-
final isClosed = editorClient.isDtdClosed;
274-
if (isClosed) {
275-
_shouldReconnect.value = true;
276-
timer.cancel();
277-
}
278-
});
279-
}
280-
281260
bool _filteredOutBySettings(
282261
EditableProperty property, {
283262
required Filter filter,

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/property_editor_panel.dart

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import '../../../shared/primitives/query_parameters.dart';
1717
import '../../../shared/ui/common_widgets.dart';
1818
import 'property_editor_controller.dart';
1919
import 'property_editor_view.dart';
20-
import 'reconnecting_overlay.dart';
2120

2221
/// The side panel for the Property Editor.
2322
class PropertyEditorPanel extends StatefulWidget {
@@ -106,43 +105,31 @@ class _PropertyEditorConnectedPanelState
106105

107106
@override
108107
Widget build(BuildContext context) {
109-
return ValueListenableBuilder<bool>(
110-
valueListenable: widget.controller.shouldReconnect,
111-
builder: (context, shouldReconnect, _) {
112-
return Stack(
113-
children: [
114-
Scrollbar(
108+
return Scrollbar(
109+
controller: scrollController,
110+
thumbVisibility: true,
111+
child: Column(
112+
children: [
113+
Expanded(
114+
child: SingleChildScrollView(
115115
controller: scrollController,
116-
thumbVisibility: true,
117-
child: Column(
118-
children: [
119-
Expanded(
120-
child: SingleChildScrollView(
121-
controller: scrollController,
122-
child: Padding(
123-
padding: const EdgeInsets.fromLTRB(
124-
denseSpacing,
125-
defaultSpacing,
126-
defaultSpacing, // Additional right padding for scroll bar.
127-
defaultSpacing,
128-
),
129-
child: Column(
130-
crossAxisAlignment: CrossAxisAlignment.start,
131-
children: [
132-
PropertyEditorView(controller: widget.controller),
133-
],
134-
),
135-
),
136-
),
137-
),
138-
const _PropertyEditorFooter(),
139-
],
116+
child: Padding(
117+
padding: const EdgeInsets.fromLTRB(
118+
denseSpacing,
119+
defaultSpacing,
120+
defaultSpacing, // Additional right padding for scroll bar.
121+
defaultSpacing,
122+
),
123+
child: Column(
124+
crossAxisAlignment: CrossAxisAlignment.start,
125+
children: [PropertyEditorView(controller: widget.controller)],
126+
),
140127
),
141128
),
142-
if (shouldReconnect) const ReconnectingOverlay(),
143-
],
144-
);
145-
},
129+
),
130+
const _PropertyEditorFooter(),
131+
],
132+
),
146133
);
147134
}
148135
}

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/reconnecting_overlay.dart

Lines changed: 0 additions & 71 deletions
This file was deleted.

packages/devtools_app/lib/src/standalone_ui/ide_shared/property_editor/utils/utils.dart

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
44

55
import 'package:flutter/widgets.dart';
6-
import '_utils_desktop.dart' if (dart.library.js_interop) '_utils_web.dart';
76

87
/// Converts a [dartDocText] String into a [Text] widget.
98
class DartDocConverter {
@@ -133,10 +132,3 @@ class DartDocConverter {
133132
return result;
134133
}
135134
}
136-
137-
/// Workaround to force reload the Property Editor when it disconnects.
138-
///
139-
/// See https://github.com/flutter/devtools/issues/9028 for details.
140-
void forceReload() {
141-
reloadIframe();
142-
}

0 commit comments

Comments
 (0)