Added size support to Network tab (#9744)

## Overview

This PR adds support for displaying response payload size in the Network tab
and fixes #6165.

It introduces a new **"Size"** column in the network requests table and displays response size in the **Overview panel** of the request inspector.

---

## Changes

### 1. Data Model Updates

**File:**
`packages/devtools_app/lib/src/screens/network/network_model.dart`

* Added two new getters to the `NetworkRequest` base class:

  * `requestBytes`
  * `responseBytes`
* Implemented these getters in the `Socket` class using:

  * `writeBytes` : request size
  * `readBytes` : response size

**Purpose:**
Expose byte-level data in a unified way for all network request types.

---

### 2. HTTP Data Handling

**File:**
`packages/devtools_app/lib/src/shared/http/http_request_data.dart`

* Added logic to extract response size using the `content-length` header
* Handles both `String` and `List<String>` header formats

**Purpose:**
Provide response size for HTTP requests when available, without requiring changes to the Dart VM.

---

### 3. Shared Utility

**File:**
`packages/devtools_app/lib/src/screens/network/utils/http_utils.dart`

* Moved `formatBytes` into a reusable utility function
* Uses **decimal (base-10) units** (`kB`, `MB`) to align with Chrome DevTools
* Handles null and negative values safely

**Purpose:**
Ensure consistent formatting across the network table and inspector views.

---

### 4. Network Table UI

**File:**
`packages/devtools_app/lib/src/screens/network/network_screen.dart`

* Added a new column: **"Size"**
* Displays formatted response size
* Shows `-` when size is unavailable

---

### 5. Request Inspector (Overview Panel)

**File:**
`packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart`

* Added a new row:

  * **Response Size**
* Uses shared `formatBytes` utility

---

### 6. Tests

* Added unit tests for:

  * `formatBytes` utility in `http_utils_test.dart`
  * `responseBytes` parsing logic in `http_request_data.dart`
* Covers edge cases including:

  * string and list headers
  * invalid values
  * null handling

---

## Why request size is not included

Request size is not reliably available for HTTP requests due to limitations in the current DevTools and VM service APIs:

* The Dart VM does not expose request payload size in `HttpProfileRequest`
* HTTP request bodies are not always accessible or fully captured
* Headers such as `content-length` are often absent for outgoing requests
* Streaming and chunked requests complicate accurate measurement

While socket-level request size (`writeBytes`) is available, it is not consistently applicable to HTTP requests.

Therefore, including request size would require changes in the Dart SDK / VM layer.

This PR focuses on **response size**, which can be reliably determined using:

* Socket `readBytes`
* HTTP `content-length` header (when present)

---

## Screenshot

<img width="1359" height="882" alt="Screenshot 2026-03-27 233804" src="https://github.com/user-attachments/assets/4ddce5eb-1a4b-4a9e-80b6-cd16fa226c13" />

---

## Future Work

* Add request size support when VM-level data becomes available
* Introduce separate request/response size columns
* Improve accuracy via VM instrumentation

---

### General checklist

* [x] I read the Contributor Guide
* [x] I read the Tree Hygiene guidelines
* [x] I followed the Flutter Style Guide
* [x] I signed the CLA
* [x] I updated relevant documentation

---

### Issues checklist

* [x] This PR fixes #6165

---

### Tests checklist

* [x] Added unit tests for new functionality

---

### AI-tooling checklist

* [x] I used AI tooling responsibly and verified all generated content

---

### Feature-change checklist

* [x] This PR changes DevTools UI
* [x] Added entry to `NEXT_RELEASE_NOTES.md`
* [x] Included screenshots
* [x] Verified changes locally
diff --git a/packages/devtools_app/lib/src/screens/network/network_model.dart b/packages/devtools_app/lib/src/screens/network/network_model.dart
index 8a6697f..9ccc3c5 100644
--- a/packages/devtools_app/lib/src/screens/network/network_model.dart
+++ b/packages/devtools_app/lib/src/screens/network/network_model.dart
@@ -29,6 +29,9 @@
 
   int? get port;
 
+  int? get requestBytes => null;
+  int? get responseBytes => null;
+
   bool get didFail;
 
   /// True if the request hasn't completed yet.
@@ -160,6 +163,12 @@
   @override
   int get port => _socket.port;
 
+  @override
+  int get requestBytes => writeBytes;
+
+  @override
+  int get responseBytes => readBytes;
+
   // TODO(kenz): what determines a web socket request failure?
   @override
   bool get didFail => false;
diff --git a/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart b/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart
index e9bf905..118f76c 100644
--- a/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart
+++ b/packages/devtools_app/lib/src/screens/network/network_request_inspector_views.dart
@@ -17,6 +17,7 @@
 import '../../shared/ui/common_widgets.dart';
 import 'network_controller.dart';
 import 'network_model.dart';
+import 'utils/http_utils.dart';
 
 // Approximately double the indent of the expandable tile's title.
 const _rowIndentPadding = 30.0;
@@ -625,6 +626,7 @@
   }
 
   List<Widget> _buildGeneralRows(BuildContext context) {
+    final bytes = data.responseBytes;
     return [
       // TODO(kenz): show preview for requests (png, response body, proto)
       _buildRow(
@@ -658,6 +660,14 @@
         ),
         const SizedBox(height: defaultSpacing),
       ],
+
+      _buildRow(
+        context: context,
+        title: 'Response Size',
+        child: _valueText(bytes != null ? formatBytes(bytes) : '-'),
+      ),
+      const SizedBox(height: defaultSpacing),
+
       if (data.contentType != null) ...[
         _buildRow(
           context: context,
diff --git a/packages/devtools_app/lib/src/screens/network/network_screen.dart b/packages/devtools_app/lib/src/screens/network/network_screen.dart
index 0320b51..a9358f3 100644
--- a/packages/devtools_app/lib/src/screens/network/network_screen.dart
+++ b/packages/devtools_app/lib/src/screens/network/network_screen.dart
@@ -29,6 +29,7 @@
 import 'network_controller.dart';
 import 'network_model.dart';
 import 'network_request_inspector.dart';
+import 'utils/http_utils.dart';
 
 class NetworkScreen extends Screen {
   NetworkScreen() : super.fromMetaData(ScreenMetaData.network);
@@ -352,6 +353,7 @@
   static const statusColumn = StatusColumn();
   static const typeColumn = TypeColumn();
   static const durationColumn = DurationColumn();
+  static const responseSizeColumn = ResponseSizeColumn();
   static final timestampColumn = TimestampColumn();
   static const actionsColumn = ActionsColumn();
   static final columns = <ColumnData<NetworkRequest>>[
@@ -360,6 +362,7 @@
     statusColumn,
     typeColumn,
     durationColumn,
+    responseSizeColumn,
     timestampColumn,
     actionsColumn,
   ];
@@ -394,6 +397,20 @@
   }
 }
 
+class ResponseSizeColumn extends ColumnData<NetworkRequest> {
+  const ResponseSizeColumn()
+    : super('Size', alignment: ColumnAlignment.right, fixedWidthPx: 90);
+
+  @override
+  int? getValue(NetworkRequest dataObject) => dataObject.responseBytes;
+
+  @override
+  String getDisplayValue(NetworkRequest dataObject) {
+    final bytes = dataObject.responseBytes;
+    return bytes != null ? formatBytes(bytes) : '-';
+  }
+}
+
 class AddressColumn extends ColumnData<NetworkRequest>
     implements ColumnRenderer<NetworkRequest> {
   AddressColumn()
diff --git a/packages/devtools_app/lib/src/screens/network/utils/http_utils.dart b/packages/devtools_app/lib/src/screens/network/utils/http_utils.dart
index 9efd548..b4c3079 100644
--- a/packages/devtools_app/lib/src/screens/network/utils/http_utils.dart
+++ b/packages/devtools_app/lib/src/screens/network/utils/http_utils.dart
@@ -29,3 +29,18 @@
   // Calculate the byte length of the headers string
   return utf8.encode(headersString).length;
 }
+
+// Output Formats:
+// - 512 → "512 B"
+// - 2000 → "2.0 kB"
+// - 1000000 → "1.0 MB"
+// Values are rounded to one decimal place for kB and MB.
+// Uses decimal (base-10) units to match Chrome DevTools.
+String formatBytes(int? bytes) {
+  if (bytes == null || bytes < 0) return '-';
+  if (bytes < 1000) return '$bytes B';
+  if (bytes < 1000 * 1000) {
+    return '${(bytes / 1000).toStringAsFixed(1)} kB';
+  }
+  return '${(bytes / (1000 * 1000)).toStringAsFixed(1)} MB';
+}
diff --git a/packages/devtools_app/lib/src/shared/http/http_request_data.dart b/packages/devtools_app/lib/src/shared/http/http_request_data.dart
index 270bf06..81ec0d3 100644
--- a/packages/devtools_app/lib/src/shared/http/http_request_data.dart
+++ b/packages/devtools_app/lib/src/shared/http/http_request_data.dart
@@ -251,8 +251,28 @@
     return connectionInfo != null ? connectionInfo[_localPortKey] : null;
   }
 
+  @override
+  int? get responseBytes {
+    final headers = _request.response?.headers;
+    if (headers == null) return null;
+
+    final contentLength = headers['content-length'];
+
+    if (contentLength is String) {
+      return int.tryParse(contentLength);
+    }
+    if (contentLength is List && contentLength.isNotEmpty) {
+      final first = contentLength.first;
+
+      if (first is int) return first;
+      if (first is String) return int.tryParse(first);
+    }
+    return null;
+  }
+
   /// True if the HTTP request hasn't completed yet, determined by
   /// `isRequestComplete` / `isResponseComplete` from the profile data.
+
   @override
   bool get inProgress {
     if (_isCancelled) return false;
diff --git a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
index 3a13606..323d3c1 100644
--- a/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
+++ b/packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
@@ -40,8 +40,14 @@
 
 ## Network profiler updates
 
+- Added response size column to the Network tab and displayed response size in the request inspector overview.  
+  [#9744](https://github.com/flutter/devtools/pull/9744)
+
 - Improved HTTP request status classification in the Network tab to better distinguish cancelled, completed, and in-flight requests (for example, avoiding some cases where cancelled requests appeared as pending). [#9683](https://github.com/flutter/devtools/pull/9683)
 
+- Added a filter setting to hide HTTP-profiler socket data.  
+  [#9698](https://github.com/flutter/devtools/pull/9698)
+  
 ## Logging updates
 
 - Fixed an issue where log messages containing newline characters were incorrectly split into multiple separate entries in the Logging screen. [#9757](https://github.com/flutter/devtools/pull/9757)
diff --git a/packages/devtools_app/test/shared/http/http_request_data_test.dart b/packages/devtools_app/test/shared/http/http_request_data_test.dart
new file mode 100644
index 0000000..ba09d7f
--- /dev/null
+++ b/packages/devtools_app/test/shared/http/http_request_data_test.dart
@@ -0,0 +1,95 @@
+import 'package:devtools_app/src/shared/http/http_request_data.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('responseBytes', () {
+    Map<String, Object?> baseJson(Map<String, Object?> responseHeaders) {
+      return {
+        'isolateId': 'isolate-1',
+        'id': 'request-1',
+        'method': 'GET',
+        'uri': 'https://example.com',
+        'events': <Object?>[],
+        'startTime': DateTime.now().microsecondsSinceEpoch,
+        'endTime': DateTime.now().microsecondsSinceEpoch,
+        'request': {
+          'headers': <String, Object?>{},
+          'connectionInfo': null,
+          'contentLength': null,
+          'cookies': <Object?>[],
+          'followRedirects': true,
+          'maxRedirects': 5,
+          'persistentConnection': true,
+        },
+        'response': {
+          'headers': responseHeaders,
+          'connectionInfo': null,
+          'contentLength': null,
+          'cookies': <Object?>[],
+          'compressionState': 'ResponseBodyCompressionState.notCompressed',
+          'isRedirect': false,
+          'persistentConnection': true,
+          'reasonPhrase': 'OK',
+          'redirects': <Map<String, dynamic>>[],
+          'statusCode': 200,
+          'startTime': DateTime.now().microsecondsSinceEpoch,
+        },
+      };
+    }
+
+    // Verifies parsing when content-length is a string value.
+    test('parses content-length from string', () {
+      final request = DartIOHttpRequestData.fromJson(
+        baseJson({'content-length': '1234'}),
+        null,
+        null,
+      );
+
+      expect(request.responseBytes, 1234);
+    });
+
+    // Verifies parsing when content-length is a list of strings.
+    test('parses content-length from list of strings', () {
+      final request = DartIOHttpRequestData.fromJson(
+        baseJson({
+          'content-length': ['5678'],
+        }),
+        null,
+        null,
+      );
+
+      expect(request.responseBytes, 5678);
+    });
+
+    // Ensures integer values inside a list are handled correctly.
+    test('handles integer in list', () {
+      final request = DartIOHttpRequestData.fromJson(
+        baseJson({
+          'content-length': [91011],
+        }),
+        null,
+        null,
+      );
+
+      expect(request.responseBytes, 91011);
+    });
+
+    // Returns null when header is missing.
+    test('returns null for missing header', () {
+      final request = DartIOHttpRequestData.fromJson(baseJson({}), null, null);
+
+      expect(request.responseBytes, null);
+    });
+
+    // Returns null when parsing fails.
+    test('returns null for invalid value', () {
+      final request = DartIOHttpRequestData.fromJson(
+        baseJson({'content-length': 'invalid'}),
+        null,
+        null,
+      );
+
+      expect(request.responseBytes, null);
+    });
+  });
+}
diff --git a/packages/devtools_app/test/shared/http/http_utils_test.dart b/packages/devtools_app/test/shared/http/http_utils_test.dart
new file mode 100644
index 0000000..9485373
--- /dev/null
+++ b/packages/devtools_app/test/shared/http/http_utils_test.dart
@@ -0,0 +1,19 @@
+import 'package:devtools_app/src/screens/network/utils/http_utils.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('formatBytes', () {
+    // Verifies correct formatting across different unit ranges.
+    test('formats bytes correctly', () {
+      expect(formatBytes(512), '512 B'); // bytes
+      expect(formatBytes(2000), '2.0 kB'); // kilobytes (base-10)
+      expect(formatBytes(1000000), '1.0 MB'); // megabytes (base-10)
+    });
+
+    // Ensures handling of invalid or missing values.
+    test('handles null and negative values', () {
+      expect(formatBytes(null), '-');
+      expect(formatBytes(-1), '-');
+    });
+  });
+}