Flutter上傳多張圖片到Firebase對象存儲空間

開場廢話

Hello,大家好卷拘,很久沒來寫心得了喊废,一是最近自己工作比較忙,二是最近一直在折騰如何把之前給老婆做的diy活動app的內(nèi)容搬到云端栗弟。為什么要放在云端呢污筷,因為做產(chǎn)品的都懂的,用戶的需求是永遠(yuǎn)不會斷的乍赫。有一次老婆在用app記錄活動的時候問我瓣蛀,如果換手機或者手機壞了陆蟆,丟了數(shù)據(jù)還有嗎? 這惋增。叠殷。。因為之前我是用的本地數(shù)據(jù)庫诈皿,所以如果手機丟了壞了數(shù)據(jù)還真沒了林束。所以本著滿足用戶一切需求的想法,我想辦法給她把數(shù)據(jù)搬云端去稽亏。

廢話少說壶冒,先看東西

用蘋果的捷徑直接視頻轉(zhuǎn)gif的,所以清晰度可能有點差措左,大家見諒


IMG_0516.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ù)了。

不聊了震蒋,老婆又提新需求了茸塞,我趕緊溜了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末查剖,一起剝皮案震驚了整個濱河市钾虐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌笋庄,老刑警劉巖效扫,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異直砂,居然都是意外死亡菌仁,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門静暂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來济丘,“玉大人,你說我怎么就攤上這事洽蛀∧∶裕” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵郊供,是天一觀的道長峡碉。 經(jīng)常有香客問我,道長颂碘,這世上最難降的妖魔是什么异赫? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任椅挣,我火速辦了婚禮,結(jié)果婚禮上塔拳,老公的妹妹穿的比我還像新娘鼠证。我一直安慰自己,他們只是感情好靠抑,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布量九。 她就那樣靜靜地躺著,像睡著了一般颂碧。 火紅的嫁衣襯著肌膚如雪荠列。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天载城,我揣著相機與錄音肌似,去河邊找鬼。 笑死诉瓦,一個胖子當(dāng)著我的面吹牛川队,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播睬澡,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼固额,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了煞聪?” 一聲冷哼從身側(cè)響起斗躏,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昔脯,沒想到半個月后啄糙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡栅干,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年迈套,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碱鳞。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖踱蛀,靈堂內(nèi)的尸體忽然破棺而出窿给,到底是詐尸還是另有隱情,我是刑警寧澤率拒,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布崩泡,位于F島的核電站,受9級特大地震影響猬膨,放射性物質(zhì)發(fā)生泄漏角撞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谒所。 院中可真熱鬧热康,春花似錦、人聲如沸劣领。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尖淘。三九已至奕锌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間村生,已是汗流浹背惊暴。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留趁桃,地道東北人缴守。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像镇辉,于是被迫代替她去往敵國和親屡穗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,905評論 2 89
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,090評論 1 32
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫忽肛、插件村砂、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,059評論 4 62
  • Pod Unable to find a specification for `SGQRCode` podrepo...
    adalillian閱讀 154評論 0 0
  • 從前我在一個酒店兼職 去吃飯。廚師就會欺負(fù)外來兼職的 從前我不喜歡吃牛肉 但是我不知道那是牛肉 他就當(dāng)所有人 呵斥...
    meng1234閱讀 176評論 0 0