You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter相机图片无法上传Firebase Storage:onComplete方法过时问题

解决Flutter上传图片至Firebase Storage的过时API问题

问题分析

你的代码存在两处API过时问题:

  1. Firebase Storage的onComplete方法已被移除,putFile现在直接返回UploadTask,无需链式调用onComplete
  2. ImagePicker.pickImage是旧版API,当前版本的image_picker包已经更新了调用方式

移除onComplete后图片仍未上传,核心原因是上传逻辑没有正确执行,同时旧版相机选图API可能存在空值传递问题。

修复步骤

1. 修复Firebase Storage上传逻辑

替换原来的上传代码,直接await ref.putFile(image)完成上传。如果需要后续显示图片,建议获取图片下载URL并存入Firestore:

修改_submitAuthForm中的上传部分:

final ref = FirebaseStorage.instance
    .ref()
    .child('user_image')
    .child(authResult.user!.uid + '.jpg');

// 直接等待上传完成
await ref.putFile(image);
// 获取图片下载URL
final imageUrl = await ref.getDownloadURL();

await FirebaseFirestore.instance
    .collection('users')
    .doc(authResult.user!.uid)
    .set({
  'username': username,
  'email': email,
  'image_url': imageUrl, // 新增存储图片URL,方便后续调用
});

2. 修复ImagePicker调用逻辑

新版image_picker中,pickImage需要通过实例调用,同时要处理用户取消拍照的空值情况:

修改UserImagePicker的_pickImage方法:

void _pickImage() async {
  final picker = ImagePicker();
  // 使用新版API调用,可添加图片压缩参数
  final pickedImageFile = await picker.pickImage(
    source: ImageSource.camera,
    imageQuality: 80, // 可选:压缩图片,减少上传体积
  );
  
  if (pickedImageFile == null) return; // 用户取消拍照,直接终止逻辑

  setState(() {
    _pickedImage = File(pickedImageFile.path);
  });

  widget.imagePickFn(_pickedImage!);
}

3. 检查Firebase Storage权限规则

确保Storage规则允许上传,测试阶段可临时设置宽松规则:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if true;
    }
  }
}

生产环境务必修改为基于用户认证的安全规则,比如:

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /user_image/{userId}/{allPaths=**} {
      allow read, write: if request.auth != null && request.auth.uid == userId;
    }
  }
}

修改后的完整代码

AuthScreen修改后代码

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

import 'package:chat_app2/widgets/auth/auth_form.dart';
import 'package:flutter/services.dart';
import 'package:firebase_storage/firebase_storage.dart';

class AuthScreen extends StatefulWidget {
  @override
  _AuthScreenState createState() => _AuthScreenState();
}

class _AuthScreenState extends State<AuthScreen> {
  final _auth = FirebaseAuth.instance;
  var _isLoading = false;
  void _submitAuthForm(String email, String username, String password,
      File image, bool isLogin, BuildContext ctx) async {
    UserCredential authResult;
    try {
      setState(() {
        _isLoading = true;
      });
      if (isLogin) {
        authResult = await _auth.signInWithEmailAndPassword(
          email: email,
          password: password,
        );
      } else {
        authResult = await _auth.createUserWithEmailAndPassword(
          email: email,
          password: password,
        );
        
         final ref = FirebaseStorage.instance
             .ref()
             .child('user_image')
             .child(authResult.user!.uid + '.jpg');

        // 修复上传逻辑
        await ref.putFile(image);
        final imageUrl = await ref.getDownloadURL();

        await FirebaseFirestore.instance
            .collection('users')
            .doc(authResult.user!.uid)
            .set({
          'username': username,
          'email': email,
          'image_url': imageUrl, // 新增图片URL存储
        });
      }
    } on PlatformException catch (err) {
      var message = '发生错误,请检查您的账号信息';
      if (err.message != null) {
        message = err.message!;
      }
      final snackBar = SnackBar(
        content: Text(message),
        backgroundColor: Theme.of(ctx).errorColor,
      );
      ScaffoldMessenger.of(ctx).showSnackBar(snackBar);
      setState(() {
        _isLoading = false;
      });
    } catch (err) {
      final snackBar = SnackBar(
        content: Text(err.toString()),
        backgroundColor: Theme.of(ctx).errorColor,
      );
      ScaffoldMessenger.of(ctx).showSnackBar(snackBar);
      setState(() {
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Theme.of(context).primaryColor,
      body: AuthForm(
        _submitAuthForm,
        _isLoading,
      ),
    );
  }
}

UserImagePicker修改后代码

// ignore_for_file: unnecessary_null_comparison

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

class UserImagePicker extends StatefulWidget {
  UserImagePicker(this.imagePickFn);

  final void Function(File pickedImage) imagePickFn;
  @override
  _UserImagePickerState createState() => _UserImagePickerState();
}

class _UserImagePickerState extends State<UserImagePicker> {
  File? _pickedImage;

  void _pickImage() async {
    final picker = ImagePicker();
    // 使用新版ImagePicker API
    final pickedImageFile = await picker.pickImage(
      source: ImageSource.camera,
      imageQuality: 80,
    );

    if (pickedImageFile == null) return;

    setState(() {
      _pickedImage = File(pickedImageFile.path);
    });

    widget.imagePickFn(_pickedImage!);
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        CircleAvatar(
          radius: 50,
          backgroundColor: Colors.grey,
          backgroundImage:
              _pickedImage != null ? FileImage(_pickedImage!) : null,
        ),
        TextButton.icon(
            style: TextButton.styleFrom(
              primary: Theme.of(context).primaryColor,
            ),
            onPressed: _pickImage,
            icon: Icon(Icons.image),
            label: Text('添加图片')),
      ],
    );
  }
}

内容的提问来源于stack exchange,提问作者Placido Penitente

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 21:45:30