开始使用 AWS

构建 Flutter 应用程序

使用 AWS Amplify 创建简单的 Flutter 应用程序

模块 4:添加图像存储功能

在本模块中,您将添加存储以及将图像与您的应用程序中的备注关联的功能。

简介

现在,只有经过身份验证的用户才能进入应用程序,我们可以允许用户拍照并将其上传到我们应用程序 Amazon S3 存储桶中的私有文件夹中。

在此模块中,我们将 Storage 类别添加到您的 Amplify 应用程序中,上传通过设备摄像头拍摄的照片,然后下载并显示与单个用户关联的所有照片。

您将学到的内容

  • 配置 Storage 类别
  • 将文件上传到 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

    当被问及经过身份验证的用户应具有哪种访问权限时,请按 A 键选择创建/更新、读取和删除:

    ? 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)

    它表明正在创建 Storage 类别,并且身份验证与上一个模块中的设置没有任何变化。

    在后端完成配置 Storage 资源后,我们将看到成功输出:

    ✔ All resources are updated in the cloud
  • 安装依赖项

    接下来,在 Visual Studio Code 中打开 pubspec.yaml 文件,将 Storage 插件添加为依赖项:

    ... # 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,以便可以将 Storage 作为插件添加到我们的 Amplify 实例上:

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

    运行应用程序。您应该仍然会在日志中看到成功消息,表明 Amplify 的配置仍是正确的,并且包含 Storage 插件。

    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. 接下来,我们要求 Storage 列出给定 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 作为实参,以便它可以观察从 Storage 检索的图像 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. 用户界面要求快照具有数据,以显示与用户相关的任何内容。
    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 存储桶的私有部分。

在下一个模块中,我们将添加分析。

此模块有帮助吗?

谢谢
请告知我们您喜欢什么。
关闭
很抱歉让您失望了
是否存在过时、令人困惑或不准确的内容? 请向我们提供反馈,帮助我们改进本教程。
关闭