diff --git a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart index 8bed01cc548..591e86a46aa 100644 --- a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart +++ b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart @@ -185,6 +185,7 @@ class PerformanceController extends DevToolsScreenController Future _loadOfflineData(OfflinePerformanceData data) async { await clearData(); offlinePerformanceData = data; + selectedFeatureTabIndex = data.selectedTab; await _applyToFeatureControllersAsync( (c) => c.setOfflineData(offlinePerformanceData!), ); @@ -285,6 +286,7 @@ class PerformanceController extends DevToolsScreenController selectedFrame: flutterFramesController.selectedFrame.value, rebuildCountModel: rebuildCountModel, displayRefreshRate: flutterFramesController.displayRefreshRate.value, + selectedTab: selectedFeatureTabIndex, ).toJson(), ); diff --git a/packages/devtools_app/lib/src/screens/performance/performance_model.dart b/packages/devtools_app/lib/src/screens/performance/performance_model.dart index 637eae39969..a965879822c 100644 --- a/packages/devtools_app/lib/src/screens/performance/performance_model.dart +++ b/packages/devtools_app/lib/src/screens/performance/performance_model.dart @@ -18,6 +18,7 @@ class OfflinePerformanceData { this.frames = const [], this.selectedFrame, this.rebuildCountModel, + this.selectedTab = 0, double? displayRefreshRate, }) : displayRefreshRate = displayRefreshRate ?? defaultRefreshRate; @@ -36,6 +37,7 @@ class OfflinePerformanceData { selectedFrame: selectedFrame, rebuildCountModel: json.rebuildCountModel, displayRefreshRate: json.displayRefreshRate, + selectedTab: json.selectedTab, ); } @@ -44,6 +46,7 @@ class OfflinePerformanceData { static const displayRefreshRateKey = 'displayRefreshRate'; static const flutterFramesKey = 'flutterFrames'; static const selectedFrameIdKey = 'selectedFrameId'; + static const selectedTabKey = 'selectedTab'; final Uint8List? perfettoTraceBinary; @@ -56,6 +59,12 @@ class OfflinePerformanceData { final FlutterFrame? selectedFrame; + /// The index of the feature tab that was selected when the data was exported. + /// + /// This is restored when loading offline data so the user lands on the same + /// tab they exported from. + final int selectedTab; + bool get isEmpty => perfettoTraceBinary == null; Map toJson() => { @@ -64,6 +73,7 @@ class OfflinePerformanceData { selectedFrameIdKey: selectedFrame?.id, displayRefreshRateKey: displayRefreshRate, rebuildCountModelKey: rebuildCountModel?.toJson(), + selectedTabKey: selectedTab, }; } @@ -77,6 +87,9 @@ extension type _PerformanceDataJson(Map json) { int? get selectedFrameId => json[OfflinePerformanceData.selectedFrameIdKey] as int?; + int get selectedTab => + json[OfflinePerformanceData.selectedTabKey] as int? ?? 0; + List get frames => (json[OfflinePerformanceData.flutterFramesKey] as List? ?? []) .cast() diff --git a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart index 05eef54cd1c..52096fe6517 100644 --- a/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart +++ b/packages/devtools_app/lib/src/screens/performance/tabbed_performance_view.dart @@ -80,17 +80,27 @@ class _TabbedPerformanceViewState extends State .map((t) => t.featureController) .toList(); - // If there is not an active feature, activate the first. + // The set of visible tabs can differ between when offline data was exported + // and when it is loaded (e.g. the Frame Analysis and Rebuild Stats tabs are + // only shown for Flutter apps with the relevant data). Clamp the restored + // tab index to the tabs that are actually available to avoid an + // out-of-bounds selection. + final selectedTabIndex = controller.selectedFeatureTabIndex.clamp( + 0, + tabs.length - 1, + ); + + // If there is not an active feature, activate the selected one. if (featureControllers.firstWhereOrNull( (controller) => controller?.isActiveFeature ?? false, ) == null) { - _setActiveFeature(0, featureControllers[0]); + _setActiveFeature(selectedTabIndex, featureControllers[selectedTabIndex]); } return AnalyticsTabbedView( tabs: tabs, - initialSelectedIndex: controller.selectedFeatureTabIndex, + initialSelectedIndex: selectedTabIndex, gaScreen: gac.performance, onTabChanged: (int index) { _setActiveFeature(index, featureControllers[index]); diff --git a/packages/devtools_app/test/screens/performance/performance_model_test.dart b/packages/devtools_app/test/screens/performance/performance_model_test.dart index 84f1f061a98..7dade0fd72f 100644 --- a/packages/devtools_app/test/screens/performance/performance_model_test.dart +++ b/packages/devtools_app/test/screens/performance/performance_model_test.dart @@ -16,6 +16,7 @@ void main() { expect(offlineData.selectedFrame, isNull); expect(offlineData.rebuildCountModel, isNull); expect(offlineData.displayRefreshRate, 60.0); + expect(offlineData.selectedTab, 0); }); test('init from parse', () { @@ -32,6 +33,7 @@ void main() { expect(offlineData.selectedFrame!.id, equals(2)); expect(offlineData.displayRefreshRate, equals(60)); expect(offlineData.rebuildCountModel, isNull); + expect(offlineData.selectedTab, equals(0)); }); test('to json', () { @@ -44,12 +46,22 @@ void main() { OfflinePerformanceData.selectedFrameIdKey: null, OfflinePerformanceData.displayRefreshRateKey: 60, OfflinePerformanceData.rebuildCountModelKey: null, + OfflinePerformanceData.selectedTabKey: 0, }), ); offlineData = OfflinePerformanceData.fromJson(rawPerformanceData); expect(offlineData.toJson(), rawPerformanceData); }); + + test('round trips a non-zero selectedTab', () { + final offlineData = OfflinePerformanceData(selectedTab: 2); + final json = offlineData.toJson(); + expect(json[OfflinePerformanceData.selectedTabKey], equals(2)); + + final parsed = OfflinePerformanceData.fromJson(json); + expect(parsed.selectedTab, equals(2)); + }); }); group('$FlutterTimelineEvent', () { diff --git a/packages/devtools_test/lib/src/test_data/_performance_data.dart b/packages/devtools_test/lib/src/test_data/_performance_data.dart index 88403e32399..d2f0f490377 100644 --- a/packages/devtools_test/lib/src/test_data/_performance_data.dart +++ b/packages/devtools_test/lib/src/test_data/_performance_data.dart @@ -108244,7 +108244,8 @@ final Map samplePerformanceData = json.decode(''' } ], "displayRefreshRate": 60, - "rebuildCountModel": null + "rebuildCountModel": null, + "selectedTab": 0 } } ''');