Démarrer avec AWS

Créer une application Android

Créer une application Android simple avec AWS Amplify

Module 5 : Ajouter la capacité de stocker des images

Dans ce module, vous apprendrez à augmenter la capacité de stockage et à associer une image avec les notes dans votre application.

Introduction

Maintenant que l'application fonctionne, apprenons à associer une image à chaque note. Dans ce module, vous utiliserez l'interface de ligne de commande Amplify et les bibliothèques pour créer un service de stockage qui exploite Amazon S3. Pour finir, vous mettrez à jour l'application Android pour autoriser le téléchargement, la récupération et le rendu d'images.

Ce que vous apprendrez

  • Créer un service de stockage
  • Mettre à jour votre application Android : la logique de chargement et de téléchargement des images
  • Mettre à jour votre application Android : l'interface utilisateur

Concepts clés

Service de stockage :le stockage et la requête de fichiers, tels que des images et des vidéos, sont des exigences communes à la plupart des applications. L'une des solutions consiste à encoder le fichier en Base64 et à l'envoyer en tant que chaîne à sauvegarder dans la base de données. Cela présente toutefois des inconvénients, notamment le fait que le fichier codé est plus volumineux que le binaire d'origine, que l'opération est coûteuse en calculs et que le codage et le décodage sont plus complexes. Une autre option consiste à disposer d'un service de stockage spécialement conçu et optimisé pour le stockage de fichiers. Les services de stockage, comme Amazon S3, assurent ces opérations de manière facile, performante et aussi peu coûteuse que possible.

 Durée nécessaire

10 minutes

 Services utilisés

Implémentation

  • Créer le service de stockage

    Pour ajouter une fonctionnalité de stockage d'images, nous utiliserons la catégorie de stockage 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

    Après quelques instants, ce message devrait s'afficher :

    Successfully added resource image locally
  • Déployer le service de stockage

    Pour déployer le service de stockage que nous venons de créer, ouvrez votre terminal et exécutez cette commande :

    amplify push

    Appuyez sur la touche Y pour confirmer. Ce message s'affiche ensuite :

    ✔ Successfully pulled backend environment amplify from the cloud.
  • Ajouter des bibliothèques de stockage Amplify au projet Android Studio

    Avant d'accéder au code, revenez sous Android Studio, ajoutez cette dépendance au 'build.gradle' de votre module avec les autres implémentations 'amplifyframework' ajoutées auparavant, puis cliquez sur Sync Now (Synchroniser maintenant) :

    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'
    }
  • Initialiser le plug-in de stockage Amplify à l'exécution

    Dans Android Studio, sous java/example.androidgettingstarted, ouvrez Backend.kt et ajoutez une ligne à la séquence d'initialisation d'Amplify dans la méthode initialize(). Le bloc de code complet doit ressembler à ceci :

    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)
    }
  • Ajouter des méthodes CRUD d'image à la classe Backend

    Toujours dans Backend.kt, dans la classe Backend, ajoutez les trois méthodes suivantes pour charger, télécharger et supprimer des images du stockage :

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

    Ces trois méthodes appellent simplement leurs équivalents Amplify. Le stockage d'Amplify dispose de trois niveaux de protection des fichiers :

    • Public : accessible par tous les utilisateurs ;
    • Protégé : accessible en lecture par tous les utilisateurs, mais accessible en écriture uniquement par l'utilisateur l'ayant créé ;
    • Privé : accessible en lecture et en écriture uniquement par l'utilisateur l'ayant créé.

    Pour cette application, nous voulons que les images soient uniquement accessibles au propriétaire de la Note. Nous utilisons donc la propriété StorageAccessLevel.PRIVATE.

  • Ajouter du code d'UI pour capturer une image

    L'étape suivante consiste à modifier l'UI afin de permettre à l'utilisateur de sélectionner une image dans la bibliothèque du téléphone lorsqu'il clique sur le bouton « Add Image » (Ajouter une image) dans l'activité AddNoteACtivity.

    Deux changements sont nécessaires : modifiez la disposition de l'activité « Add Note » (Ajouter une note) afin d'intégrer le bouton « Add Image » (Ajouter une image) et une vue d'image, et ajoutez le code du gestionnaire dans la classe de l'activité.

    Dans Android Studio, sous res/layout, ouvrez le fichier activity_add_note.xml et ajoutez cet élément Bouton juste au-dessus du bouton 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" />

    Dans Android Studio, sous java/example.androidgettingstarted, ouvrez le fichier AddNoteActivity.kt et ajoutez ce code dans la méthode 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()

    Ajoutez l'importation requise dans Intent, MediaStore et CornerFamily.

    Ajoutez également cette valeur constante dans l'objet complémentaire :

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

    Enfin, ajoutez le code recevant et stockant l'image sélectionnée dans un fichier temporaire.

    Ajoutez le code ci-dessous dans la 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()
            }
        }
    }

    Le code ci-dessus consume l'image sélectionnée en tant qu'InputStream, deux fois. Le premier InputStream crée une image Bitmap à afficher dans l'UI, le second enregistre un fichier temporaire à envoyer vers le backend.

    Ce module emploie un fichier temporaire car l'API Amplify consume les Fileobjects. Le code, de conception rudimentaire, a l'avantage d'être simple.

    Pour vérifier que tout fonctionne comme prévu, générez le projet. Cliquez dans le menu Build (Générer) et sélectionnez Make Project (Créer le projet), ou, sur Mac, appuyez sur ⌘F9. Il ne devrait pas y avoir d'erreur.

  • Stocker des images lorsque des notes sont créées

    Invoquons les méthodes de stockage depuis le backend lorsqu'une note est créée. Ouvrez AddNoteActivity.kt et modifiez la méthode addNote.setOnClickListener() pour ajouter le code ci-dessous une fois l'objet Note créé.

    // 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!!)
    }
  • Charger des images lorsque des notes sont chargées

    Pour charger des images, nous modifions la fonction statique depuis la méthode dans la classe de données Note. Ainsi, chaque fois qu'un objet NoteData retourné par l'API est converti en objet Note, l'image est chargée en parallèle. Une fois l'image chargée, nous signalons aux UserData de LiveData de faire savoir aux observateurs qu'il y a eu un changement. Cela déclenche une actualisation de l'UI.

    Ouvrez UserData.kt et modifiez l'objet complémentaire de la classe NoteData comme ceci :

    // 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
        }
    }
  • Supprimer des images lorsque des notes sont supprimées

    La dernière étape consiste à nettoyer derrière nous, c'est-à-dire à supprimer les images du stockage dans le cloud lorsqu'un utilisateur supprime une note. Si ce n'est pour libérer de l'espace de stockage, faites-le pour vos factures AWS : Amazon S3 facture en effet les données stockées par Go/mois (les premiers 5 Go étant gratuits, l'exécution de ce tutoriel ne vous sera pas facturée).

    Ouvrez SwipeCallback.kt et ajoutez le code ci-dessous à la fin de la méthode onSwipe() :

    if (note?.imageName != null) {
        //asynchronously delete the image (and assume it will work)
        Backend.deleteImage(note.imageName!!)
    }
  • Générer et tester

    Pour vérifier que tout fonctionne comme prévu, générez et exécutez le projet. Cliquez sur l'icône Exécuter ▶️ dans la barre d'outils ou tapez ^ R. Il ne devrait pas y avoir d'erreur.

    Si votre connexion est toujours active, l'application démarre sur la liste de notes que vous n'avez pas supprimée de la section précédente. Utilisez à nouveau le bouton Add Note (Ajouter une note) pour créer une note. Cette fois, ajoutez une image sélectionnée dans l'espace de stockage local.

    Quittez l'application et relancez-la pour vérifier que l'image est correctement chargée.

    Voici la procédure complète.

    AndroidAppTutorial_Modiule5_Image1
    AndroidAppTutorial_Modiule5_Image2
    AndroidAppTutorial_Modiule5_Image3
    AndroidAppTutorial_Modiule5_Image4
    AndroidAppTutorial_Modiule5_Image5

Conclusion

Vous avez créé une application Android avec AWS Amplify. Vous avez ajouté une authentification à votre application, qui permet aux utilisateurs de s'inscrire, de se connecter et de gérer leur compte. L'application possède également une API GraphQL évolutive, configurée avec une base de données Amazon DynamoDB, qui permet aux utilisateurs de créer et de supprimer des notes. Vous avez également ajouté le stockage de fichiers en utilisant Amazon S3 pour permettre aux utilisateurs de télécharger des images et de les visionner dans leur application.

La dernière section contient les instructions à suivre pour réutiliser ou supprimer le backend que nous venons de créer.

  • Partager votre backend entre plusieurs projets

    Amplify facilite le partage de backend entre plusieurs applications front-end.

    À partir d'un terminal, naviguez vers le répertoire de votre autre projet et exécutez la commande suivante :

    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.

    Ce message apparaît après quelques secondes :

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

    On peut voir les deux fichiers de configuration qui ont été extraits. En répondant positivement à la question « Do you plan on modifying this backend? » (Comptez-vous modifier ce backend ?), on voit également un répertoire 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
  • Supprimer votre backend

    Lorsque vous créez un backend dans le cadre d'un test, pour un prototype ou bien à des fins d'apprentissage, comme dans ce tutoriel, vous pourrez supprimer les ressources que vous avez créées.

    Dans le contexte de ce tutoriel, l'utilisation de ces ressources est comprise dans l'offre gratuite. Toutefois, une bonne pratique consiste à nettoyer les ressources inutilisées dans le cloud.

    Pour nettoyer votre projet Amplify, exécutez la commande suivante à partir d'un terminal :

    amplify delete

    Après quelques instants, le message ci-dessous apparaît, confirmant la suppression de l'ensemble des ressources cloud.

    ✔ Project deleted in the cloud
    Project deleted locally.

Merci d'avoir suivi ce tutoriel jusqu'à la fin. Vous pouvez nous envoyer vos commentaires en utilisant l'outil ci-dessous ou effectuer une demande d'extraction sur notre référentiel Github.

Ce module vous a-t-il été utile ?

Merci
Merci de nous indiquer ce que vous avez aimé.
Fermer
Nous sommes désolés de vous décevoir.
Quelque chose est-il obsolète, déroutant ou inexact ? Aidez-nous à améliorer ce didacticiel en fournissant des commentaires.
Fermer

Félicitations !

Vous avez créé une application Android sur AWS ! Prochaine grande étape : approfondissez la découverte de technologies AWS spécifiques pour faire évoluer votre application au niveau supérieur.