開場廢話
Hello,大家好卷拘,很久沒來寫心得了喊废,一是最近自己工作比較忙,二是最近一直在折騰如何把之前給老婆做的diy活動app的內(nèi)容搬到云端栗弟。為什么要放在云端呢污筷,因為做產(chǎn)品的都懂的,用戶的需求是永遠(yuǎn)不會斷的乍赫。有一次老婆在用app記錄活動的時候問我瓣蛀,如果換手機或者手機壞了陆蟆,丟了數(shù)據(jù)還有嗎? 這惋增。叠殷。。因為之前我是用的本地數(shù)據(jù)庫诈皿,所以如果手機丟了壞了數(shù)據(jù)還真沒了林束。所以本著滿足用戶一切需求的想法,我想辦法給她把數(shù)據(jù)搬云端去稽亏。
廢話少說壶冒,先看東西
用蘋果的捷徑直接視頻轉(zhuǎn)gif的,所以清晰度可能有點差措左,大家見諒
最后結(jié)束的時候可能你發(fā)現(xiàn)突然少了一張依痊,那是我點擊的右上角X避除,想演示刪除操作的怎披。
一、圖片云端存儲方案
1瓶摆、需求分析
老婆要數(shù)據(jù)不丟失(這次我們先解決圖片的問題)
2凉逛、原來圖片存儲方式
之前是通過兩種方式存儲
1、每次復(fù)制一張圖片到app文檔目錄群井,然后把地址存入本地數(shù)據(jù)庫文件状飞。
2、直接把圖片原始數(shù)據(jù)uint8List數(shù)據(jù)存入數(shù)據(jù)庫
這里不討論這兩種方式的好壞书斜,關(guān)鍵問題是一旦手機丟或壞诬辈,那么圖片數(shù)據(jù)就都沒了。
3荐吉、為什么我選擇Firebase_storage對象存儲
網(wǎng)上查了很多資料焙糟,國內(nèi)的諸如阿里云、騰訊云雖然都有對象存儲样屠,但是都要依賴后端穿撮,是的,我就是一個自學(xué)flutter痪欲,不會后端的菜雞悦穿,而Firebase的對象存儲和數(shù)據(jù)庫都是幫你把后端服務(wù)給做好了的,你直接使用就可以业踢,只要簡單看下api就可以上手使用栗柒。
二、app添加Firebase以及使用的相關(guān)插件
(往下的內(nèi)容可能都需要科學(xué)上網(wǎng))
1知举、應(yīng)用配置
要在自己的app里應(yīng)用firebase體系的東西需要先進(jìn)行配置瞬沦,配置方法我這里就不說了深员,我推薦去firebase官方學(xué)習(xí)文檔里學(xué)習(xí)一下,都是中文說明蛙埂,所以應(yīng)該沒有困難倦畅。我這里貼一個官方地址教你如何進(jìn)行配置:
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
//網(wǎng)絡(luò)圖片加載插件,支持緩存
4惩嘉、multi_image_picker: ^2.2.55
//圖片多選插件
5罢洲、path_provider: ^0.4.0
//提供獲得應(yīng)用路徑
6、path: ^1.6.2
//路徑操作
三文黎、擼碼
擼碼前我建議大家去看下firebase_storage的幫助文檔惹苗,了解使用方法后理解更加深刻。
在開擼前我還是習(xí)慣先梳理業(yè)務(wù)流程耸峭,分為以下幾步:
1桩蓉、獲得你自己的對象存儲空間實例
2、獲得一個對象存儲空間的位置引用劳闹,用來存儲圖片院究,注意這個引用的位置需要包含你的文件完整名字。
3本涕、本地相冊選擇圖片业汰,然后你可以選擇壓縮后上傳,或者不壓縮直接上傳到對象存儲空間
4菩颖、上傳完成后獲得上傳圖片的下載地址(當(dāng)然還提供了獲得諸如文件位置样漆、名稱、大小等各類信息的方法)
5位他、將獲得圖片下載地址存入數(shù)據(jù)庫
業(yè)務(wù)核心邏輯代碼:
//圖片上傳任務(wù)列表
List<StorageUploadTask> _upLoadTask = [];
//存放firestorage返回的圖片下載地址
List<String> _imageUrl = [];
//存放壓縮后的圖片數(shù)據(jù)路徑
List<String> _imagePath = [];
//選擇圖片并上傳
_pickImageUpLoad() async {
//通過MultiImagePicker插件從本地相冊選取圖片氛濒,配置一次最多選擇12張,禁止攝像頭拍照
var requestList = await MultiImagePicker.pickImages(
maxImages: 12,
enableCamera: false,
);
if (!mounted) return;
//獲得目前上傳任務(wù)數(shù)量
int _taskNum = _upLoadTask.length;
//這里進(jìn)行一下判斷鹅髓,是否選擇了圖片舞竿,如果沒有選擇圖片不進(jìn)行任何操作。
if (requestList.length != 0) {
for (int i = 0; i < requestList.length; i++) {
//獲得一個uuud碼用于給圖片命名
final String uuid = Uuid().v1();
//請求原始圖片數(shù)據(jù)
await requestList[i].requestOriginal();
//獲取圖片數(shù)據(jù)窿冯,并轉(zhuǎn)換成uint8List類型
Uint8List imageData = requestList[i].imageData.buffer.asUint8List();
print('開始壓縮第$i張圖片');
//通過圖片壓縮插件進(jìn)行圖片壓縮
var result = await FlutterImageCompress.compressWithList(imageData,
quality: 100);
//獲得應(yīng)用臨時目錄路徑
final Directory _directory = await getTemporaryDirectory();
final Directory _imageDirectory =
await new Directory('${_directory.path}/image/')
.create(recursive: true);
_path = _imageDirectory.path;
print('本次獲得路徑:${_imageDirectory.path}');
//將壓縮的圖片暫時存入應(yīng)用緩存目錄
File imageFile = new File('${_path}originalImage_$uuid.png')
..writeAsBytesSync(result);
_imagePath.add(imageFile.path);
print('圖片$i的 本地路徑是:${imageFile.path}');
print('開始創(chuàng)建第$i個上傳任務(wù)');
//獲得對象存儲控件實例后獲得圖片引用地址
StorageReference storageReference =
FirebaseStorage.instance.ref().child('image').child('image_${uuid}.jpg');
//將圖片上傳至對象存儲空間
StorageUploadTask storageUploadTask =
storageReference.putFile(imageFile);
setState(() {
_upLoadTask.add(storageUploadTask);
});
//釋放圖片原始數(shù)據(jù)資源
requestList[i].releaseOriginal();
}
//根據(jù)上傳任務(wù)數(shù)量獲得上傳成功后的圖片下載地址
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');
}
}
}
以上代碼是業(yè)務(wù)流程的核心邏輯代碼
我基本都寫了注釋骗奖,在這里我再說幾點注意點:
1、上傳圖片的方法目前支持上傳file文件
putFile(File file)
和內(nèi)存數(shù)據(jù)putData(Uint8List data)
,建議使用上傳文件的方式执桌,因為上傳內(nèi)存數(shù)據(jù)的話需要圖片數(shù)據(jù)先在內(nèi)存中編譯完成鄙皇,可能導(dǎo)致內(nèi)存占用方面的問題。
2仰挣、我用的這個多圖選擇插件個人感覺是目前最好的一個伴逸,但是他返回的是圖片的內(nèi)存數(shù)據(jù),所以記得一旦不用的時候要釋放膘壶。
3错蝴、圖片上傳的時候是否可以對過程進(jìn)行操作和監(jiān)控呢?當(dāng)然可以颓芭。請往下看顷锰。
UI代碼
剛才上面的是邏輯層面的,我們需要繼續(xù)編寫UI內(nèi)容用于展示選擇圖片后的可以看到的界面變化亡问。就好像開頭效果展示一樣官紫,下面會顯示上傳任務(wù)的容器,容器底下還會顯示上傳狀態(tài)和上傳進(jìn)度的變化州藕。
分析:無論有多少個上傳任務(wù)束世,每個任務(wù)展示的UI其實是一樣的,如何展示只是形式的問題慎框,可能你喜歡豎著排良狈,他可能喜歡橫著排后添,這個看個人喜好笨枯。我這里主要放上單個上傳任務(wù)的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('準(zhǔn)備上傳');
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、該類構(gòu)造函數(shù)接收三個參數(shù)遇西,上傳任務(wù)task馅精,回調(diào)函數(shù)onCancel用于刪除上傳任務(wù),imagePath用于臨時顯示上傳任務(wù)的展示
2粱檀、get status
方法用于獲取上傳任務(wù)的狀態(tài)
3洲敢、StreamBuilder<StorageTaskEvent>
是一個上傳任務(wù)事件類型的數(shù)據(jù)流構(gòu)造方法,stream數(shù)據(jù)流就是任務(wù)事件茄蚯。通過判斷任務(wù)快照是否在進(jìn)行數(shù)據(jù)傳輸顯示任務(wù)狀態(tài)压彭。
4、最上層的ui就是一個stack層疊控件渗常,包裹著圖片顯示壮不,右上角的刪除任務(wù)按鈕和底下的帶有上傳狀態(tài)和進(jìn)度的控件。
總結(jié)
至此基本上上傳多個圖片到Firebase_storage就實現(xiàn)了皱碘,上傳后你可以在你的存儲空間看到上傳的圖片询一。當(dāng)然了后續(xù)業(yè)務(wù)還包括下載圖片、刪除圖片等,原理差不多健蕊,都是先獲得圖片在對象控件的引用菱阵,然后進(jìn)行相關(guān)操作,這里我就不多介紹缩功,感興趣的朋友可以自己操作一下晴及。
本文畢竟貼了核心代碼,所以如果你在操作的過程出現(xiàn)問題嫡锌,可以去我項目地址查看全部源碼抗俄。地址是:
https://gitee.com/xusujun33/activity_record_jia
本人是一個產(chǎn)品經(jīng)理自學(xué)開發(fā)的小學(xué)生,所以代碼簡陋世舰、錯誤難免动雹,還望各位海涵。下個目標(biāo)是把diy活動信息部分也搬到firebase上跟压,這樣就實現(xiàn)了所有數(shù)據(jù)的云端化胰蝠,再也不怕丟數(shù)據(jù)了。
不聊了震蒋,老婆又提新需求了茸塞,我趕緊溜了。