Skip to content

Commit da43067

Browse files
authored
Preserve selected feature tab in Performance page exports (#9861)
* Preserve selected feature tab in Performance page exports The Performance page tracked the selected feature tab via `selectedFeatureTabIndex`, but it was never persisted to offline exports, so loading exported data always reset to the first tab. Mirror the Memory page's existing behavior: serialize the selected tab index into `OfflinePerformanceData` and restore it when loading offline data so the user lands on the tab they exported from. Closes #5150 * Honor and clamp the restored tab index when loading offline data Address review feedback on the offline tab restoration: - The restored selectedFeatureTabIndex was immediately reset to 0 by the default-feature activation in TabbedPerformanceView. Use the restored index (clamped to the available tabs) when activating the default feature instead of hardcoding 0. - The number of visible tabs can be smaller when loading offline data than when it was exported, so an out-of-range index could be passed to AnalyticsTabbedView. Clamp the index passed as initialSelectedIndex. - Add a round-trip test for a non-zero selectedTab. * Add release note for performance export tab fix
1 parent 2670279 commit da43067

6 files changed

Lines changed: 45 additions & 5 deletions

File tree

packages/devtools_app/lib/src/screens/performance/performance_controller.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ class PerformanceController extends DevToolsScreenController
185185
Future<void> _loadOfflineData(OfflinePerformanceData data) async {
186186
await clearData();
187187
offlinePerformanceData = data;
188+
selectedFeatureTabIndex = data.selectedTab;
188189
await _applyToFeatureControllersAsync(
189190
(c) => c.setOfflineData(offlinePerformanceData!),
190191
);
@@ -285,6 +286,7 @@ class PerformanceController extends DevToolsScreenController
285286
selectedFrame: flutterFramesController.selectedFrame.value,
286287
rebuildCountModel: rebuildCountModel,
287288
displayRefreshRate: flutterFramesController.displayRefreshRate.value,
289+
selectedTab: selectedFeatureTabIndex,
288290
).toJson(),
289291
);
290292

packages/devtools_app/lib/src/screens/performance/performance_model.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class OfflinePerformanceData {
1818
this.frames = const <FlutterFrame>[],
1919
this.selectedFrame,
2020
this.rebuildCountModel,
21+
this.selectedTab = 0,
2122
double? displayRefreshRate,
2223
}) : displayRefreshRate = displayRefreshRate ?? defaultRefreshRate;
2324

@@ -36,6 +37,7 @@ class OfflinePerformanceData {
3637
selectedFrame: selectedFrame,
3738
rebuildCountModel: json.rebuildCountModel,
3839
displayRefreshRate: json.displayRefreshRate,
40+
selectedTab: json.selectedTab,
3941
);
4042
}
4143

@@ -44,6 +46,7 @@ class OfflinePerformanceData {
4446
static const displayRefreshRateKey = 'displayRefreshRate';
4547
static const flutterFramesKey = 'flutterFrames';
4648
static const selectedFrameIdKey = 'selectedFrameId';
49+
static const selectedTabKey = 'selectedTab';
4750

4851
final Uint8List? perfettoTraceBinary;
4952

@@ -56,6 +59,12 @@ class OfflinePerformanceData {
5659

5760
final FlutterFrame? selectedFrame;
5861

62+
/// The index of the feature tab that was selected when the data was exported.
63+
///
64+
/// This is restored when loading offline data so the user lands on the same
65+
/// tab they exported from.
66+
final int selectedTab;
67+
5968
bool get isEmpty => perfettoTraceBinary == null;
6069

6170
Map<String, Object?> toJson() => {
@@ -64,6 +73,7 @@ class OfflinePerformanceData {
6473
selectedFrameIdKey: selectedFrame?.id,
6574
displayRefreshRateKey: displayRefreshRate,
6675
rebuildCountModelKey: rebuildCountModel?.toJson(),
76+
selectedTabKey: selectedTab,
6777
};
6878
}
6979

@@ -77,6 +87,9 @@ extension type _PerformanceDataJson(Map<String, Object?> json) {
7787
int? get selectedFrameId =>
7888
json[OfflinePerformanceData.selectedFrameIdKey] as int?;
7989

90+
int get selectedTab =>
91+
json[OfflinePerformanceData.selectedTabKey] as int? ?? 0;
92+
8093
List<FlutterFrame> get frames =>
8194
(json[OfflinePerformanceData.flutterFramesKey] as List? ?? [])
8295
.cast<Map>()

packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,27 @@ class _TabbedPerformanceViewState extends State<TabbedPerformanceView>
8080
.map((t) => t.featureController)
8181
.toList();
8282

83-
// If there is not an active feature, activate the first.
83+
// The set of visible tabs can differ between when offline data was exported
84+
// and when it is loaded (e.g. the Frame Analysis and Rebuild Stats tabs are
85+
// only shown for Flutter apps with the relevant data). Clamp the restored
86+
// tab index to the tabs that are actually available to avoid an
87+
// out-of-bounds selection.
88+
final selectedTabIndex = controller.selectedFeatureTabIndex.clamp(
89+
0,
90+
tabs.length - 1,
91+
);
92+
93+
// If there is not an active feature, activate the selected one.
8494
if (featureControllers.firstWhereOrNull(
8595
(controller) => controller?.isActiveFeature ?? false,
8696
) ==
8797
null) {
88-
_setActiveFeature(0, featureControllers[0]);
98+
_setActiveFeature(selectedTabIndex, featureControllers[selectedTabIndex]);
8999
}
90100

91101
return AnalyticsTabbedView(
92102
tabs: tabs,
93-
initialSelectedIndex: controller.selectedFeatureTabIndex,
103+
initialSelectedIndex: selectedTabIndex,
94104
gaScreen: gac.performance,
95105
onTabChanged: (int index) {
96106
_setActiveFeature(index, featureControllers[index]);

packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ To learn more about DevTools, check out the
2727

2828
## Performance updates
2929

30-
TODO: Remove this section if there are not any updates.
30+
* Fixed a bug where the selected feature tab was not restored when loading
31+
exported Performance data. -
32+
[#9861](https://github.com/flutter/devtools/pull/9861)
3133

3234
## CPU profiler updates
3335

packages/devtools_app/test/screens/performance/performance_model_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ void main() {
1616
expect(offlineData.selectedFrame, isNull);
1717
expect(offlineData.rebuildCountModel, isNull);
1818
expect(offlineData.displayRefreshRate, 60.0);
19+
expect(offlineData.selectedTab, 0);
1920
});
2021

2122
test('init from parse', () {
@@ -32,6 +33,7 @@ void main() {
3233
expect(offlineData.selectedFrame!.id, equals(2));
3334
expect(offlineData.displayRefreshRate, equals(60));
3435
expect(offlineData.rebuildCountModel, isNull);
36+
expect(offlineData.selectedTab, equals(0));
3537
});
3638

3739
test('to json', () {
@@ -44,12 +46,22 @@ void main() {
4446
OfflinePerformanceData.selectedFrameIdKey: null,
4547
OfflinePerformanceData.displayRefreshRateKey: 60,
4648
OfflinePerformanceData.rebuildCountModelKey: null,
49+
OfflinePerformanceData.selectedTabKey: 0,
4750
}),
4851
);
4952

5053
offlineData = OfflinePerformanceData.fromJson(rawPerformanceData);
5154
expect(offlineData.toJson(), rawPerformanceData);
5255
});
56+
57+
test('round trips a non-zero selectedTab', () {
58+
final offlineData = OfflinePerformanceData(selectedTab: 2);
59+
final json = offlineData.toJson();
60+
expect(json[OfflinePerformanceData.selectedTabKey], equals(2));
61+
62+
final parsed = OfflinePerformanceData.fromJson(json);
63+
expect(parsed.selectedTab, equals(2));
64+
});
5365
});
5466

5567
group('$FlutterTimelineEvent', () {

packages/devtools_test/lib/src/test_data/_performance_data.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108244,7 +108244,8 @@ final Map<String, Object?> samplePerformanceData = json.decode('''
108244108244
}
108245108245
],
108246108246
"displayRefreshRate": 60,
108247-
"rebuildCountModel": null
108247+
"rebuildCountModel": null,
108248+
"selectedTab": 0
108248108249
}
108249108250
}
108250108251
''');

0 commit comments

Comments
 (0)