Repository: cfug/dio

Issue:
Uploading with FormData throws a TypeError: type 'List<int>' is not a subtype of type 'Uint8List' of 'value'
### Package

dio

### Version

5.4.3+1

### Operating-System

Android, iOS

### Adapter

Default Dio

### Output of `flutter doctor -v`

```shell
[✓] Flutter (Channel stable, 3.22.1, on macOS 14.5 23F79 darwin-arm64, locale it-IT)
    • Flutter version 3.22.1 on channel stable at /Users/hans/fvm/versions/3.22.1
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision a14f74ff3a (2 weeks ago), 2024-05-22 11:08:21 -0500
    • Engine revision 55eae6864b
    • Dart version 3.4.1
    • DevTools version 2.34.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/hans/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 15F31d
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 17.0.7+0-17.0.7b1000.6-10550314)

[✓] IntelliJ IDEA Community Edition (version 2024.1.2)
    • IntelliJ at /Applications/IntelliJ IDEA CE.app
    • Flutter plugin version 80.0.2
    • Dart plugin version 241.17502

[✓] VS Code (version 1.90.0)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension can be installed from:
      🔨 https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[✓] Connected device (5 available)
    • iPhone di Roberto Tassi (mobile) • 00008101-00111C3A0190001E            • ios            • iOS 17.5.1 21F90
    • iPhone 15 Pro Max (mobile)       • 27013218-0307-4598-9E9D-1ADA200AC39C • ios            • com.apple.CoreSimulator.SimRuntime.iOS-17-5 (simulator)
    • macOS (desktop)                  • macos                                • darwin-arm64   • macOS 14.5 23F79 darwin-arm64
    • Mac Designed for iPad (desktop)  • mac-designed-for-ipad                • darwin         • macOS 14.5 23F79 darwin-arm64
    • Chrome (web)                     • chrome                               • web-javascript • Google Chrome 125.0.6422.142

[✓] Network resources
    • All expected network resources are available.

• No issues found!
```


### Dart Version

_No response_

### Steps to Reproduce

```
final formData = dio.FormData();
// fileData is not a Uint8List!
List<int> fileData = ...;
formData.files.add(MapEntry(
  'file',
  dio.MultipartFile.fromBytes(
    fileData,
    filename: fileName,
    contentType: contentType,
  ),
));
```

### Expected Result

No error

### Actual Result

```
flutter: Uncaught Error: type 'List<int>' is not a subtype of type 'Uint8List' of 'value'
flutter: Stacktrace: #0      _StreamController.add (dart:async/stream_controller.dart:604:14)
flutter: #1      _rootRunUnary (dart:async/zone.dart:1407:47)
flutter: #2      _CustomZone.runUnary (dart:async/zone.dart:1308:19)
flutter: #3      _CustomZone.runUnaryGuarded (dart:async/zone.dart:1217:7)
flutter: #4      _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:365:11)
flutter: #5      _BufferingStreamSubscription._add (dart:async/stream_impl.dart:297:7)
flutter: #6      _MultiStreamController.addSync (dart:async/stream_impl.dart:1127:36)
flutter: #7      new Stream.fromIterable.<anonymous closure>.next (dart:async/stream.dart:375:26)
flutter: #8      _rootRun (dart:async/zone.dart:1399:13)
flutter: #9      _CustomZone.run (dart:async/zone.dart:1301:19)
flutter: #10     _CustomZone.runGuarded (dart:async/zone.dart:1209:7)
flutter: #11     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1249:23)
flutter: #12     _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
flutter: #13     _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5)
```


Discussion: I think is related to:

- https://github.com/cfug/dio/issues/2184
- https://github.com/cfug/dio/issues/2186

I suppose the problem is in `form_data.dart`:
```
final controller = StreamController<Uint8List>(sync: false);

...

Future<void>(() async {
  for (final file in files) {
    writeUtf8('--$boundary$_rn');
    writeUtf8(_headerForFile(file));
    await writeStreamToSink(file.value.finalize(), controller);
    writeLine();
  }
}).then((_) {
  writeUtf8('--$boundary--$_rn');
}).whenComplete(() {
  controller.close();
});

multipart_file.dart
Stream<List<int>> finalize();
```

Calling `writeStreamToSink(Stream<List<int>>, StreamController<Uint8List>)` throws a `TypeError` because is trying to add `List<int>` to a `Uint8List` sink.


@fromlabs Could you try updating the implementation to below solve the problem?

```dart
Future<void>(() async {
  for (final file in files) {
    writeUtf8('--$boundary$_rn');
    writeUtf8(_headerForFile(file));
    await writeStreamToSink<Uint8List>(
      file.value
          .finalize()
          .map((e) => e is Uint8List ? e : Uint8List.fromList(e)),
      controller,
    );
    writeLine();
  }
}).then((_) {
  writeUtf8('--$boundary--$_rn');
}).whenComplete(() {
  controller.close();
});
```



Fix patch:
diff --git a/dio/CHANGELOG.md b/dio/CHANGELOG.md
index eebd85f38..310727a0a 100644
--- a/dio/CHANGELOG.md
+++ b/dio/CHANGELOG.md
@@ -8,6 +8,7 @@ See the [Migration Guide][] for the complete breaking changes list.**
 - Raise the min Dart SDK version to 2.18.0.
 - Add constructor for `DioExceptionType.badCertificate`.
 - Create type alias `DioMediaType` for `http_parser`'s `MediaType`.
+- Fix the type conversion regression when using `MultipartFile.fromBytes`.
 
 ## 5.4.3+1
 
diff --git a/dio/lib/src/form_data.dart b/dio/lib/src/form_data.dart
index b022734d3..10bc5f626 100644
--- a/dio/lib/src/form_data.dart
+++ b/dio/lib/src/form_data.dart
@@ -1,7 +1,7 @@
 import 'dart:async';
 import 'dart:convert';
 import 'dart:math' as math;
-import 'dart:typed_data';
+import 'dart:typed_data' show Uint8List;
 
 import 'multipart_file.dart';
 import 'options.dart';
@@ -188,7 +188,7 @@ class FormData {
       for (final file in files) {
         writeUtf8('--$boundary$_rn');
         writeUtf8(_headerForFile(file));
-        await writeStreamToSink(file.value.finalize(), controller);
+        await writeStreamToSink<Uint8List>(file.value.finalize(), controller);
         writeLine();
       }
     }).then((_) {
diff --git a/dio/lib/src/multipart_file.dart b/dio/lib/src/multipart_file.dart
index 013401acd..f40e050cf 100644
--- a/dio/lib/src/multipart_file.dart
+++ b/dio/lib/src/multipart_file.dart
@@ -1,7 +1,7 @@
-import 'dart:async';
-import 'dart:convert';
+import 'dart:convert' show utf8;
+import 'dart:typed_data' show Uint8List;
 
-import 'package:http_parser/http_parser.dart';
+import 'package:http_parser/http_parser.dart' show MediaType;
 
 import 'multipart_file/io_multipart_file.dart'
     if (dart.library.html) 'multipart_file/browser_multipart_file.dart';
@@ -32,7 +32,7 @@ class MultipartFile {
     this.filename,
     DioMediaType? contentType,
     Map<String, List<String>>? headers,
-  })  : _data = (() => stream),
+  })  : _dataBuilder = (() => stream),
         headers = caseInsensitiveKeyMap(headers),
         contentType = contentType ?? MediaType('application', 'octet-stream');
 
@@ -48,7 +48,7 @@ class MultipartFile {
     this.filename,
     DioMediaType? contentType,
     Map<String, List<String>>? headers,
-  })  : _data = data,
+  })  : _dataBuilder = data,
         headers = caseInsensitiveKeyMap(headers),
         contentType = contentType ?? MediaType('application', 'octet-stream');
 
@@ -112,7 +112,7 @@ class MultipartFile {
   final DioMediaType? contentType;
 
   /// The stream builder that will emit the file's contents for every call.
-  final Stream<List<int>> Function() _data;
+  final Stream<List<int>> Function() _dataBuilder;
 
   /// Whether [finalize] has been called.
   bool get isFinalized => _isFinalized;
@@ -153,7 +153,7 @@ class MultipartFile {
 
   bool _isFinalized = false;
 
-  Stream<List<int>> finalize() {
+  Stream<Uint8List> finalize() {
     if (isFinalized) {
       throw StateError(
         'The MultipartFile has already been finalized. '
@@ -162,7 +162,8 @@ class MultipartFile {
       );
     }
     _isFinalized = true;
-    return _data.call();
+    return _dataBuilder()
+        .map((e) => e is Uint8List ? e : Uint8List.fromList(e));
   }
 
   /// Clone MultipartFile, returning a new instance of the same object.
@@ -170,7 +171,7 @@ class MultipartFile {
   /// such as an unauthorized exception can be solved by refreshing the token.
   MultipartFile clone() {
     return MultipartFile.fromStream(
-      _data,
+      _dataBuilder,
       length,
       filename: filename,
       contentType: contentType,
diff --git a/dio/lib/src/utils.dart b/dio/lib/src/utils.dart
index 5cd919898..74d6db283 100644
--- a/dio/lib/src/utils.dart
+++ b/dio/lib/src/utils.dart
@@ -21,8 +21,8 @@ const kReleaseMode = bool.fromEnvironment('dart.vm.product');
 /// Pipes all data and errors from [stream] into [sink]. Completes [Future] once
 /// [stream] is done. Unlike [store], [sink] remains open after [stream] is
 /// done.
-Future writeStreamToSink(Stream stream, EventSink sink) {
-  final completer = Completer();
+Future<void> writeStreamToSink<T>(Stream<T> stream, EventSink<T> sink) {
+  final completer = Completer<void>();
   stream.listen(
     sink.add,
     onError: sink.addError,


Test patch:
diff --git a/dio/test/formdata_test.dart b/dio/test/formdata_test.dart
index b416adcdd..109a53246 100644
--- a/dio/test/formdata_test.dart
+++ b/dio/test/formdata_test.dart
@@ -37,6 +37,14 @@ void main() async {
               },
               contentType: DioMediaType.parse('text/plain'),
             ),
+            MultipartFile.fromBytes(
+              utf8.encode('hello world again.').toList(),
+              filename: '3.txt',
+              headers: {
+                'test': <String>['d'],
+              },
+              contentType: DioMediaType.parse('text/plain'),
+            ),
           ],
         });
         final fmStr = await fm.readAsBytes();
@@ -91,6 +99,19 @@ void main() async {
             ),
           ),
         );
+        fm1.files.add(
+          MapEntry(
+            'files',
+            MultipartFile.fromBytes(
+              utf8.encode('hello world again.'),
+              filename: '3.txt',
+              headers: {
+                'test': <String>['d'],
+              },
+              contentType: DioMediaType.parse('text/plain'),
+            ),
+          ),
+        );
         expect(fmStr.length, fm1.length);
       },
       testOn: 'vm',
@@ -125,6 +146,14 @@ void main() async {
               },
               contentType: DioMediaType.parse('text/plain'),
             ),
+            MultipartFile.fromBytes(
+              utf8.encode('hello world again.'),
+              filename: '3.txt',
+              headers: {
+                'test': <String>['d'],
+              },
+              contentType: DioMediaType.parse('text/plain'),
+            ),
           ],
         });
         final fmStr = await fm.readAsBytes();
diff --git a/dio/test/mock/_formdata b/dio/test/mock/_formdata
index dc6d79e06..814c2c946 100644
--- a/dio/test/mock/_formdata
+++ b/dio/test/mock/_formdata
@@ -34,4 +34,10 @@ test: c
 我很好人类
 哈哈哈哈哈😆
 
+----dio-boundary-3788753558
+content-disposition: form-data; name="files"; filename="3.txt"
+content-type: text/plain
+test: d
+
+hello world again.
 ----dio-boundary-3788753558--
diff --git a/dio_test/lib/src/test/download_tests.dart b/dio_test/lib/src/test/download_tests.dart
index 85e3f7a7b..53cb617dc 100644
--- a/dio_test/lib/src/test/download_tests.dart
+++ b/dio_test/lib/src/test/download_tests.dart
@@ -64,7 +64,7 @@ void downloadTests(
           cancelToken.cancel();
         });
 
-        final completer = Completer();
+        final completer = Completer<Never>();
         res.data!.stream.listen(
           (event) {},
           onError: (e, s) {