Flutter上传多张图片到Firebase对象存储空间

11 篇文章 1 订阅

用苹果的捷径直接视频转gif的,所以清晰度可能有点差,大家见谅

 

IMG_0516.GIF

 

最后结束的时候可能你发现突然少了一张,那是我点击的右上角X,想演示删除操作的。

一、图片云端存储方案

1、需求分析

老婆要数据不丢失(这次我们先解决图片的问题)

2、原来图片存储方式

之前是通过两种方式存储

1、每次复制一张图片到app文档目录,然后把地址存入本地数据库文件。
2、直接把图片原始数据uint8List数据存入数据库
这里不讨论这两种方式的好坏,关键问题是一旦手机丢或坏,那么图片数据就都没了。

3、为什么我选择Firebase_storage对象存储

网上查了很多资料,国内的诸如阿里云、腾讯云虽然都有对象存储,但是都要依赖后端,是的,我就是一个自学flutter,不会后端的菜鸡,而Firebase的对象存储和数据库都是帮你把后端服务给做好了的,你直接使用就可以,只要简单看下api就可以上手使用。

二、app添加Firebase以及使用的相关插件

(往下的内容可能都需要科学上网)
1、应用配置
要在自己的app里应用firebase体系的东西需要先进行配置,配置方法我这里就不说了,我推荐去firebase官方学习文档里学习一下,都是中文说明,所以应该没有困难。我这里贴一个官方地址教你如何进行配置:
https://firebase.google.com/docs/flutter/setup?authuser=0
2、本次需要使用的插件

1、firebase_storage: ^1.0.4 //firebase对象存储插件,核心插件
2、flutter_image_compress: ^0.2.3 //图片压缩插件。非必选
3、cached_network_image: ^0.5.1 //网络图片加载插件,支持缓存
4、multi_image_picker: ^2.2.55 //图片多选插件
5、path_provider: ^0.4.0 //提供获得应用路径
6、path: ^1.6.2 //路径操作

三、撸码

撸码前我建议大家去看下firebase_storage的帮助文档,了解使用方法后理解更加深刻。
在开撸前我还是习惯先梳理业务流程,分为以下几步:

1、获得你自己的对象存储空间实例
2、获得一个对象存储空间的位置引用,用来存储图片,注意这个引用的位置需要包含你的文件完整名字。
3、本地相册选择图片,然后你可以选择压缩后上传,或者不压缩直接上传到对象存储空间
4、上传完成后获得上传图片的下载地址(当然还提供了获得诸如文件位置、名称、大小等各类信息的方法)
5、将获得图片下载地址存入数据库

业务核心逻辑代码:

  //图片上传任务列表
  List<StorageUploadTask> _upLoadTask = [];
  //存放firestorage返回的图片下载地址
  List<String> _imageUrl = [];
  //存放压缩后的图片数据路径
  List<String> _imagePath = [];
  //选择图片并上传
  _pickImageUpLoad() async {
    //通过MultiImagePicker插件从本地相册选取图片,配置一次最多选择12张,禁止摄像头拍照
    var requestList = await MultiImagePicker.pickImages(
      maxImages: 12,
      enableCamera: false,
    );
    if (!mounted) return;
    //获得目前上传任务数量
    int _taskNum = _upLoadTask.length;
    //这里进行一下判断,是否选择了图片,如果没有选择图片不进行任何操作。
    if (requestList.length != 0) {
      for (int i = 0; i < requestList.length; i++) {
        //获得一个uuud码用于给图片命名
        final String uuid = Uuid().v1();
        //请求原始图片数据
        await requestList[i].requestOriginal();
        //获取图片数据,并转换成uint8List类型
        Uint8List imageData = requestList[i].imageData.buffer.asUint8List();
        print('开始压缩第$i张图片');
        //通过图片压缩插件进行图片压缩
        var result = await FlutterImageCompress.compressWithList(imageData,
            quality: 100);
        //获得应用临时目录路径
        final Directory _directory = await getTemporaryDirectory();
        final Directory _imageDirectory =
            await new Directory('${_directory.path}/image/')
                .create(recursive: true);
        _path = _imageDirectory.path;
        print('本次获得路径:${_imageDirectory.path}');
        //将压缩的图片暂时存入应用缓存目录
        File imageFile = new File('${_path}originalImage_$uuid.png')
          ..writeAsBytesSync(result);
        _imagePath.add(imageFile.path);
        print('图片$i的 本地路径是:${imageFile.path}');
        print('开始创建第$i个上传任务');
        //获得对象存储控件实例后获得图片引用地址
        StorageReference storageReference =
            FirebaseStorage.instance.ref().child('image').child('image_${uuid}.jpg');
        //将图片上传至对象存储空间
        StorageUploadTask storageUploadTask =
            storageReference.putFile(imageFile);
        setState(() {
          _upLoadTask.add(storageUploadTask);
        });
        //释放图片原始数据资源
        requestList[i].releaseOriginal();
      }
      //根据上传任务数量获得上传成功后的图片下载地址
      for (int i = _taskNum == 0 ? 0 : (_upLoadTask.length - _taskNum);
          i < _upLoadTask.length;
          i++) {
        StorageTaskSnapshot snapshot = await _upLoadTask[i].onComplete;
        String uri = await snapshot.ref.getDownloadURL();
        _imageUrl.add(uri);
        print('上传图片返回的url:$uri');
      }
    }
  }

以上代码是业务流程的核心逻辑代码
我基本都写了注释,在这里我再说几点注意点:

1、上传图片的方法目前支持上传file文件putFile(File file)和内存数据putData(Uint8List data),建议使用上传文件的方式,因为上传内存数据的话需要图片数据先在内存中编译完成,可能导致内存占用方面的问题。
2、我用的这个多图选择插件个人感觉是目前最好的一个,但是他返回的是图片的内存数据,所以记得一旦不用的时候要释放。
3、图片上传的时候是否可以对过程进行操作和监控呢?当然可以。请往下看。

UI代码

刚才上面的是逻辑层面的,我们需要继续编写UI内容用于展示选择图片后的可以看到的界面变化。就好像开头效果展示一样,下面会显示上传任务的容器,容器底下还会显示上传状态和上传进度的变化。

分析:无论有多少个上传任务,每个任务展示的UI其实是一样的,如何展示只是形式的问题,可能你喜欢竖着排,他可能喜欢横着排,这个看个人喜好。我这里主要放上单个上传任务的UI
upload_task.dart

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

import 'package:transparent_image/transparent_image.dart';

class UpLoadTaskContainer extends StatelessWidget {
  UpLoadTaskContainer({
    this.task,
//    this.onDismissed,
    this.onCancel,
    this.imagePath,
//    this.index,
//    this.url
//    this.getUrl,
  });

  final StorageUploadTask task;
//  final VoidCallback onDismissed;
  final VoidCallback onCancel;
//  final String url;
  final String imagePath;
//  final int index;
//  final VoidCallback getUrl;

  get status {
    String result;
    if (task.isComplete) {
      if (task.isSuccessful) {
        result = '成功';
      } else if (task.isCanceled) {
        result = '取消';
      } else
        result = '失败:${task.lastSnapshot.error}';
    } else if (task.isPaused) {
      result = '暂停';
    } else if (task.isInProgress) {
      result = '上传中';
    }
    return result;
  }

  _bytePercent(StorageTaskSnapshot snapshot) {
    return '${((snapshot.bytesTransferred / snapshot.totalByteCount) * 100).toInt()}%';
  }

  @override
  Widget build(BuildContext context) {
    return new StreamBuilder<StorageTaskEvent>(
      stream: task.events,
      builder: (BuildContext context,
          AsyncSnapshot<StorageTaskEvent> asyncSnapShot) {
        Widget subtitle;
        if (asyncSnapShot.hasData) {
          StorageTaskEvent event = asyncSnapShot.data;
          StorageTaskSnapshot snapshot = event.snapshot;
          subtitle = new Text(
            '$status:${_bytePercent(snapshot)}',
            style: new TextStyle(color: Colors.white),
          );
        } else
          subtitle = new Text('准备上传');
        return new Stack(
          alignment: Alignment.bottomCenter,
          children: <Widget>[
            new FadeInImage(
              placeholder: new MemoryImage(kTransparentImage),
              image: AssetImage(
                imagePath,
              ),
              fit: BoxFit.cover,
              width: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
              height: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
            ),
            new Positioned(
              right: 0.08,
              top: 0.08,
              child: new GestureDetector(
                onTap: onCancel,
                child: new Container(
                  decoration: new BoxDecoration(
                    color: Colors.black45,
                    shape: BoxShape.circle,
                  ),
                  child: new Icon(
                    Icons.close,
                    color: Colors.white,
                    size: 20.0,
                  ),
                ),
              ),
            ),
            new Positioned(
                child: new Container(
              alignment: Alignment.center,
              height: 20.0,
              width: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
              color: Colors.black45,
              child: subtitle,
            )),
          ],
        );
      },
    );
  }
}

代码简析:

1、该类构造函数接收三个参数,上传任务task,回调函数onCancel用于删除上传任务,imagePath用于临时显示上传任务的展示
2、get status方法用于获取上传任务的状态
3、StreamBuilder<StorageTaskEvent>是一个上传任务事件类型的数据流构造方法,stream数据流就是任务事件。通过判断任务快照是否在进行数据传输显示任务状态。
4、最上层的ui就是一个stack层叠控件,包裹着图片显示,右上角的删除任务按钮和底下的带有上传状态和进度的控件。

总结

至此基本上上传多个图片到Firebase_storage就实现了,上传后你可以在你的存储空间看到上传的图片。当然了后续业务还包括下载图片、删除图片等,原理差不多,都是先获得图片在对象控件的引用,然后进行相关操作,这里我就不多介绍,感兴趣的朋友可以自己操作一下。

本文毕竟贴了核心代码,所以如果你在操作的过程出现问题,可以去我项目地址查看全部源码。地址是:
https://gitee.com/xusujun33/activity_record_jia
本人是一个产品经理自学开发的小学生,所以代码简陋、错误难免,还望各位海涵。下个目标是把diy活动信息部分也搬到firebase上,这样就实现了所有数据的云端化,再也不怕丢数据了。

不聊了,老婆又提新需求了,我赶紧溜了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值