Flutter Web用universal_io传图至Supabase Storage遇UnimplementedError
Flutter Web 上传图片至Supabase Storage 报错解决
问题说明
在Flutter Web环境中尝试上传图片到Supabase Storage,使用官方上传方法:
supabase.storage.from('avatars').upload(fileName, file)
通过universal_io包构造File对象:
var file = File(_session.photoFilePath!); // import 'package:universal_io/io.dart';
打印的文件路径为blob:http://localhost:55914/eff9c2bb-061f-4577-bf00-eff523029635,文件名为tiger.jpeg。已完成登录且拥有avatars存储桶上传权限,但执行上传时抛出UnimplementedError,错误栈如下:
UnimplementedError dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 266:49 throw_ packages/universal_io/src/io/file.dart 225:7 new packages/fr_panel/view/session_page.dart 351:42 <fn> dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 84:54 runBody dart-sdk/lib/_internal/js_dev_runtime/patch/async_patch.dart 123:5 _async packages/fr_panel/view/session_page.dart 348:38 <fn> packages/fr_panel/widgets/next_button.dart 40:27 <fn> packages/flutter/src/widgets/framework.dart 1114:30 setState packages/fr_panel/widgets/next_button.dart 39:9 <fn> packages/flutter/src/material/ink_well.dart 1072:21 handleTap packages/flutter/src/gestures/recognizer.dart 253:24 invokeCallback packages/flutter/src/gestures/tap.dart 627:11 handleTapUp packages/flutter/src/gestures/tap.dart 306:5 [_checkUp] packages/flutter/src/gestures/tap.dart 239:7 handlePrimaryPointer packages/flutter/src/gestures/recognizer.dart 615:9 handleEvent packages/flutter/src/gestures/pointer_router.dart 98:12 [_dispatch] packages/flutter/src/gestures/pointer_router.dart 143:9 <fn> dart-sdk/lib/_internal/js_dev_runtime/private/linked_hash_map.dart 21:13 forEach packages/flutter/src/gestures/pointer_router.dart 141:17 [_dispatchEventToRoutes] packages/flutter/src/gestures/pointer_router.dart 127:7 route packages/flutter/src/gestures/binding.dart 460:19 handleEvent packages/flutter/src/gestures/binding.dart 440:14 dispatchEvent packages/flutter/src/rendering/binding.dart 337:11 dispatchEvent packages/flutter/src/gestures/binding.dart 395:7 [_handlePointerEventImmediately] packages/flutter/src/gestures/binding.dart 357:5 handlePointerEvent packages/flutter/src/gestures/binding.dart 314:7 [_flushPointerEventQueue] packages/flutter/src/gestures/binding.dart 295:7 [_handlePointerDataPacket] lib/_engine/engine/platform_dispatcher.dart 1183:13 invoke1 lib/_engine/engine/platform_dispatcher.dart 244:5 invokeOnPointerDataPacket lib/_engine/engine/pointer_binding.dart 147:39 [_onPointerData] lib/_engine/engine/pointer_binding.dart 653:20 <fn> lib/_engine/engine/pointer_binding.dart 594:14 <fn> lib/_engine/engine/pointer_binding.dart 288:16 loggedHandler lib/_engine/engine/pointer_binding.dart 179:80 <fn> dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 334:14 _checkAndCall dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 339:39 dcall
使用的Flutter环境:
Doctor summary (to see all details, run flutter doctor -v): [✓] Flutter (Channel master, 3.1.0-0.0.pre.1698, on macOS 13.0 22A5286j darwin-arm, locale en-TR) [✓] Android toolchain - develop for Android devices (Android SDK version 32.1.0-rc1) [✓] Xcode - develop for iOS and macOS (Xcode 14.0) [✓] Chrome - develop for the web [✓] Android Studio (version 2021.1) [✓] VS Code (version 1.69.2) [✓] Connected device (2 available) [✓] HTTP Host Availability • No issues found!
解决方法
问题核心是Flutter Web环境下,universal_io的File类无法处理blob:协议路径,Supabase Flutter SDK在Web端需要直接使用浏览器原生的File对象(而非dart:io/Universal IO的File)。
1. 获取浏览器原生File对象
如果是通过文件选择器获取图片,直接使用dart:html包的File对象:
import 'dart:html' as html; Future<void> pickAndUpload() async { final input = html.FileUploadInputElement()..accept = 'image/*'; input.click(); await input.onChange.first; final file = input.files?.first; if (file != null) { await uploadToSupabase(file, file.name); } }
如果你的photoFilePath是blob:链接,先将其转换为原生File:
import 'dart:html' as html; Future<html.File> blobUrlToFile(String blobUrl, String fileName) async { final response = await html.HttpRequest.request(blobUrl, responseType: 'blob'); final blob = response.response as html.Blob; return html.File([blob], fileName); }
2. 调用Supabase上传接口
直接传入浏览器原生File对象到上传方法:
Future<void> uploadToSupabase(html.File file, String fileName) async { try { final result = await supabase.storage.from('avatars').upload( fileName, file, fileOptions: const FileOptions(contentType: 'image/jpeg'), // 根据文件类型调整 ); print('上传成功:${result.data?.path}'); } catch (e) { print('上传失败:$e'); } }
3. 跨平台兼容(可选)
如果需要同时支持移动端和Web端,通过条件导入区分实现:
import 'dart:io' show Platform; import 'package:flutter/foundation.dart' show kIsWeb; // 定义抽象服务类 abstract class ImageUploader { Future<void> uploadImage(); } // 移动端实现 import 'dart:io'; import 'package:image_picker/image_picker.dart'; class MobileImageUploader implements ImageUploader { @override Future<void> uploadImage() async { final picker = ImagePicker(); final image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { await supabase.storage.from('avatars').upload(image.name, File(image.path)); } } } // Web端实现 import 'dart:html' as html; class WebImageUploader implements ImageUploader { @override Future<void> uploadImage() async { final input = html.FileUploadInputElement()..accept = 'image/*'; input.click(); await input.onChange.first; final file = input.files?.first; if (file != null) { await supabase.storage.from('avatars').upload(file.name, file); } } } // 使用时选择对应实现 final uploader = kIsWeb ? WebImageUploader() : MobileImageUploader();
内容的提问来源于stack exchange,提问作者bilalkubilay
相关产品推荐
相关产品推荐

