Nozioni di base su AWS
Creazione di un'applicazione Android
Creazione di un'applicazione Android semplice utilizzando AWS Amplify
Modulo 5: Aggiunta della possibilità di archiviare immagini
In questo modulo, aggiungerai storage e la possibilità di associare un'immagine alle note nell'applicazione.
Introduzione
Ora che l'app delle note funziona, aggiungiamo la possibilità di associare un'immagine a ogni nota. In questo modulo, utilizzerai l'interfaccia a riga di comando e le librerie Amplify per creare un servizio di storage grazie ad Amazon S3. Infine, aggiornerai l'applicazione Android per abilitare le funzioni per caricare, recuperare ed effettuare il rendering dell'immagine.
Avrai modo di approfondire i seguenti aspetti
- Creazione di un servizio di storage
- Aggiornamento dell'applicazione Android - La logica per caricare e scaricare immagini
- Aggiornamento dell'applicazione Android - L'interfaccia utente
Concetti chiave
Servizio di storage: l'archiviazione e le query per i file come le immagini e i video sono un requisito comune per la maggior parte delle applicazioni. Un'opzione per farlo è effettuare la codifica Base64 del file e inviarlo come stringa da salvare nel database. Ma questo procedimento ha anche degli svantaggi, ad esempio il fatto che il file codificato è più grande del binario originale, le operazioni sono costose a livello di codice e i procedimenti di codifica e decodifica adeguati sono ulteriormente complessi. Un'altra opzione è avere un servizio di storage specifico creato e ottimizzato per lo storage del file. I servizi di storage come Amazon S3 esistono per rendere il procedimento più facile, performante ed economico possibile.
Tempo richiesto per il completamento
10 minuti
Servizi utilizzati
Implementazione
-
Creazione del servizio di storage
Per aggiungere una funzionalità di storage di immagini, utilizzeremo la categoria di storage 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
Dopo qualche istante, dovresti visualizzare quanto segue:
Successfully added resource image locally
-
Distribuzione del servizio di storage
Per distribuire il servizio di storage appena creato, apri il terminale ed esegui il comando:
amplify push
Premi Y per confermare e, dopo qualche istante, dovresti visualizzare quanto segue:
✔ Successfully pulled backend environment amplify from the cloud.
-
Aggiunta delle librerie di storage Amplify al progetto Android Studio
Prima di passare al codice, torna in Android Studio, aggiungi la seguente dipendenza al file 'build.gradle' del modulo, insieme alle altre implementazioni 'amplifyframework' aggiunte in precedenza e fai clic su Sincronizza ora quando richiesto:
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' }
-
Inizializzazione del plug-in Storage di Amplify al runtime
Torna in Android Studio, in java/example.androidgettingstarted, apri Backend.kit e aggiungi una riga nella sequenza di inizializzazione di Amplify nel metodo initialize(). Il blocco di codice completo dovrebbe avere il seguente aspetto:
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) }
-
Aggiunta di metodi CRUD di immagini alla classe Backend
Sei ancora in Backend.kt. In qualsiasi punto della classe Backend, aggiungi i tre metodi seguenti per caricare, scaricare ed eliminare l'immagine dallo storage:
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) } ) }
Questi tre metodi semplicemente richiamano la loro controparte in Amplify. Lo storage Amplify presenta tre livelli di protezione file:
- Pubblico Accessibile da parte di tutti gli utenti
- Protetto Leggibile da parte di tutti gli utenti, ma scrivibile solo da parte dell'utente responsabile della creazione
- Privato Leggibile e scrivibile solo da parte dell'utente responsabile della creazione
Da questa applicazione, se vogliamo che le immagini siano disponibili solo per il proprietario della nota, utilizziamo la proprietà StorageAccessLevel.PRIVATE.
-
Aggiunta del codice dell'interfaccia utente per acquisire un'immagine
Il passaggio successivo consiste nella modifica dell'interfaccia utente per consentire all'utente di selezionare un'immagine dalla libreria del telefono quando fa clic sul pulsante "Add image (Aggiungi immagine)" nella classe AddNoteACtivity.
Sono necessarie due modifiche: la modifica del layout dell'attività "Add Note (Aggiungi nota)" per aggiungere un pulsante "Add image (Aggiungi immagine)" e una visualizzazione dell'immagine e l'aggiunta del codice di gestione nella classe dell'attività.
In Android Studio, in res/layout, apri il file activity_add_note.xml e aggiungi l'elemento Button sopra il pulsante 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" />
In Android Studio, in java/example.androidgettingstarted, apri il file AddNoteACtivity.kt e aggiungi questo codice nel metodo 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()
Aggiungi l'importazione richiesta in Intent, MediaStore e CornerFamily.
Aggiungi anche questo valore costante nell'oggetto complementare:
// add this to the companion object private const val SELECT_PHOTO = 100
Infine, aggiungi il codice che ricevi e archivia l'immagine selezionata in un file temporaneo.
Aggiungi il codice in basso in qualsiasi punto nella classe AddNoteACtivity:
//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() } } }
Il codice in alto utilizza l'immagine selezionata come InputStream due volte. Il primo InputStream crea un'immagine bitmap da visualizzare nell'interfaccia utente, il secondo InputStream salva un file temporaneo da inviare al back-end.
Questo modulo passa attraverso un file temporaneo in quanto l'API Amplify utilizza diversi Fileobject. Nonostante la struttura non sia la più efficiente, il codice è semplice.
Per verificare che tutto funzioni come previsto, crea il progetto. Fai clic sul menu Build (Crea) e seleziona Make Project (Crea progetto) oppure, nei Mac, digita ⌘F9. Non dovrebbero essere restituiti errori.
-
Archiviazione delle immagini quando vengono create le note
Richiama i metodi di storage dal back-end quando viene creata una nota. Apri AddNoteActivity.kt e modifica il metodo addNote.setOnClickListener() per aggiungere il codice in basso dopo la creazione dell'oggetto Note.
// 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!!) }
-
Caricamento delle immagini quando vengono caricate le note
Per caricare le immagini, modifichiamo il metodo da statico nella classe di dati Note. In questo modo, ogni volta che un oggetto NoteData restituito dall'API viene convertito in un oggetto Note, l'immagine viene caricata in parallelo. Una volta caricata l'immagine, notifichiamo gli UserData di LiveData per informare gli osservatori della modifica. In questo modo viene attivato un aggiornamento dell'interfaccia utente.
Apri UserData.kt e modifica l'oggetto complementare della classe di dati Note come segue:
// 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 } }
-
Eliminazione delle immagini quando vengono eliminate le note
L'ultimo passaggio consiste in un'operazione di eliminazione, ad esempio, eliminare le immagini dallo storage su cloud quando un utente elimina una nota. Questa operazione è utile sia per risparmiare spazio di archiviazione che per ridurre gli importi delle fatture AWS, in quanto Amazon S3 addebita un tot di GB/mese per i dati archiviati (i primi 5 GB sono gratuiti, non ti verrà addebitato alcun costo per eseguire questo tutorial).
Apri SwipeCallback.kt e aggiungi il codice in basso alla fine del metodo onSwipe():
if (note?.imageName != null) { //asynchronously delete the image (and assume it will work) Backend.deleteImage(note.imageName!!) }
-
Creazione e test
Per verificare che tutto funzioni come previsto, crea ed esegui il progetto. Fai clic sull'icona Run (Esegui) ▶️ nella barra degli strumenti o digita ^ R. Non dovrebbero essere restituiti errori.
Supponendo che non ti sia ancora disconnesso, l'applicazione parte dall'elenco di note che non hai eliminato dalla sezione precedente. Utilizza di nuovo il pulsante Add Note (Aggiungi nota) per creare una nota. Questa volta, aggiungi un'immagine selezionata dall'archivio immagini locale.
Chiudi l'applicazione e riavviala per verificare che l'immagine sia stata caricata correttamente.
Questo è il flusso completo.
Conclusione
Hai creato un'applicazione Android utilizzando AWS Amplify. Hai inserito l'autenticazione nella tua app per permettere agli utenti di iscriversi, registrarsi e gestire gli account. L'app ha anche un'API GraphQL scalabile configurata con un database Amazon DynamoDB per permettere agli utenti di creare ed eliminare note. Inoltre, hai aggiunto anche lo storage dei file utilizzando Amazon S3 per permettere agli utenti di caricare le immagini e visualizzarle nella loro applicazioni.
Nell'ultima sezione troverai le istruzioni per riutilizzare o eliminare il back-end appena creato.
-
Condivisione del back-end tra più progetti
Amplify semplifica la condivisione di un singolo back-end tra più applicazioni di front-end.
In un terminale, accedi alla directory dell'altro progetto ed esegui il seguente 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.
Dopo qualche secondo, vedrai il seguente messaggio:
Added backend environment config object to your project. Run 'amplify pull' to sync upstream changes.
Puoi visualizzare i due file di configurazione che sono stati estratti. Se rispondi 'Sì' quando ti viene richiesto se prevedi di modificare questo back-end, vedrai anche una directory 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
-
Eliminazione del back-end
Quando crei un back-end per un test, un prototipo o solo a scopo di apprendimento, come quando segui questo tutorial, puoi eliminare le risorse cloud che sono state create.
Anche se l'utilizzo di queste risorse nel contesto del tutorial rientra nel piano gratuito, è consigliabile eliminare le risorse inutilizzate nel cloud.
Per la pulizia del progetto Amplify, in un terminale, esegui il seguente comando:
amplify delete
Dopo qualche istante, vedrai il messaggio in basso che conferma che tutte le risorse cloud sono state eliminate.
✔ Project deleted in the cloud Project deleted locally.
Complimenti!
Hai creato un'applicazione Android in AWS. Il passo successivo è approfondire le tecnologie specifiche di AWS e migliorare ulteriormente la tua applicazione.