Conceitos básicos da AWS

Criar uma aplicação Flutter

Criar uma aplicação Flutter simples usando o AWS Amplify

Módulo 4: Adicionar a capacidade de armazenar imagens

Neste módulo, você adicionará armazenamento e a capacidade de associar imagens às notas no aplicativo.

Introdução

Agora que apenas usuários autenticados podem entrar no aplicativo, podemos permitir que o usuário tire fotos e faça upload delas em uma pasta privada no bucket do Amazon S3 do nosso aplicativo.

Neste módulo, vamos adicionar a categoria de armazenamento ao seu aplicativo Amplify, fazer upload de fotos tiradas da câmera do dispositivo e, em seguida, fazer download e exibir todas as fotos associadas a um usuário individual.

O que você aprenderá

  • Configurar a categoria de armazenamento
  • Fazer upload de arquivos para o Amazon S3
  • Fazer download de arquivos do Amazon S3
  • Exibir e armazenar em cache imagens de um URL

Conceitos principais

Armazenamento – o conceito de armazenamento é ser capaz de armazenar arquivos em um local e recuperá-los quando necessário. Nesse caso, armazenamento e download de imagens do Amazon S3.

 Tempo para a conclusão

20 minutos

 Serviços usados

Implementação

  • Criar o serviço de armazenamento

    Adicione o serviço de armazenamento ao projeto do Amplify inserindo o seguinte comando no terminal no diretório raiz do projeto:

    amplify add storage

    Assim como qualquer outra categoria, a CLI do Amplify fará perguntas sobre como você deseja configurar o serviço de armazenamento. Usaremos a tecla Enter para responder à maioria das perguntas com a resposta padrão:

    ➜  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

    Quando questionado sobre que tipo de acesso os usuários autenticados devem ter, pressione a tecla para selecionar criar/atualizar, ler e excluir:

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

    Em seguida, continue inserindo as respostas padrão até que o recurso de armazenamento esteja totalmente configurado:

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

    Agora, precisamos enviar o recurso de armazenamento configurado para o nosso backup para ficarmos em sincronia. Execute o seguinte comando:

    amplify push

    A CLI do Amplify fornecerá um relatório de status sobre quais mudanças estão ocorrendo:

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

    Isso mostra que a categoria de armazenamento está sendo criada e a autenticação não teve nenhuma alteração de configuração no módulo anterior.

    Assim que terminar a configuração do nosso recurso de armazenamento no back-end, veremos que o resultado foi bem-sucedido:

    ✔ All resources are updated in the cloud
  • Instalar a dependência

    Em seguida, abra o arquivo pubspec.yaml no código do Visual Studio para adicionar o plug-in de armazenamento como uma dependência:

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

    Agora salve o arquivo para que o Visual Studio Code instale o plug-in Amplify Auth Cognito. Você também pode executar o comando $ flutter pub get no terminal se a dependência não estiver instalada depois de salvar.

    O resultado deve ser este:

    exit code 0
  • Configurar o plug-in

    Agora navegue de volta para main.dart para que o armazenamento possa ser adicionado como um plug-in em nossa instância do Amplify:

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

    Execute o aplicativo. Você ainda deve ver a mensagem de conclusão com êxito nos logs, indicando que o Amplify continua devidamente configurado e inclui o plug-in de armazenamento.

    flutter: Successfully configured Amplify 🎉
  • Implementar funcionalidade

    Para manter nosso código organizado, vamos criar um arquivo separado denominado storage_service.dart que encapsulará a lógica de upload e download de arquivos. Adicione o seguinte 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. Começamos inicializando um StreamController que gerenciará os URLs de imagem recuperados do Amazon S3.
    2. Essa função iniciará o processo de busca das imagens que precisam ser exibidas na GalleryPage.
    3. Como queremos mostrar apenas as fotos enviadas pelo usuário, especificamos o nível de acesso como StorageAccessLevel.private para garantir a privacidade das fotos particulares dos nossos usuários.
    4. Em seguida, solicitamos que o armazenamento liste todas as fotos relevantes segundo S3ListOptions.
    5. Se o resultado da lista tiver êxito, precisaremos do URL de download real de cada foto, pois o resultado da lista contém apenas uma lista de chaves, e não o URL real da foto.
    6. Usamos .map para interagir com cada item no resultado da lista e retornar de maneira assíncrona a URL de download de cada item.
    7. Por fim, enviamos a lista de URLs na sequência para serem observados.
    8. Se houver algum erro no caminho, bastará imprimirmos um erro.

    No iOS, para garantir que o aplicativo possa fazer download das imagens, precisamos atualizar o App Transport Security em Info.plist (ios > Runner > Info.plist):

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

    É inútil tentar listar URLs de imagens do S3 se você não fez upload de nada. Vamos então adicionar uma função para fazer upload das imagens:

    // 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á uma função assíncrona que usa um caminho de imagem fornecido pela câmera.
    2. Para garantir que a foto tenha uma chave exclusiva, usaremos um carimbo de data/hora como chave.
    3. Conforme declarado durante a implementação de getImages, precisamos especificar o nível de acesso como StorageAccessLevel.private para que o usuário faça upload das imagens em sua própria pasta no bucket do S3.
    4. Em seguida, fazemos upload do arquivo especificando a chave e fazemos upload das opções de arquivo.
      Por último, chamamos getImages para obter a lista mais recente de URLs de imagens e as encaminhamos.
    5. Concluímos toda a codificação necessária para que nossos uploads e downloads funcionem. Agora precisamos conectar tudo e testar.

    Vamos começar atualizando GalleryPage para usar um StreamController como argumento para que ele possa observar os URLs de imagem recuperados no armazenamento.

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

    Em seguida, atualize _galleryGrid para retornar um StreamBuilder em vez de apenas 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. O StreamBuilder usará o imageUrlsController que será passado do StorageService para fornecer snapshots de nossos dados.
    2. A IU exige que o snapshot tenha dados para exibir algo relevante para o usuário.
    3. Também precisamos determinar se os dados realmente têm itens. Se isso acontecer, continuaremos criando o GridView.
    4. Em vez de usar um número codificado, podemos agora definir o tamanho de nosso GridView com base no tamanho dos dados no snapshot.
    5. Se o snapshot não tiver nenhum item, exibiremos um texto indicando que não há nada para mostrar.

    No momento, ainda estamos mostrando um espaço reservado para cada item na grade. Precisamos fazer download de cada imagem do URL fornecido pelo fluxo. Para facilitar, vamos adicionar uma nova dependência em pubspec.yaml:

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

    Essa biblioteca fornece um widget que pode fazer download de uma imagem e armazená-la em cache simplesmente recebendo um URL. Salve as alterações e volte para GalleryPage para que possamos substituir o espaço reservado:

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

    O espaço reservado foi substituído por CachedNetworkImage, que é transmitido ao URL do snapshot e indexado por meio de itemBuilder. Enquanto a imagem carrega, o widget exibirá um CircularProgressIndicator.

    Agora podemos conectar GalleryPage e CameraPage a StorageService em _CameraFlowState. Comece criando uma propriedade para manter uma instância de StorageService:

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

    Em seguida, inicialize _storageService no método initState:

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

    Imediatamente após inicializar o StorageService, chamamos getImages para que todas as imagens carregadas sejam recuperadas.

    Vamos passar StreamController para GalleryPage:

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

    Por último, atualize a funcionalidade de didProvideImagePath de CameraPage assim que a foto for tirada:

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

    Isso é tudo! Estamos prontos para começar a tirar fotos com nosso aplicativo e fazer upload delas no S3.

    Crie e execute para testar!

    EndofModule4-gif

Conclusão

Impressionante! A funcionalidade principal do aplicativo foi implementada, e o usuário pode tirar fotos que serão armazenadas em uma seção privada do bucket do S3 do aplicativo.

No próximo módulo, adicionaremos análises de dados.

Este módulo foi útil?

Agradecemos a sua atenção
Gostaríamos de saber do que você gostou.
Fechar
Desculpe por ter desapontado
Encontrou algo desatualizado, confuso ou incorreto? Envie seus comentários para nos ajudar a melhorar este tutorial.
Fechar

Adicionar análise de dados