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.

    AndroidAppTutorial_Modiule5_Image1
    AndroidAppTutorial_Modiule5_Image2
    AndroidAppTutorial_Modiule5_Image3
    AndroidAppTutorial_Modiule5_Image4
    AndroidAppTutorial_Modiule5_Image5

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.

Grazie per aver seguito questo tutorial fino alla fine. Inviaci il tuo feedback utilizzando lo strumento in basso o effettuando una richiesta pull sul nostro repository Github.

Questo modulo è stato utile?

Grazie
Facci sapere cosa ti è piaciuto.
Chiudi
Spiacenti di non esserti stati d'aiuto
C'è qualcosa di obsoleto, ambiguo o approssimativo? Aiutaci a migliorare questo tutorial con il tuo feedback.
Chiudi

Complimenti!

Hai creato un'applicazione Android in AWS. Il passo successivo è approfondire le tecnologie specifiche di AWS e migliorare ulteriormente la tua applicazione.