开始使用 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'); } } }
- 我们首先初始化一个 StreamController,用于管理从 Amazon S3 检索的图像 URL。
- 此功能将启动获取需要在 GalleryPage 中显示的图像的过程。
- 由于我们只想显示用户上传的照片,因此我们将访问级别指定为 StorageAccessLevel.private,以确保用户的私人照片保持私密状态。
- 接下来,我们要求 Storage 列出给定 S3ListOptions 的所有相关照片。
- 如果列表结果成功,则我们需要获取每张照片的实际下载 URL,因为列表结果仅包含键列表,而不包含照片的实际 URL。
- 我们使用 .map 遍历列表结果中的每个项目,并异步返回每个项目的下载 URL。
- 最后,我们仅将 URL 列表发送到要观察的流中。
- 如果在执行过程中出现任何错误,我们将简单地打印出一个错误。
在 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'); } }
- 这将是一个异步函数,它采用相机提供的图像路径。
- 为了确保照片具有唯一的键,我们将使用时间戳作为键。
- 如实施 getImages 时所述,我们需要将访问级别指定为 StorageAccessLevel.private,以便用户将图像上传到 S3 存储桶中其自己的文件夹。
- 然后,我们只需指定其键和上传文件选项即可上传文件。
最后,我们调用 getImages 来获得图像 URL 的最新列表并将其发送到下游。 - 我们已经完成了上传和下载工作所需的所有编码。现在我们需要将所有项连接起来并对其进行测试。
我们首先更新 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()); } }); }
- StreamBuilder 将使用从 StorageService 传入的 imageUrlsController 来提供有关我们数据的快照。
- 用户界面要求快照具有数据,以显示与用户相关的任何内容。
- 我们还需要确定数据是否实际包含项目。如果包含项目,那么我们继续构建 GridView。
- 现在,我们可以根据快照中数据的长度来确定 GridView 的大小,而不必使用硬编码数字。
- 如果快照中没有任何项目,我们将显示一些文本,表明没有内容可显示。
现在,我们仍在为网格中的每个项目显示一个占位符。我们将需要从流提供的 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。
构建并运行,尝试一下!