AWS 入門

建置 Flutter 應用程式

使用 AWS Amplify 建立簡單的 Flutter 應用程式

第四單元︰新增儲存影像的功能

在本單元中,您將新增儲存體至應用程式,並讓影像能夠與應用程式中的筆記關聯。

簡介

現在,只有經過身份驗證的使用者才能進入應用程式,我們可以允許使用者拍照,並將其上傳至我們應用程式的 Amazon S3 儲存貯體私有資料夾。

在本單元中,我們將儲存體類別新增至您的 Amplify 應用程式,從裝置相機上傳圖片,然後下載並顯示與個別使用者關聯的所有相片。

您將學到的內容

  • 設定儲存體類別
  • 上傳檔案至 Amazon S3
  • 從 Amazon S3 下載檔案
  • 顯示和快取 URL 中的影像

主要概念

儲存體 - 儲存體的概念即能夠將檔案存放在某個位置,並在需要時擷取這些檔案。在此情況下,是指將影像存放在 Amazon S3 或從 Amazon S3 下載影像。

 完成時間

20 分鐘

 使用的服務

實作

  • 建立儲存服務

    透過在專案根目錄的終端機輸入以下命令,將儲存服務新增至 Amplify 專案:

    amplify add storage

    就像任何其他類別一樣,Amplify CLI 會提示您有關如何設定儲存服務的問題。我們將使用 enter 鍵,用預設答案回答大多數問題:

    ➜  photo_gallery git:(master) amplify add storage
    ? Please select from one of the below mentioned services: Content (Images, audio
    , video, etc.)
    
    ? Please provide a friendly name for your resource that will be used to label th
    is category in the project: s33daafe54
    ? Please provide bucket name: photogalleryf3fb7bda3f5d47939322aa3899275aab
    ? Who should have access: Auth users only

    當被問及經過身份驗證的使用者應具有哪種存取權限時,請按下按鍵選取建立/更新、讀取和刪除:

    ? What kind of access do you want for Authenticated users? create/update, read,
    delete

    然後繼續輸入預設答案,直至完全設定儲存資源:

    ? Do you want to add a Lambda Trigger for your S3 Bucket? No
    Successfully added resource s33daafe54 locally

    現在,我們需要將設定的儲存資源傳送給我們的支援者,以便保持同步。執行以下命令:

    amplify push

    Amplify CLI 將提供目前正在發生的變更的狀態報告:

    ✔ Successfully pulled backend environment dev from the cloud.
    
    Current Environment: dev
    
    | Category | Resource name        | Operation | Provider plugin   |
    | -------- | -------------------- | --------- | ----------------- |
    | Storage  | s33daafe54           | Create    | awscloudformation |
    | Auth     | photogallery42b5391b | No Change | awscloudformation |
    ? Are you sure you want to continue? (y/n)

    其顯示正在建立儲存類別,並且身份驗證與我們在上一個單元中的設定沒有任何變化。

    我們的儲存資源在後端完成設定後,我們將看到成功輸出:

    ✔ All resources are updated in the cloud
  • 安裝相依項

    接下來,在 Visual Studio 程式碼中開啟 pubspec.yaml 檔案,將儲存體外掛程式新增為相依項:

    ... # amplify_auth_cognito: '<1.0.0'
    
    amplify_storage_s3: '<1.0.0'
    
    ... # dev_dependencies:

    現在儲存檔案,以使 Visual Studio Code 安裝 Amplify Auth Cognito 外掛程式。如果未在儲存時安裝相依項,還可以從終端機執行 $ flutter pub get。

    您應取得以下輸出:

    exit code 0
  • 設定外掛程式

    現在導覽回到 main.dart,以便可以將儲存體作為外掛程式,新增至我們的 Amplify 執行個體:

    ... // void _configureAmplify() async {
    
    _amplify.addPlugin(
        authPlugins: [AmplifyAuthCognito()],
        storagePlugins: [AmplifyStorageS3()]);
    
    ... // try {

    執行應用程式。您仍應在日誌中看到成功訊息,表明 Amplify 仍正確設定並且包含儲存體外掛程式。

    flutter: Successfully configured Amplify 🎉
  • 實作功能

    為了使程式碼整理有序,我們來建立名稱為 storage_service.dart 的單獨檔案,該檔案將封裝用於上傳和下載檔案的邏輯。新增以下程式碼:

    import 'dart:async';
    import 'package:amplify_core/amplify_core.dart';
    import 'package:amplify_storage_s3/amplify_storage_s3.dart';
    
    class StorageService {
      // 1
      final imageUrlsController = StreamController<List<String>>();
    
      // 2
      void getImages() async {
        try {
          // 3
          final listOptions =
              S3ListOptions(accessLevel: StorageAccessLevel.private);
          // 4
          final result = await Amplify.Storage.list(options: listOptions);
    
          // 5
          final getUrlOptions =
              GetUrlOptions(accessLevel: StorageAccessLevel.private);
          // 6
          final List<String> imageUrls =
              await Future.wait(result.items.map((item) async {
            final urlResult =
                await Amplify.Storage.getUrl(key: item.key, options: getUrlOptions);
            return urlResult.url;
          }));
    
          // 7
          imageUrlsController.add(imageUrls);
        
        // 8
        } catch (e) {
          print('Storage List error - $e');
        }
      }
    }
    1. 我們首先初始化 StreamController,其將管理從 Amazon S3 擷取的影像 URL。
    2. 該功能將啟動擷取需要在 GalleryPage 中顯示之影像的程序。
    3. 由於我們只想顯示使用者上傳的相片,因此我們將存取級別指定為 StorageAccessLevel.private,以確保使用者的私人照片保持私有狀態。
    4. 接下來,我們要求儲存體列出給定 S3ListOptions 的所有相關相片。
    5. 如果清單結果成功,我們需要取得每張相片的實際下載 URL,因為清單結果僅包含金鑰清單,而不包含相片的實際 URL。
    6. 我們使用 .map 反覆查看清單結果中的每個項目,並以非同步方式返回每個項目的下載 URL。
    7. 最後,我們僅將 URL 清單傳送至要觀察的串流。
    8. 如果在執行過程中出現任何錯誤,我們只需輸出該錯誤。

    在 iOS 上,為確保應用程式可以下載影像,我們需要在 Info.plist (ios > Runner > Info.plist) 中更新 App Transport Security:

    ... <!-- <string>Need to take pictures</string> -->
    
    <key>NSAppTransportSecurity</key>
    <dict>
       <key>NSAllowsArbitraryLoads</key>
       <true/>
    </dict>
    
    ... <!-- </dict> -->

    如果您還沒有上傳任何內容,嘗試從 S3 列出影像 URL 是沒有意義的,因此我們來新增一個上傳影像的功能:

    // 1
    void uploadImageAtPath(String imagePath) async {
     final imageFile = File(imagePath);
     // 2
     final imageKey = '${DateTime.now().millisecondsSinceEpoch}.jpg';
    
     try {
       // 3
       final options = S3UploadFileOptions(
           accessLevel: StorageAccessLevel.private);
    
       // 4
       await Amplify.Storage.uploadFile(
           local: imageFile, key: imageKey, options: options);
    
       // 5
       getImages();
     } catch (e) {
       print('upload error - $e');
     }
    }
    1. 這將是一個非同步功能,採用相機提供的影像路徑。
    2. 為了確保相片具有唯一金鑰,我們將使用時間戳記為金鑰。
    3. 如實作 getImages 時所述,我們需要將存取級別指定為 StorageAccessLevel.private,以便使用者將影像上傳至 S3 儲存貯體中自己的資料夾。
    4. 然後,我們只需上傳檔案,指定其金鑰並上傳檔案選項。
      最後,我們將叫用 getImages,以取得影像 URL 的最新清單並將其傳送至下游。
    5. 我們已經完成了上傳和下載工作所需的所有編碼。現在,我們需要連線所有內容並對其進行測試。

    我們首先更新 GalleryPage,將 StreamController 作為參數,以便其可以觀察從儲存體擷取的影像 URL。

    ... // class GalleryPage extends StatelessWidget {
    
    final StreamController<List<String>> imageUrlsController;
    
    ... // final VoidCallback shouldLogOut;
    ... // final VoidCallback shouldShowCamera;
    
    GalleryPage(
       {Key key,
       this.imageUrlsController,
       this.shouldLogOut,
       this.shouldShowCamera})
        : super(key: key);
    
    ... // @override

    接下來更新 _galleryGrid,以返回 StreamBuilder 而非 GridView.builder:

    Widget _galleryGrid() {
     return StreamBuilder(
         // 1
         stream: imageUrlsController.stream,
         builder: (context, snapshot) {
           // 2
           if (snapshot.hasData) {
             // 3
             if (snapshot.data.length != 0) {
               return GridView.builder(
                   gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                       crossAxisCount: 2),
                   // 4
                   itemCount: snapshot.data.length,
                   itemBuilder: (context, index) {
                     return CachedNetworkImage(
                       imageUrl: snapshot.data[index],
                       fit: BoxFit.cover,
                       placeholder: (context, url) => Container(
                           alignment: Alignment.center,
                           child: CircularProgressIndicator()),
                     );
                   });
             } else {
               // 5
               return Center(child: Text('No images to display.'));
             }
           } else {
             // 6
             return Center(child: CircularProgressIndicator());
           }
         });
    }
    1. StreamBuilder 將使用將從 StorageService 傳入的 imageUrlsController 來提供資料快照。
    2. UI 要求快照具有資料,以顯示與使用者相關的任何內容。
    3. 我們還需要確定資料是否實際包含項目。如果是這樣,那麼我們將繼續建置 GridView。
    4. 現在,我們可以使用快照中資料的長度來設定 GridView 的大小,而非使用硬編碼的數字。
    5. 如果快照中沒有任何項目,我們將顯示一些文字,表明沒有可顯示的內容。

    現在,我們仍在為網格中的每個項目顯示一個預留位置。我們將需要從串流提供的 URL 下載每個影像。為了簡化操作,我們在 pubspec.yaml 中新增一個相依項:

    ... # amplify_storage_s3: '<1.0.0'
    
    cached_network_image: ^2.3.3
    
    ... # dev_dependencies:

    該程式庫提供了一個小工具,只需接收 URL 即可下載和快取影像。儲存變更並導覽回到 GalleryPage,以便我們取得預留位置:

    ... // itemBuilder: (context, index) {
    
    return CachedNetworkImage(
      imageUrl: snapshot.data[index],
      fit: BoxFit.cover,
      placeholder: (context, url) => Container(
          alignment: Alignment.center,
          child: CircularProgressIndicator()),
    );
    
    ... // itemBuilder closing });

    預留位置已被 CachedNetworkImage 取代,其從快照傳遞 URL,並透過 itemBuilder 進行索引。載入影像時,小工具將顯示 CircularProgressIndicator。

    現在,我們可以將 GalleryPage 和 CameraPage 連線至 _CameraFlowState 中的 StorageService。首先,建立一個屬性來儲存 StorageService 的執行個體:

    ... // bool _shouldShowCamera = false;
    
    StorageService _storageService;
    
    ... // List<MaterialPage> get _pages {

    接著,在 initState 方法中初始化 _storageService:

    ... // _getCamera();
    
    _storageService = StorageService();
    _storageService.getImages();
    
    ... // initState closing }

    初始化 StorageService 後,我們將叫用 getImages,以擷取所有上傳的影像。

    現在,將 StreamController 傳遞給 GalleryPage:

    ... // child: GalleryPage(
        
    imageUrlsController: _storageService.imageUrlsController,
    
    ... // shouldLogOut: widget.shouldLogOut,

    最後,在拍照後更新 CameraPage 的 didProvideImagePath 功能:

    ... // this._toggleCameraOpen(false);
    
    this._storageService.uploadImageAtPath(imagePath);
    
    ... // didProvideImagePath closing }

    就這麼簡單! 我們可隨時開始使用我們的應用程式拍照並將其上傳至 S3。

    嘗試建置並執行!

    EndofModule4-gif

結論

太棒了! 該應用程式的核心功能已實作,使用者可以拍照,這會存放在該應用程式 S3 儲存貯體存的私有區域。

在下一個單元中,我們將新增分析。

這個單元對您是否有幫助?

感謝您
請告訴我們您喜歡的部分。
關閉
抱歉,讓您失望
是有內容過時、令人困擾,或不準確嗎? 請提供意見回饋,協助我們改進此教學課程。
關閉