Fix some offline import issues with the Performance page. (#4735)
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats.dart b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats.dart index 5ad02b4..3bf28fc 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats.dart
@@ -29,15 +29,14 @@ @override Widget build(BuildContext context) { - return DualValueListenableBuilder<RasterStats, bool>( + return DualValueListenableBuilder<RasterStats?, bool>( firstListenable: rasterStatsController.rasterStats, secondListenable: rasterStatsController.loadingSnapshot, builder: (context, rasterStats, loading, _) { if (loading) { return const CenteredCircularProgressIndicator(); } - final snapshots = rasterStats.layerSnapshots; - if (snapshots.isEmpty) { + if (rasterStats == null || rasterStats.layerSnapshots.isEmpty) { return const Center( child: Text( 'Take a snapshot to view raster stats for the current screen.', @@ -50,15 +49,14 @@ children: [ LayerSnapshotTable( controller: rasterStatsController, - snapshots: snapshots, + snapshots: rasterStats.layerSnapshots, ), ValueListenableBuilder<LayerSnapshot?>( valueListenable: rasterStatsController.selectedSnapshot, builder: (context, snapshot, _) { return LayerImage( snapshot: snapshot, - originalFrameSize: - rasterStatsController.rasterStats.value.originalFrameSize, + originalFrameSize: rasterStats.originalFrameSize, includeFullScreenButton: true, ); },
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_controller.dart b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_controller.dart index 66ad20b..d3f1d2f 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_controller.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_controller.dart
@@ -15,8 +15,8 @@ RasterStatsController(super.performanceController); /// The active raster stats for the view. - ValueListenable<RasterStats> get rasterStats => _rasterStats; - final _rasterStats = ValueNotifier<RasterStats>(RasterStats.empty()); + ValueListenable<RasterStats?> get rasterStats => _rasterStats; + final _rasterStats = ValueNotifier<RasterStats?>(null); ValueListenable<bool> get loadingSnapshot => _loadingSnapshot; final _loadingSnapshot = ValueNotifier<bool>(false); @@ -24,7 +24,8 @@ final selectedSnapshot = ValueNotifier<LayerSnapshot?>(null); void selectSnapshot(LayerSnapshot? snapshot) { - _rasterStats.value.selectedSnapshot = snapshot; + assert(_rasterStats.value != null); + _rasterStats.value!.selectedSnapshot = snapshot; selectedSnapshot.value = snapshot; } @@ -44,9 +45,9 @@ } } - void setData(RasterStats stats) { + void setData(RasterStats? stats) { _rasterStats.value = stats; - selectedSnapshot.value = stats.selectedSnapshot; + selectedSnapshot.value = stats?.selectedSnapshot; performanceController.data!.rasterStats = stats; } @@ -65,6 +66,6 @@ @override void clearData() { - setData(RasterStats.empty()); + setData(null); } }
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_model.dart b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_model.dart index 3897987..bd698b0 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_model.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/raster_stats/raster_stats_model.dart
@@ -15,21 +15,16 @@ required this.selectedSnapshot, }); - factory RasterStats.empty() { - return RasterStats._( - layerSnapshots: [], - originalFrameSize: null, - selectedSnapshot: null, - totalRasterTime: Duration.zero, - ); - } - factory RasterStats.parse(Map<String, Object?> json) { - final originalFrameSize = Size( - (json[_frameWidthKey] as num).toDouble(), - (json[_frameHeightKey] as num).toDouble(), - ); - + Size? originalFrameSize; + final originalWidth = json[_frameWidthKey] as num?; + final originalHeight = json[_frameHeightKey] as num?; + if (originalHeight != null && originalWidth != null) { + originalFrameSize = Size( + originalWidth.toDouble(), + originalHeight.toDouble(), + ); + } int? selectedId; LayerSnapshot? selected; if (json[_selectedIdKey] != null) {
diff --git a/packages/devtools_app/lib/src/screens/performance/panes/rebuild_stats/rebuild_counts.dart b/packages/devtools_app/lib/src/screens/performance/panes/rebuild_stats/rebuild_counts.dart index 8fe8121..4b001e5 100644 --- a/packages/devtools_app/lib/src/screens/performance/panes/rebuild_stats/rebuild_counts.dart +++ b/packages/devtools_app/lib/src/screens/performance/panes/rebuild_stats/rebuild_counts.dart
@@ -209,6 +209,8 @@ return _rebuildsForFrame[frameNumber]; } + bool get isNotEmpty => _rebuildsForFrame.isNotEmpty; + Map<String, dynamic>? toJson() { if (_rebuildsForFrame.isEmpty) { // No need to encode data unless there were actually rebuilds reported.
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 4dd8644..22f1f02 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
@@ -77,12 +77,24 @@ @override Widget build(BuildContext context) { + final isOffline = offlineController.offlineMode.value; + final isFlutterApp = serviceManager.connectedApp!.isFlutterAppNow!; + + var showFrameAnalysis = isFlutterApp; + var showRasterStats = isFlutterApp; + var showRebuildStats = FeatureFlags.widgetRebuildstats && isFlutterApp; + final offlineData = controller.offlinePerformanceData; + if (isOffline && offlineData != null) { + showFrameAnalysis = showFrameAnalysis && offlineData.frames.isNotEmpty; + showRasterStats = showRasterStats && offlineData.rasterStats != null; + showRebuildStats = + showRebuildStats && offlineData.rebuildCountModel.isNotEmpty; + } + final tabRecords = <_PerformanceTabRecord>[ - if (serviceManager.connectedApp!.isFlutterAppNow!) ...[ - _frameAnalysisRecord(), - _rasterStatsRecord(), - if (FeatureFlags.widgetRebuildstats) _rebuildStatsRecord(), - ], + if (showFrameAnalysis) _frameAnalysisRecord(), + if (showRasterStats) _rasterStatsRecord(), + if (showRebuildStats) _rebuildStatsRecord(), _timelineEventsRecord(), ]; @@ -164,33 +176,36 @@ return _PerformanceTabRecord( tab: _buildTab( tabName: 'Raster Stats', - trailing: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconLabelButton( - tooltip: 'Take a snapshot of the rendering layers on the current' - ' screen', - icon: Icons.camera, - label: 'Take Snapshot', - outlined: false, - onPressed: () { - ga.select( - PerformanceScreen.id, - analytics_constants.collectRasterStats, - ); - unawaited( - controller.rasterStatsController.collectRasterStats(), - ); - }, - ), - const SizedBox(width: denseSpacing), - ClearButton( - outlined: false, - onPressed: controller.rasterStatsController.clearData, - ), - const SizedBox(width: densePadding), - ], - ), + trailing: !offlineController.offlineMode.value + ? Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconLabelButton( + tooltip: + 'Take a snapshot of the rendering layers on the current' + ' screen', + icon: Icons.camera, + label: 'Take Snapshot', + outlined: false, + onPressed: () { + ga.select( + PerformanceScreen.id, + analytics_constants.collectRasterStats, + ); + unawaited( + controller.rasterStatsController.collectRasterStats(), + ); + }, + ), + const SizedBox(width: denseSpacing), + ClearButton( + outlined: false, + onPressed: controller.rasterStatsController.clearData, + ), + const SizedBox(width: densePadding), + ], + ) + : null, ), tabView: KeepAliveWrapper( child: Center( @@ -223,9 +238,10 @@ gaSelection: analytics_constants.timelineFlameChartHelp, ), ], - RefreshTimelineEventsButton( - controller: _timelineEventsController, - ), + if (!offlineController.offlineMode.value) + RefreshTimelineEventsButton( + controller: _timelineEventsController, + ), ], ); },
diff --git a/packages/devtools_app/test/performance/raster_stats/raster_stats_controller_test.dart b/packages/devtools_app/test/performance/raster_stats/raster_stats_controller_test.dart index 6314b7b..fe10d3f 100644 --- a/packages/devtools_app/test/performance/raster_stats/raster_stats_controller_test.dart +++ b/packages/devtools_app/test/performance/raster_stats/raster_stats_controller_test.dart
@@ -30,43 +30,36 @@ test('calling collectRasterStats sets data', () async { var rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots, isEmpty); - expect(rasterStats.selectedSnapshot, isNull); - expect(rasterStats.originalFrameSize, isNull); - expect(rasterStats.totalRasterTime, equals(Duration.zero)); + expect(rasterStats, isNull); await controller.collectRasterStats(); rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots.length, equals(2)); + expect(rasterStats, isNotNull); + expect(rasterStats!.layerSnapshots.length, equals(2)); expect(rasterStats.selectedSnapshot, isNotNull); expect(rasterStats.originalFrameSize, isNotNull); expect(rasterStats.totalRasterTime, isNot(equals(Duration.zero))); }); - test('calling collectRasterStats sets empty data for bad service response', + test('calling collectRasterStats sets null data for bad service response', () async { var rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots, isEmpty); - expect(rasterStats.selectedSnapshot, isNull); - expect(rasterStats.originalFrameSize, isNull); - expect(rasterStats.totalRasterTime, equals(Duration.zero)); + expect(rasterStats, isNull); when(mockServiceManager.renderFrameWithRasterStats) .thenAnswer((_) => throw Exception('something went wrong')); await controller.collectRasterStats(); rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots, isEmpty); - expect(rasterStats.selectedSnapshot, isNull); - expect(rasterStats.originalFrameSize, isNull); - expect(rasterStats.totalRasterTime, equals(Duration.zero)); + expect(rasterStats, isNull); }); - test('clear', () async { + test('calling clear nulls out raster stats', () async { await controller.collectRasterStats(); var rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots.length, equals(2)); + expect(rasterStats, isNotNull); + expect(rasterStats!.layerSnapshots.length, equals(2)); expect(rasterStats.selectedSnapshot, isNotNull); expect(rasterStats.originalFrameSize, isNotNull); expect(rasterStats.totalRasterTime, isNot(equals(Duration.zero))); @@ -74,22 +67,20 @@ controller.clearData(); rasterStats = controller.rasterStats.value; - expect(rasterStats.layerSnapshots, isEmpty); - expect(rasterStats.selectedSnapshot, isNull); - expect(rasterStats.originalFrameSize, isNull); - expect(rasterStats.totalRasterTime, equals(Duration.zero)); + expect(rasterStats, isNull); }); test('setOfflineData', () async { final rasterStats = RasterStats.parse(rasterStatsFromServiceJson); - // Ensure we are starting in an empty state. - expect(controller.rasterStats.value.layerSnapshots, isEmpty); + // Ensure we are starting in a null state. + expect(controller.rasterStats.value, isNull); final offlineData = PerformanceData(rasterStats: rasterStats); await controller.setOfflineData(offlineData); - expect(controller.rasterStats.value.layerSnapshots.length, equals(2)); + expect(controller.rasterStats.value, isNotNull); + expect(controller.rasterStats.value!.layerSnapshots.length, equals(2)); }); }); }
diff --git a/packages/devtools_app/test/performance/raster_stats/raster_stats_model_test.dart b/packages/devtools_app/test/performance/raster_stats/raster_stats_model_test.dart index 4b9cf5e..6c89238 100644 --- a/packages/devtools_app/test/performance/raster_stats/raster_stats_model_test.dart +++ b/packages/devtools_app/test/performance/raster_stats/raster_stats_model_test.dart
@@ -13,14 +13,6 @@ late RasterStats rasterStats; group('$RasterStats model', () { - test('empty constructor', () { - rasterStats = RasterStats.empty(); - expect(rasterStats.layerSnapshots, isEmpty); - expect(rasterStats.selectedSnapshot, isNull); - expect(rasterStats.originalFrameSize, isNull); - expect(rasterStats.totalRasterTime, equals(Duration.zero)); - }); - test('parse from service data', () { rasterStats = RasterStats.parse(rasterStatsFromServiceJson); expect(rasterStats.layerSnapshots.length, equals(2)); @@ -78,19 +70,9 @@ }); test('to json', () { - rasterStats = RasterStats.empty(); - var json = rasterStats.json; - var expected = <String, Object?>{ - 'frame_width': null, - 'frame_height': null, - 'snapshots': [], - 'selectedId': null - }; - expect(collectionEquals(json, expected), isTrue); - rasterStats = RasterStats.parse(rasterStatsFromServiceJson); - json = rasterStats.json; - expected = Map<String, Object?>.from(rasterStatsFromServiceJson); + final json = rasterStats.json; + final expected = Map<String, Object?>.from(rasterStatsFromServiceJson); // The expected output should not have the 'type' field that comes from // the service protocol and it should have an additional field for the id // of the selected snapshot.
diff --git a/packages/devtools_app/test/performance/raster_stats/raster_stats_test.dart b/packages/devtools_app/test/performance/raster_stats/raster_stats_test.dart index 0eb5669..b9f28b5 100644 --- a/packages/devtools_app/test/performance/raster_stats/raster_stats_test.dart +++ b/packages/devtools_app/test/performance/raster_stats/raster_stats_test.dart
@@ -79,16 +79,14 @@ testWidgets('can change layer selection', (tester) async { await pumpRenderingLayerVisualizer(tester); - final layers = controller.rasterStats.value.layerSnapshots; + final rasterStats = controller.rasterStats.value!; + final layers = rasterStats.layerSnapshots; final firstLayer = layers.first; final secondLayer = layers.last; expect(firstLayer.displayName, equals('Layer 12731')); expect(secondLayer.displayName, equals('Layer 12734')); - expect( - controller.rasterStats.value.selectedSnapshot, - equals(firstLayer), - ); + expect(rasterStats.selectedSnapshot, equals(firstLayer)); await tester.tap(find.richText('Layer 12734')); await tester.pumpAndSettle();
diff --git a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart index 98108bc..f5c7161 100644 --- a/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart +++ b/packages/devtools_test/lib/src/mocks/generated_mocks_factories.dart
@@ -19,6 +19,7 @@ when(controller.data).thenReturn(PerformanceData()); when(controller.enhanceTracingController) .thenReturn(EnhanceTracingController()); + when(controller.offlinePerformanceData).thenReturn(null); // Stubs for Flutter Frames feature. when(controller.flutterFramesController).thenReturn(flutterFramesController);