Conceitos básicos da AWS

Criar uma aplicação Android

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

Módulo 5: 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 o aplicativo de notas está funcionando, vamos adicionar a capacidade de associar uma imagem a cada nota. Neste módulo, você usará a CLI e as bibliotecas do Amplify para criar um serviço de armazenamento usando o Amazon S3. Para terminar, você atualizará o aplicativo Android para permitir o upload, a busca e a renderização de imagens.

O que você aprenderá

  • Criar um serviço de armazenamento
  • Atualizar o aplicativo Android – a lógica para fazer upload e download de imagens
  • Atualizar o aplicativo Android – a interface do usuário

Conceitos principais

Serviço de armazenamento - Armazenamento e consulta de arquivos, como imagens e vídeos, é um requisito comum para a maioria das aplicações. Uma opção para fazer isso é codificar o arquivo em Base64 e enviar como uma string para salvar no banco de dados. Essa opção apresenta desvantagens, como o arquivo codificado ser maior que o binário original, a operação ser computacionalmente cara e a complexidade adicional devido à codificação e decodificação adequadas. Outra opção seria ter um serviço de armazenamento especificamente criado e otimizado para o armazenamento de arquivos. Serviços de armazenamento como o Amazon S3 existem para tornar isso o mais fácil, eficiente e barato possível.

 Tempo para a conclusão

10 minutos

 Serviços usados

Implementação

  • Criar o serviço de armazenamento

    Para adicionar a funcionalidade de armazenamento de imagens, usaremos a categoria de armazenamento Amplify:

    amplify add storage
    
    ? Please select from one of the below mentioned services: accept the default Content (Images, audio, video, etc.) and press enter
    ? Please provide a friendly name for your resource that will be used to label this category in the project: type image and press enter
    ? Please provide bucket name: accept the default and press enter
    ? Who should have access: accept the default Auth users only and press enter
    ? What kind of access do you want for Authenticated users? select all three options create/update, read, delete using the space and arrows keys, then press enter
    ? Do you want to add a Lambda Trigger for your S3 Bucket? accept the default No and press enter

    Após algum tempo, deverá aparecer:

    Successfully added resource image locally
  • Implantar o serviço de armazenamento

    Para implantar o serviço de armazenamento que acabamos de criar, acesse o seu terminal e execute o comando:

    amplify push

    Pressione Y para confirmar e, em breve, deverá aparecer:

    ✔ Successfully pulled backend environment amplify from the cloud.
  • Adicionar bibliotecas de armazenamento do Amplify ao projeto do Android Studio

    Antes de ir para o código, volte ao Android Studio, adicione a seguinte dependência ao `build.gradle` do seu módulo junto com outras implementações de `amplifyframework` que você adicionou antes e clique em Sync Now (Sincronizar agora) quando solicitado:

    dependencies {
     ...
        // Amplify core dependency
        implementation 'com.amplifyframework:core:1.4.0'
        implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'
        implementation 'com.amplifyframework:aws-api:1.4.0'
        implementation 'com.amplifyframework:aws-storage-s3:1.4.0'
    }
  • Inicializar o plug-in de armazenamento do Amplify em tempo de execução

    De volta ao Android Studio, em java/example.androidgettingstarted, abra Backend.kit e adicione uma linha na sequência de inicialização do Amplify no método initialize(). Aparência do bloco de códigos completo:

    try {
        Amplify.addPlugin(AWSCognitoAuthPlugin())
        Amplify.addPlugin(AWSApiPlugin())
        Amplify.addPlugin(AWSS3StoragePlugin())
    
        Amplify.configure(applicationContext)
    
        Log.i(TAG, "Initialized Amplify")
    } catch (e: AmplifyException) {
        Log.e(TAG, "Could not initialize Amplify", e)
    }
  • Adicionar métodos Image CRUD à classe Backend

    Ainda em Backend.kt. Em qualquer parte da classe Backend, adicione estes três métodos para fazer upload, download e excluir imagem do armazenamento:

    fun storeImage(filePath: String, key: String) {
        val file = File(filePath)
        val options = StorageUploadFileOptions.builder()
            .accessLevel(StorageAccessLevel.PRIVATE)
            .build()
    
        Amplify.Storage.uploadFile(
            key,
            file,
            options,
            { progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") },
            { result -> Log.i(TAG, "Successfully uploaded: " + result.key) },
            { error -> Log.e(TAG, "Upload failed", error) }
        )
    }
    
    fun deleteImage(key : String) {
    
        val options = StorageRemoveOptions.builder()
            .accessLevel(StorageAccessLevel.PRIVATE)
            .build()
    
        Amplify.Storage.remove(
            key,
            options,
            { result -> Log.i(TAG, "Successfully removed: " + result.key) },
            { error -> Log.e(TAG, "Remove failure", error) }
        )
    }
    
    fun retrieveImage(key: String, completed : (image: Bitmap) -> Unit) {
        val options = StorageDownloadFileOptions.builder()
            .accessLevel(StorageAccessLevel.PRIVATE)
            .build()
    
        val file = File.createTempFile("image", ".image")
    
        Amplify.Storage.downloadFile(
            key,
            file,
            options,
            { progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") },
            { result ->
                Log.i(TAG, "Successfully downloaded: ${result.file.name}")
                val imageStream = FileInputStream(file)
                val image = BitmapFactory.decodeStream(imageStream)
                completed(image)
            },
            { error -> Log.e(TAG, "Download Failure", error) }
        )
    }

    Esses três métodos chamam a contraparte deles do Amplify. O armazenamento no Amplify tem três níveis de proteção de arquivo:

    • Público Acessível a todos os usuários
    • Protegido Acesso somente-leitura para todos os usuários, mas acesso de gravação apenas para o usuário que o criou
    • Privado Acesso de leitura e gravação apenas para o usuário que o criou

    Para este aplicativo, queremos que as imagens estejam disponíveis somente para o proprietário da nota, e usamos a propriedade StorageAccessLevel.PRIVATE.

  • Adicionar código de IU para capturar uma imagem

    A próxima etapa é modificar a IU para permitir que o usuário selecione uma imagem na biblioteca do telefone ao clicar no botão "Add image" (Adicionar imagem) no AddNoteACtivity.

    Duas alterações são necessárias: mude o layout de atividade "Add Note" (Adicionar nota) para adicionar um botão "Add image" (Adicionar imagem) e uma visão da imagem; adicione também código de processador na classe de atividade.

    No Android Studio, em res/layout, abra o arquivo activity_add_note.xml e adicione este elemento Button logo acima do botão addNote:

    <!-- after the description EditText -->
    <com.google.android.material.imageview.ShapeableImageView
        android:id="@+id/image"
        android:layout_width="match_parent"
        android:layout_height="280dp"
        android:layout_margin="16dp"
        android:scaleType="centerCrop" />
    
    <!-- after the Space -->
    <Button
        android:id="@+id/captureImage"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:backgroundTint="#009688"
        android:text="Add image" />

    No Android Studio, em java/example.androidgettingstarted, abra AddNoteACtivity.kt file e adicione este código no método onCreate():

    // inside onCreate() 
    // Set up the listener for add Image button
    captureImage.setOnClickListener {
        val i = Intent(
            Intent.ACTION_GET_CONTENT,
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI
        )
        startActivityForResult(i, SELECT_PHOTO)
    }
    
    // create rounded corners for the image
    image.shapeAppearanceModel = image.shapeAppearanceModel
        .toBuilder()
        .setAllCorners(CornerFamily.ROUNDED, 150.0f)
        .build()

    Adicione a importação necessária em Intent, MediaStore e CornerFamily.

    Adicione também este valor de constante no objeto associado:

    // add this to the companion object 
    private const val SELECT_PHOTO = 100

    Por fim, adicione o código que recebe e armazena a imagem selecionada a um arquivo temporário.

    Adicione o código abaixo em qualquer lugar em AddNoteACtivityclass:

    //anywhere in the AddNoteActivity class
    
    private var noteImagePath : String? = null
    private var noteImage : Bitmap? = null
    
    override fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) {
        super.onActivityResult(requestCode, resultCode, imageReturnedIntent)
        Log.d(TAG, "Select photo activity result : $imageReturnedIntent")
        when (requestCode) {
            SELECT_PHOTO -> if (resultCode == RESULT_OK) {
                val selectedImageUri : Uri? = imageReturnedIntent!!.data
    
                // read the stream to fill in the preview
                var imageStream: InputStream? = contentResolver.openInputStream(selectedImageUri!!)
                val selectedImage = BitmapFactory.decodeStream(imageStream)
                val ivPreview: ImageView = findViewById<View>(R.id.image) as ImageView
                ivPreview.setImageBitmap(selectedImage)
    
                // store the image to not recreate the Bitmap every time
                this.noteImage = selectedImage
    
                // read the stream to store to a file
                imageStream = contentResolver.openInputStream(selectedImageUri)
                val tempFile = File.createTempFile("image", ".image")
                copyStreamToFile(imageStream!!, tempFile)
    
                // store the path to create a note
                this.noteImagePath = tempFile.absolutePath
    
                Log.d(TAG, "Selected image : ${tempFile.absolutePath}")
            }
        }
    }
    
    private fun copyStreamToFile(inputStream: InputStream, outputFile: File) {
        inputStream.use { input ->
            val outputStream = FileOutputStream(outputFile)
            outputStream.use { output ->
                val buffer = ByteArray(4 * 1024) // buffer size
                while (true) {
                    val byteCount = input.read(buffer)
                    if (byteCount < 0) break
                    output.write(buffer, 0, byteCount)
                }
                output.flush()
                output.close()
            }
        }
    }

    O código acima consome a imagem selecionada como um InputStream duas vezes. O primeiro InputStream cria uma imagem Bitmap para exibir na IU. O segundo salva um arquivo temporário para enviar para o back-end.

    Este módulo passa por um arquivo temporário porque a API do Amplify consome Fileobjects. O código é simples, embora não tenha sido projetado da forma mais eficiente.

    Para verificar se tudo funciona conforme o esperado, compile o projeto. Clique no menu Build (Compilar) e selecione Make Project (Criar projeto) ou, no Mac, digite ⌘F9. Não deve aparecer nenhum erro.

  • Armazenar imagem quando notas forem criadas

    Vamos invocar os métodos de armazenamento do back-end quando uma nota for criada. Abra AddNoteActivity.kt e modifique o método addNote.setOnClickListener() para adicionar o código abaixo após o objeto Note ser criado.

    // add this in AddNoteACtivity.kt, inside the addNote.setOnClickListener() method and after the Note() object is created.
    if (this.noteImagePath != null) {
        note.imageName = UUID.randomUUID().toString()
        //note.setImage(this.noteImage)
        note.image = this.noteImage
    
        // asynchronously store the image (and assume it will work)
        Backend.storeImage(this.noteImagePath!!, note.imageName!!)
    }
  • Carregar imagem quando notas forem carregadas

    Para carregar imagens, modificamos a parte estática do método na classe de dados Note. Dessa forma, toda vez que um objeto NoteData retornado pela API é convertido em um objeto Note, a imagem é carregada em paralelo. Quando a imagem é carregada, notificamos o UserData do LiveData para que observadores sejam informados sobre a alteração. Isso aciona a atualização da IU.

    Abra UserData.kt e modifique o objeto associado da classe de dados Note da seguinte forma:

    // static function to create a Note from a NoteData API object
    companion object {
        fun from(noteData : NoteData) : Note {
            val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)
            
            if (noteData.image != null) {
                Backend.retrieveImage(noteData.image!!) {
                    result.image = it
    
                    // force a UI update
                    with(UserData) { notifyObserver() }
                }
            }
            return result
        }
    }
  • Excluir imagens quando notas forem excluídas

    A última etapa é arrumar a bagunça que fizemos, ou seja, excluir as imagens do armazenamento na nuvem quando um usuário apagar uma nota. Se você não fizer isso para economizar espaço de armazenamento, faça pensando nas suas contas da AWS, uma vez que o Amazon S3 cobra por Gb/mês de dados armazenados (os 5 primeiros Gb são grátis, e este tutorial não acarretará cobranças).

    Abra SwipeCallback.kt e adicione o código abaixo no fim do método onSwipe():

    if (note?.imageName != null) {
        //asynchronously delete the image (and assume it will work)
        Backend.deleteImage(note.imageName!!)
    }
  • Compilar e testar

    Para verificar se tudo funciona conforme o esperado, compile e execute o projeto. Clique no ícone Run (Executar) ▶️ na barra de ferramentas ou digite ^ R. Não deve aparecer nenhum erro.

    Pressupondo que você ainda esteja conectado, o aplicativo começa na lista da nota que você não excluiu na seção anterior. Use o botão Add Note (Adicionar nota) novamente para criar uma nota. Desta vez, adicione uma imagem selecionada no armazenamento de imagens locais.

    Encerre o aplicativo e reinicie-o para verificar se a imagem foi corretamente carregada.

    Aqui está o fluxo completo.

    AndroidAppTutorial_Modiule5_Image1
    AndroidAppTutorial_Modiule5_Image2
    AndroidAppTutorial_Modiule5_Image3
    AndroidAppTutorial_Modiule5_Image4
    AndroidAppTutorial_Modiule5_Image5

Conclusão

Você criou uma aplicação Android usando o AWS Amplify! Você adicionou autenticação ao seu aplicativo, permitindo que os usuários se cadastrem, façam login e gerenciem suas contas. O aplicativo também tem uma API GraphQL escalonável configurada com um banco de dados Amazon DynamoDB, permitindo aos usuários criar e excluir notas. Você também adicionou armazenamento de arquivos usando o Amazon S3, permitindo aos usuários fazer upload de imagens e visualizá-las em seus aplicativos.

Na última seção, você encontrará instruções para reutilizar ou excluir o back-end recém-criado.

  • Compartilhar o back-end entre vários projetos

    O Amplify facilita o compartilhamento de um único back-end entre várias aplicações front-end.

    Em um terminal, navegue até o outro diretório de projeto e execute o seguinte comando:

    mkdir other-project
    cd other-project
    
    amplify pull
    
    ? Do you want to use an AWS profile? accept the default Yes and press enter
    ? Please choose the profile you want to use select the profile you want to use and press enter
    ? Which app are you working on? select the backend you want to share and press enter
    ? Choose your default editor: select you prefered text editor and press enter
    ? Choose the type of app that you're building select the operating system for your new project and press enter
    ? Do you plan on modifying this backend? most of the time, select No and press enter. All backend modifications can be done from the original iOS project.

    Após alguns segundos, você verá a seguinte mensagem:

    Added backend environment config object to your project.
    Run 'amplify pull' to sync upstream changes.

    Você verá os dois arquivos de configurações que foram extraídos. Ao responder "Yes" (Sim) à pergunta "Do you plan on modifying this backend?" (Pretende modificar este back-end?), você também verá um diretório do Amplify

    ➜  other-project git:(master) ✗ ls -al
    total 24
    drwxr-xr-x   5 stormacq  admin   160 Jul 10 10:28 .
    drwxr-xr-x  19 stormacq  admin   608 Jul 10 10:27 ..
    -rw-r--r--   1 stormacq  admin   315 Jul 10 10:28 .gitignore
    -rw-r--r--   1 stormacq  admin  3421 Jul 10 10:28 amplifyconfiguration.json
    -rw-r--r--   1 stormacq  admin  1897 Jul 10 10:28 awsconfiguration.json
  • Excluir o back-end

    Quando você cria um back-end para um teste ou protótipo ou apenas para fins de aprendizado, como ocorre neste tutorial, convém excluir os recursos de nuvem que foram criados.

    Embora o uso desses recursos no contexto desse tutorial esteja no nível gratuito, recomenda-se limpar recursos não usados na nuvem.

    Para limpar o projeto do Amplify, em um terminal, execute o seguinte comando:

    amplify delete

    Após algum tempo, você verá a mensagem abaixo confirmando que todos os recursos de nuvem foram excluídos.

    ✔ Project deleted in the cloud
    Project deleted locally.

Obrigado por seguir este tutorial até o fim. Deixe seus comentários usando a ferramenta a seguir ou uma solicitação pull no repositório Github.

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

Parabéns!

Você criou uma aplicação Android na AWS com êxito! Para continuar em grande estilo, aprofunde-se nas tecnologias específicas da AWS e leve sua aplicação para o próximo nível.