Introducción a AWS

Crear una aplicación Flutter

Crear una aplicación Flutter sencilla con AWS Amplify

Módulo 4: agregar la capacidad de almacenar imágenes

En este módulo, agregará almacenamiento y la capacidad de asociar una imagen con las notas en su aplicación.

Introducción

Ahora que solo los usuarios autenticados pueden ingresar a la aplicación, podemos permitir que el usuario tome fotografías y las cargue en una carpeta privada en nuestro bucket de Amazon S3 de la aplicación.

En este módulo, agregaremos la categoría de almacenamiento a su aplicación de Amplify, cargaremos fotografías tomadas desde la cámara del dispositivo y luego descargaremos y mostraremos todas las fotografías asociadas con un usuario individual.

Lo que aprenderá

  • Configurar la categoría de almacenamiento
  • Cargar archivos en Amazon S3
  • Descargar archivos de Amazon S3
  • Mostrar y almacenar en caché imágenes de una URL

Conceptos clave

Almacenamiento: el concepto de almacenamiento es poder guardar archivos en una ubicación y recuperar esos archivos cuando sea necesario. En este caso, almacenar y descargar imágenes de Amazon S3.

 Tiempo de realización

20 minutos

 Servicios utilizados

Implementación

  • Crear el servicio de almacenamiento

    Agregue el servicio de almacenamiento al proyecto Amplify mediante el ingreso del siguiente comando en la terminal en el directorio raíz del proyecto:

    amplify add storage

    Como en cualquier otra categoría, la CLI de Amplify le hará preguntas sobre cómo desea configurar su servicio de almacenamiento. Utilizaremos la tecla Enter (Intro) para responder a la mayoría de las preguntas con la respuesta predeterminada.

    ➜  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

    Cuando se le pregunte qué tipo de acceso tendrán los usuarios autenticados, presione la tecla “A” para seleccionar crear/actualizar, leer y eliminar.

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

    Luego continúe ingresando las respuestas predeterminadas hasta que el recurso de almacenamiento esté configurado por completo.

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

    Ahora tenemos que enviar el recurso de almacenamiento ya configurado a nuestro backend para estar sincronizados. Ejecute el siguiente comando:

    amplify push

    La CLI de Amplify le dará un informe de estado de los cambios que se realicen.

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

    Le muestra que la categoría de almacenamiento está siendo creada y que la autenticación no presenta cambios desde nuestra configuración en el módulo previo.

    Una vez que haya finalizado la configuración de nuestro recurso de almacenamiento en el backend, veremos un mensaje de éxito:

    ✔ All resources are updated in the cloud
  • Instalar la dependencia

    A continuación, abra el archivo pubspec.yaml en Visual Studio Code para agregar el complemento de almacenamiento como una dependencia.

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

    Ahora, guarde el archivo para que Visual Studio Code instale el complemento Cognito de autenticación de Amplify. También puede ejecutar $ flutter pub get desde la terminal si la dependencia no está instalada al guardar.

    Debería obtener un resultado de:

    exit code 0
  • Configurar el complemento

    Ahora regrese a main.dart para que se pueda agregar el almacenamiento como un complemento en nuestra instancia de Amplify.

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

    Ejecutar la aplicación. Debería seguir viendo el mensaje de éxito en los registros que indica que Amplify está bien configurado y el complemento de almacenamiento está incluido.

    flutter: Successfully configured Amplify 🎉
  • Implementar la funcionalidad

    Para que nuestro código esté organizado, vamos a crear un archivo aparte con el nombre storage_service.dart que va a encapsular la lógica para cargar y descargar archivos. Agregue el siguiente código:

    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. Comenzamos por iniciar un StreamController que administrará los URL de imágenes que se recuperan de Amazon S3.
    2. Esta función iniciará el proceso de búsqueda de las imágenes que deben mostrarse en la GalleryPage.
    3. Dado que solo queremos mostrar las fotos que cargó el usuario, especificamos el nivel de acceso como StorageAccessLevel.private, a fin de garantizar que las imágenes privadas de nuestros usuarios permanezcan en privado.
    4. Luego, le solicitamos al almacenamiento que haga una lista de todas las fotos pertinentes según S3ListOptions.
    5. Si los resultados de la lista son exitosos, debemos obtener el URL de descarga real de cada foto, ya que el resultado de la lista solo incluye una lista de claves y no el URL real de la foto.
    6. Utilizamos .map para repetir cada elemento de los resultados de la lista y devolver de forma asincrónica el URL de descarga de cada elemento.
    7. Por último, solo enviamos la lista de URL para que se observe en la secuencia.
    8. Si hay errores en el camino, simplemente imprimimos un error.

    En iOS, a fin de garantizar que la aplicación pueda descargar las imágenes, tenemos que actualizar la seguridad de transporte de la aplicación en Info.plist (ios > Runner > Info.plist).

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

    No tiene sentido intentar hacer una lista de URL de imágenes desde S3 si no ha cargado nada, por eso vamos a agregar una función para cargar imágenes:

    // 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. Esta será una función asincrónica que toma la ruta de una imagen brindada por la cámara.
    2. A fin de garantizar que la foto tenga una clave única, utilizaremos una marca temporal como la clave.
    3. Como se indicó al implementar getImages, debemos especificar el nivel de acceso como StorageAccessLevel.private para que el usuario cargue las imágenes a su propia carpeta en el bucket de S3.
    4. A continuación, solo cargamos el archivo e indicamos su clave y sus opciones de carga de archivos.
      Por último, llamamos a getImages para que obtenga la última lista de URL de imágenes y la enviamos a la secuencia.
    5. Hemos finalizado toda la codificación necesaria para comenzar a hacer cargas y descargas. Ahora debemos conectar todo y probarlo.

    Comencemos por actualizar GalleryPage para que tome a un StreamController como un argumento, así puede ver los URL de las imágenes recuperadas del almacenamiento.

    ... // 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

    Luego, actualice _galleryGrid a fin de recuperar un StreamBuilder en lugar de solo el 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. El StreamBuilder utilizará el imageUrlsController que llegará desde StorageService para brindar instantáneas de nuestros datos.
    2. La UI necesita que la instantánea tenga datos a fin de mostrar lo que sea pertinente para el usuario.
    3. También tenemos que determinar si los datos realmente tienen elementos. Si tienen elementos, pasamos a construir la GridView.
    4. En lugar de utilizar un número codificado de forma rígida, ahora podemos modificar el tamaño de GridView según el largo de los datos en nuestra instantánea.
    5. Si la instantánea no tiene elementos, presentaremos un texto que indique que no hay nada para mostrar.

    Ahora todavía mostramos un marcador de posición para cada elemento en la cuadrícula. Tenemos que descargar cada imagen desde el URL que nos proporciona la secuencia. Para hacer este proceso más simple, agreguemos una nueva dependencia en pubspec.yaml:

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

    Esta biblioteca proporciona un widget que puede descargar y almacenar una imagen tan solo al recibir un URL. Guarde los cambios y regrese a GalleryPage para que podamos reemplazar el marcador de posición:

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

    El marcador de posición se ha reemplazado con CachedNetworkImage, la cual ha pasado el URL desde la instantánea y está indexada a través del itemBuilder. Mientras carga la imagen, el widget mostrará un CircularProgressIndicator.

    Ahora podemos conectar GalleryPage y CameraPage al StorageService en _CameraFlowState. Comience por crear una propiedad para contener una instancia de StorageService:

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

    Luego, inicie _storageService en el método initState:

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

    Inmediatamente después de iniciar StorageService, llamamos a getImages para se recupere toda imagen descargada.

    Ahora pasemos el StreamController a la GalleryPage:

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

    Por último, actualice la función de didProvideImagePath de la CameraPage una vez que se tome una foto:

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

    ¡Eso es todo! Estamos listos para comenzar a tomar fotos con nuestra aplicación y cargarlas a S3.

    ¡Cree y ejecute la aplicación para probarlo!

    EndofModule4-gif

Conclusión

¡Increíble! Se implementó la funcionalidad principal de la aplicación y el usuario puede tomar fotografías que se almacenarán en una sección privada del bucket de S3 de la aplicación.

En el próximo módulo, agregaremos análisis.

¿Este módulo le resultó útil?

Gracias
Indíquenos lo que le gustó.
Cerrar
Lamentamos haberlo decepcionado
¿Hay información desactualizada, confusa o inexacta? Ayúdenos a mejorar este tutorial con sus comentarios.
Cerrar

Agregar análisis