Conceitos básicos da AWS
Criar uma aplicação Android
Criar uma aplicação Android simples usando o AWS Amplify

adicionar armazenamento
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.
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.
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.