开场废话
Hello,大家好,很久没来写心得了,一是最近自己工作比较忙,二是最近一直在折腾如何把之前给老婆做的diy活动app的内容搬到云端。为什么要放在云端呢,因为做产品的都懂的,用户的需求是永远不会断的。有一次老婆在用app记录活动的时候问我,如果换手机或者手机坏了,丢了数据还有吗? 这。。。因为之前我是用的本地数据库,所以如果手机丢了坏了数据还真没了。所以本着满足用户一切需求的想法,我想办法给她把数据搬云端去。
废话少说,先看东西
用苹果的捷径直接视频转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上,这样就实现了所有数据的云端化,再也不怕丢数据了。
不聊了,老婆又提新需求了,我赶紧溜了。