New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Inspector widget selection improvements (#3489) #3525
Inspector widget selection improvements (#3489) #3525
Conversation
…#3489) * Add a method for finding a path from currently selected row item to the tree root (flutter#3489) * Add InspectorBreadcrumbNavigator (flutter#3489) * Set text style explicitly (flutter#3489) * Notify controller about selected widget on tap (flutter#3489) * Make currently selected item not clickable (flutter#3489) * Move breadcrumbs out of scrollable tree container (flutter#3489) * Tweak padding size and appearance parameters (flutter#3489) * Scroll to the end of the list so the last breadcrumb is always visible (flutter#3489) * Simplify constructor (flutter#3489) * Provide inspector tree controller directly to the tree widget where needed (flutter#3489) * Provide dummy inspectorTreeController to tests in inspector_tree_test (flutter#3489) * Ensure inspectorTreeController is provided when InspectorTree is showing a widget detail tree (flutter#3489) * Test if breadcrumbs widget is displayed (flutter#3489) * Remove empty constructor (flutter#3489) * Use row for showing breadcrumbs (flutter#3489)
Disable Details tree search & Highlight search matches with orange color & breadcrumbs
…instance_viewer_test.dart (flutter#3489)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with first round of comments. Generally, we should be trying to use the search functionality in SearchControllerMixin where possible instead of trying to manage matches and match indexes manually. Happy to discuss further or chat offline for help with hooking the inspector up to the existing search functionality in DevTools.
@@ -35,18 +35,24 @@ class DiagnosticsNodeDescription extends StatelessWidget { | |||
const DiagnosticsNodeDescription( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cc @matt-ragonese in case you are using this file for your custom screen
// Reset search matches | ||
for (final row in cachedRows) { | ||
_statsWidgets++; | ||
setSearchMatch(row.node, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we shouldn't need to do this. SearchControllerMixin already resets the isSearchMatch field in _updateMatches
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setSearchMatch
does two things: it expands current node and it marks a node
(not row) as search match.
InspectorTree
uses getRow()
to get row data which is different from what's returned from getCachedRow()
. _updateMatches()
marks a row as search match but this row is a cached row so isSearchMatch
is not reflecting the actual status for rows returned by getRow()
.
I have the following questions regarding this:
- can cached rows be used in
InspectorTree
? - what would be a good way to expand a node if
setSearchMatch()
is removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple questsions:
- Why do we need to mark both the row and the node as having a search match? as far as I can tell, everywhere
hasSearchMatch
is used, we have access to the row, so we could just writerow.isSearchMatch
instead. - Why are we expanding nodes at all for search?
If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch
on cached rows and not on rows returned by getRow()
- is that correct? If so, can we do a comparison where ever we need to call getRow()
and need to know the value of isSearchMatch
to check if there is a matching cached row with isSearchMatch
set to true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to mark both the row and the node as having a search match?
It was made as a work around because isSearchMatch
is set on cached rows but different instances of rows are used when displaying the tree.
Why are we expanding nodes at all for search?
Search is looking for matches in cachedRows
. In order to have all rows in cachedRows
list all nodes need to be expanded.
If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch on cached rows and not on rows returned by getRow() - is that correct?
Yes, that's correct.
can we do a comparison where ever we need to call getRow() and need to know the value of isSearchMatch
Yes. We need to know the value of isSearchMatch
in _InspectorTreeState
but getRows()
is used there.
I've made some changes to the code:
setSearchMatch()
andhasSearchMatch
have been removedInspectorTree
now usesgetCachedRow()
. It should be okay to use it since it may still callroot?.getRow()
under the hood.
There's still need to expand the tree to get all rows cached. I did this by expanding all nodes of the tree in the overridden set search
setter (so nodes are expanded before submitting search query). I don't think it's ideal, maybe it's better to find a solution that keeps current state of collapsed and expanded nodes.
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
_statsSearchOps++; | ||
if (searchValue.toLowerCase().contains(caseInsensitiveSearch)) { | ||
matches.add(row); | ||
setSearchMatch(row.node, true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment as above. SearchControllerMixin._updateMatches should already handle this
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_breadcrumbs.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_breadcrumbs.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_breadcrumbs.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
final pathToRoot = <InspectorTreeRow>[selectedItem]; | ||
InspectorTreeNode nextParentNode = selectedItem.node.parent; | ||
while (nextParentNode != null) { | ||
final rowItem = cachedRows.firstWhere( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, but then wouldn't the path to root be incomplete, meaning the breadcrumb navigator would be incomplete and not show the root node?
// Reset search matches | ||
for (final row in cachedRows) { | ||
_statsWidgets++; | ||
setSearchMatch(row.node, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple questsions:
- Why do we need to mark both the row and the node as having a search match? as far as I can tell, everywhere
hasSearchMatch
is used, we have access to the row, so we could just writerow.isSearchMatch
instead. - Why are we expanding nodes at all for search?
If I understand correctly, the issue with cached rows vs non-cached rows is that we are setting isSearchMatch
on cached rows and not on rows returned by getRow()
- is that correct? If so, can we do a comparison where ever we need to call getRow()
and need to know the value of isSearchMatch
to check if there is a matching cached row with isSearchMatch
set to true?
@override | ||
set search(String value) { | ||
// Expand tree so all rows are available for search | ||
_expandAll(root); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of overriding search to expand the tree. Why don't we create a copy of the tree at the time of tree creation. This copy can be fully expanded from the start, which has the advantages that A) we won't have to modify the expand/collapse state of the original tree, and B) we won't have to pay the cost of expanding the tree each time we perform a search.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a copy leads to a previous issue when actual\cached rows and rows from the copy are not the same instances so isSearchMatch
doesn't reflect the actual value. Anyway I tried using a copy of the tree for search and finding a path to the root for breadcrumbs. isSearchMatch
was made to show the actual value because getCachedRow()
sets the value to the cached row before returning it.
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
final rowItem = cachedRows.firstWhere( | ||
(element) => element.node == nextParentNode, | ||
orElse: null, | ||
final rowItem = _searchableCachedRows.firstWhere( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have you tried this with a very large app? The worst case scenario of this is that you are trying to get the path from the deepest leaf node to the root. Where N is the depth of the leaf and M is the total number of nodes in the tree, this could be NxM, which could be very slow for a large app.
seems like it would be more performant to either A) make rows aware of their parent rows so that row.parent
would yield the next parent, or B) to give Node's access to their associated row, so that row.node.parent.row
would yield the next parent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Turns out it's enough to have an instance of InspectorTreeNode
for the selected row to find a way to the root. Such instance is available from an instance of InspectorTreeRow
and _cachedSelectedRow
in particular. This method now uses parent of the selected row.node
to find a path to the root. Should be more performant now as it doesn't iterate through the whole tree to find the right item on every iteration of the while
loop.
@@ -184,6 +185,10 @@ class InspectorTreeController extends Object | |||
final List<InspectorTreeRow> cachedRows = []; | |||
InspectorTreeRow _cachedSelectedRow; | |||
|
|||
/// Cached rows of the tree. Similar to [cachedRows] but items are populated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dart-doc nit: there should be a blank new line between the first sentence and the rest.
Also just to clarify, _searchableCachedRows
contains every row in the tree, is that correct? not just those that are visible (nodes with expanded parents)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it contains every row in the tree (including collapsed nodes, children of collapsed nodes), it's now reflected in the doc
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
…ify path to the root calculation (flutter#3489)
# Conflicts: # packages/devtools_app/lib/src/common_widgets.dart # packages/devtools_app/lib/src/network/network_screen.dart # packages/devtools_app/test/goldens/timeline_flame_chart_with_selected_frame.png
packages/devtools_app/lib/src/inspector/inspector_breadcrumbs.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/inspector/inspector_tree_controller.dart
Outdated
Show resolved
Hide resolved
I'm having difficulties figuring out why goldens tests fail. I'm unable to reproduce it or update golden files locally when using Flutter master or stable. As far as I understand, devtools uses specific version of SDK in CI/CD ( |
The goldens and other tests seem to be okay now |
I just checked this out locally and played with it against a large application to verify the performance is acceptable. It looks fantastic - great work! Here is one polish item that should be done as a follow up: #3561 |
Thanks for thoroughly reviewing this PR! I'll have a look at the issue |
Original ticket: #3489
Changes in this pull request: