Skip to content

Commit ae23b85

Browse files
bkonyiCoderDake
authored andcommitted
Add initial support for non-query param navigation state (#4837)
This change adds support for providing additional state when navigating through DevTools via `DevToolsRouterDelegate` without polluting the query parameters with extra entries. This state is included in the navigation history. Controllers can mixin the new `RouteStateHandlerMixin` and override `onRouteStateUpdate(DevToolsNavigationState)` to handle changes in the current DevTools navigation state. To handle state changes, `subscribeToRouterEvents` must first be called on the controller. This change also adds support for jumping to a source location in the debugger from URIs in the CPU profiler.
1 parent 428b04f commit ae23b85

File tree

17 files changed

+545
-119
lines changed

17 files changed

+545
-119
lines changed

packages/devtools_app/lib/src/app.dart

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
9797

9898
late ReleaseNotesController releaseNotesController;
9999

100+
late final routerDelegate = DevToolsRouterDelegate(_getPage);
101+
100102
@override
101103
void initState() {
102104
super.initState();
@@ -148,13 +150,19 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
148150
}
149151

150152
/// Gets the page for a given page/path and args.
151-
Page _getPage(BuildContext context, String? page, Map<String, String?> args) {
153+
Page _getPage(
154+
BuildContext context,
155+
String? page,
156+
Map<String, String?> args,
157+
DevToolsNavigationState? state,
158+
) {
152159
// Provide the appropriate page route.
153160
if (pages.containsKey(page)) {
154161
Widget widget = pages[page!]!(
155162
context,
156163
page,
157164
args,
165+
state,
158166
);
159167
assert(
160168
() {
@@ -163,6 +171,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
163171
context,
164172
page,
165173
args,
174+
state,
166175
),
167176
);
168177
return true;
@@ -185,6 +194,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
185194
BuildContext context,
186195
String? page,
187196
Map<String, String?> params,
197+
DevToolsNavigationState? state,
188198
) {
189199
final vmServiceUri = params['uri'];
190200

@@ -262,7 +272,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
262272
homePageId: _buildTabbedPage,
263273
for (final screen in widget.screens)
264274
screen.screen.screenId: _buildTabbedPage,
265-
snapshotPageId: (_, __, args) {
275+
snapshotPageId: (_, __, args, ___) {
266276
final snapshotArgs = SnapshotArguments.fromArgs(args);
267277
return DevToolsScaffold.withChild(
268278
key: UniqueKey(),
@@ -273,7 +283,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
273283
),
274284
);
275285
},
276-
appSizePageId: (_, __, ___) {
286+
appSizePageId: (_, __, ___, ____) {
277287
return DevToolsScaffold.withChild(
278288
key: const Key('appsize'),
279289
ideTheme: ideTheme,
@@ -303,7 +313,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
303313
.where(
304314
(s) => s.providesController && (offline ? s.supportsOffline : true),
305315
)
306-
.map((s) => s.controllerProvider)
316+
.map((s) => s.controllerProvider(routerDelegate))
307317
.toList();
308318

309319
return MultiProvider(
@@ -341,7 +351,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
341351
),
342352
);
343353
},
344-
routerDelegate: DevToolsRouterDelegate(_getPage),
354+
routerDelegate: routerDelegate,
345355
routeInformationParser: DevToolsRouteInformationParser(),
346356
// Disable default scrollbar behavior on web to fix duplicate scrollbars
347357
// bug, see https://github.com/flutter/flutter/issues/90697:
@@ -374,7 +384,7 @@ class DevToolsScreen<C> {
374384
///
375385
/// If [createController] and [controller] are both null, [screen] will be
376386
/// responsible for creating and maintaining its own controller.
377-
final C Function()? createController;
387+
final C Function(DevToolsRouterDelegate)? createController;
378388

379389
/// A provided controller for this screen, if non-null.
380390
///
@@ -395,7 +405,7 @@ class DevToolsScreen<C> {
395405
/// Defaults to false.
396406
final bool supportsOffline;
397407

398-
Provider<C> get controllerProvider {
408+
Provider<C> controllerProvider(DevToolsRouterDelegate routerDelegate) {
399409
assert(
400410
(createController != null && controller == null) ||
401411
(createController == null && controller != null),
@@ -404,16 +414,17 @@ class DevToolsScreen<C> {
404414
if (controllerLocal != null) {
405415
return Provider<C>.value(value: controllerLocal);
406416
}
407-
return Provider<C>(create: (_) => createController!());
417+
return Provider<C>(create: (_) => createController!(routerDelegate));
408418
}
409419
}
410420

411421
/// A [WidgetBuilder] that takes an additional map of URL query parameters and
412-
/// args.
422+
/// args, as well a state not included in the URL.
413423
typedef UrlParametersBuilder = Widget Function(
414424
BuildContext,
415425
String?,
416426
Map<String, String?>,
427+
DevToolsNavigationState?,
417428
);
418429

419430
/// Displays the checked mode banner in the bottom end corner instead of the
@@ -568,52 +579,54 @@ List<DevToolsScreen> get defaultScreens {
568579
return <DevToolsScreen>[
569580
DevToolsScreen<InspectorController>(
570581
const InspectorScreen(),
571-
createController: () => InspectorController(
582+
createController: (_) => InspectorController(
572583
inspectorTree: InspectorTreeController(),
573584
detailsTree: InspectorTreeController(),
574585
treeType: FlutterTreeType.widget,
575586
),
576587
),
577588
DevToolsScreen<PerformanceController>(
578589
const PerformanceScreen(),
579-
createController: () => PerformanceController(),
590+
createController: (_) => PerformanceController(),
580591
supportsOffline: true,
581592
),
582593
DevToolsScreen<ProfilerScreenController>(
583594
const ProfilerScreen(),
584-
createController: () => ProfilerScreenController(),
595+
createController: (_) => ProfilerScreenController(),
585596
supportsOffline: true,
586597
),
587598
DevToolsScreen<MemoryController>(
588599
const MemoryScreen(),
589-
createController: () => MemoryController(),
600+
createController: (_) => MemoryController(),
590601
),
591602
DevToolsScreen<DebuggerController>(
592603
const DebuggerScreen(),
593-
createController: () => DebuggerController(),
604+
createController: (routerDelegate) => DebuggerController(
605+
routerDelegate: routerDelegate,
606+
),
594607
),
595608
DevToolsScreen<NetworkController>(
596609
const NetworkScreen(),
597-
createController: () => NetworkController(),
610+
createController: (_) => NetworkController(),
598611
),
599612
DevToolsScreen<LoggingController>(
600613
const LoggingScreen(),
601-
createController: () => LoggingController(),
614+
createController: (_) => LoggingController(),
602615
),
603-
DevToolsScreen<void>(const ProviderScreen(), createController: () {}),
616+
DevToolsScreen<void>(const ProviderScreen(), createController: (_) {}),
604617
DevToolsScreen<AppSizeController>(
605618
const AppSizeScreen(),
606-
createController: () => AppSizeController(),
619+
createController: (_) => AppSizeController(),
607620
),
608621
DevToolsScreen<VMDeveloperToolsController>(
609622
const VMDeveloperToolsScreen(),
610-
createController: () => VMDeveloperToolsController(),
623+
createController: (_) => VMDeveloperToolsController(),
611624
),
612625
// Show the sample DevTools screen.
613626
if (debugEnableSampleScreen && (kDebugMode || kProfileMode))
614627
DevToolsScreen<ExampleController>(
615628
const ExampleConditionalScreen(),
616-
createController: () => ExampleController(),
629+
createController: (_) => ExampleController(),
617630
supportsOffline: true,
618631
),
619632
];

packages/devtools_app/lib/src/framework/scaffold.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
227227
routerDelegate.navigateIfNotCurrent(
228228
_currentScreen.screenId,
229229
routerDelegate.currentConfiguration?.args,
230+
routerDelegate.currentConfiguration?.state,
230231
);
231232
});
232233
});

packages/devtools_app/lib/src/screens/debugger/codeview.dart

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import 'debugger_controller.dart';
3333
import 'debugger_model.dart';
3434
import 'file_search.dart';
3535
import 'key_sets.dart';
36-
import 'program_explorer_model.dart';
3736
import 'variables.dart';
3837

3938
final debuggerCodeViewSearchKey =
@@ -168,56 +167,65 @@ class _CodeViewState extends State<CodeView>
168167
return;
169168
}
170169

171-
if (!verticalController.hasAttachedControllers) {
172-
// TODO(devoncarew): I'm uncertain why this occurs.
173-
log('LinkedScrollControllerGroup has no attached controllers');
174-
return;
175-
}
176-
final line = widget.codeViewController.scriptLocation.value?.location?.line;
177-
if (line == null) {
178-
// Don't scroll to top if we're just rebuilding the code view for the
179-
// same script.
180-
if (_lastScriptRef?.uri != scriptRef?.uri) {
181-
// Default to scrolling to the top of the script.
170+
void updateScrollPositionImpl() {
171+
if (!verticalController.hasAttachedControllers) {
172+
// TODO(devoncarew): I'm uncertain why this occurs.
173+
log('LinkedScrollControllerGroup has no attached controllers');
174+
return;
175+
}
176+
final line =
177+
widget.codeViewController.scriptLocation.value?.location?.line;
178+
if (line == null) {
179+
// Don't scroll to top if we're just rebuilding the code view for the
180+
// same script.
181+
if (_lastScriptRef?.uri != scriptRef?.uri) {
182+
// Default to scrolling to the top of the script.
183+
if (animate) {
184+
unawaited(
185+
verticalController.animateTo(
186+
0,
187+
duration: longDuration,
188+
curve: defaultCurve,
189+
),
190+
);
191+
} else {
192+
verticalController.jumpTo(0);
193+
}
194+
_lastScriptRef = scriptRef;
195+
}
196+
return;
197+
}
198+
199+
final position = verticalController.position;
200+
final extent = position.extentInside;
201+
202+
// TODO(devoncarew): Adjust this so we don't scroll if we're already in the
203+
// middle third of the screen.
204+
final lineCount = parsedScript?.lineCount;
205+
if (lineCount != null && lineCount * CodeView.rowHeight > extent) {
206+
final lineIndex = line - 1;
207+
final scrollPosition = lineIndex * CodeView.rowHeight -
208+
((extent - CodeView.rowHeight) / 2);
182209
if (animate) {
183210
unawaited(
184211
verticalController.animateTo(
185-
0,
212+
scrollPosition,
186213
duration: longDuration,
187214
curve: defaultCurve,
188215
),
189216
);
190217
} else {
191-
verticalController.jumpTo(0);
218+
verticalController.jumpTo(scrollPosition);
192219
}
193-
_lastScriptRef = scriptRef;
194220
}
195-
return;
221+
_lastScriptRef = scriptRef;
196222
}
197223

198-
final position = verticalController.position;
199-
final extent = position.extentInside;
200-
201-
// TODO(devoncarew): Adjust this so we don't scroll if we're already in the
202-
// middle third of the screen.
203-
final lineCount = parsedScript?.lineCount;
204-
if (lineCount != null && lineCount * CodeView.rowHeight > extent) {
205-
final lineIndex = line - 1;
206-
final scrollPosition =
207-
lineIndex * CodeView.rowHeight - ((extent - CodeView.rowHeight) / 2);
208-
if (animate) {
209-
unawaited(
210-
verticalController.animateTo(
211-
scrollPosition,
212-
duration: longDuration,
213-
curve: defaultCurve,
214-
),
215-
);
216-
} else {
217-
verticalController.jumpTo(scrollPosition);
218-
}
219-
}
220-
_lastScriptRef = scriptRef;
224+
verticalController.hasAttachedControllers
225+
? updateScrollPositionImpl()
226+
: WidgetsBinding.instance.addPostFrameCallback(
227+
(_) => updateScrollPositionImpl(),
228+
);
221229
}
222230

223231
void _onPressed(int line) {
@@ -785,12 +793,10 @@ class _LinesState extends State<Lines> with AutoDisposeMixin {
785793
itemBuilder: (context, index) {
786794
final lineNum = index + 1;
787795
final isPausedLine = pausedLine == lineNum;
788-
return ValueListenableBuilder<VMServiceObjectNode?>(
789-
valueListenable: widget
790-
.codeViewController.programExplorerController.outlineSelection,
791-
builder: (context, outlineNode, _) {
792-
final isFocusedLine =
793-
(outlineNode?.location?.location?.line ?? -1) == lineNum;
796+
return ValueListenableBuilder<int>(
797+
valueListenable: widget.codeViewController.focusLine,
798+
builder: (context, focusLine, _) {
799+
final isFocusedLine = focusLine == lineNum;
794800
return LineItem(
795801
lineContents: widget.lines[index],
796802
pausedFrame: isPausedLine ? widget.pausedFrame : null,

0 commit comments

Comments
 (0)