Démarrer avec AWS

Créer une application Android

Créer une application Android simple avec AWS Amplify

Module 4 : ajouter une API GraphQL et une base de données

Dans ce module, vous allez utiliser la ligne d'interface de commande Amplify et des bibliothèques pour configurer et ajouter une API GraphQL à votre application.

Introduction

Maintenant que nous avons créé et configuré l'application avec un système d'authentification, nous allons ajouter une API ainsi que les opérations CRUD (créer, lire, modifier, supprimer) sur une base de données.

Dans ce module, vous allez ajouter une API à votre application en utilisant l'interface de ligne commande Amplify et des bibliothèques. Vous allez créer une API GraphQL exploitant AWS AppSync (un service GraphQL géré), qui est soutenu par Amazon DynamoDB (une base de données NoSQL). Pour une introduction à GraphQL, cliquez ici.

Vous allez créer une application de prise de notes permettant aux utilisateurs de créer, supprimer et répertorier les notes. Cet exemple démontre clairement comment créer de nombreux types populaires d'applications CRUD+L (créer, lire, mettre à jour, supprimer et répertorier).

Ce que vous apprendrez

  • Créer et déployer une API GraphQL
  • Écrire du code front-end pour interagir avec l'API

Concepts clés

API : fournit une interface de programmation qui permet à plusieurs intermédiaires logiciels de communiquer et d'interagir.

GraphQL : une implémentation d'API côté serveur et de langage de requête basée sur une représentation typée de votre application. Cette représentation d'API est déclarée en utilisant un schéma basé sur le système de type GraphQL. (Pour en savoir plus sur GraphQL, consultez cette page.)

 Durée

20 minutes

 Services utilisés

Implémentation

  • Créer un service d'API GraphQL et une base de données

    Pour créer l'API GraphQL et sa base de données, ouvrez un terminal et exécutez cette commande depuis le répertoire de votre projet :

    amplify add api
    
    ? Please select from one of the below mentioned services: select GraphQL and press enter
    ? Provide API name: select the default, press enter
    ? Choose the default authorization type for the API: use the arrow key to select Amazon Cognito User Pool and press enter
    ? Do you want to configure advanced settings for the GraphQL API: select the default No, I am done and press enter
    ? Do you have an annotated GraphQL schema? keep the default N and press enter
    ? What best describes your project: choose any model, we are going to replace it with our own anyway. Press enter
    ? Do you want to edit the schema now? type Y and press enter

    L'éditeur de texte par défaut choisi à l'initialisation du projet (amplify init) s'ouvre avec un schéma de données préconfiguré.

    Supprimez ce schéma et remplacez-le par celui de notre application GraphQL :

    type NoteData
    @model
    @auth (rules: [ { allow: owner } ]) {
        id: ID!
        name: String!
        description: String
        image: String
    }

    Le modèle de données comprend une classe NodeData et quatre propriétés : « id » et « name » sont obligatoires, tandis que « description » et « image » sont des chaînes facultatives.

    Le transformateur @model indique que nous souhaitons créer une base de données pour stocker les données.

    Le transformateur @auth ajoute des règles d'authentification permettant l'accès à ces données. Pour ce projet, nous n'accorderons l'accès qu'au propriétaire de NodeData.

    Après avoir enregistré, revenez à votre terminal pour indiquer à la CLI Amplify que vous avez terminé.

    ? Press enter to continue, press enter.

    Ce message de confirmation s'affiche après quelques secondes :

    GraphQL schema compiled successfully.
  • Générer le code côté client

    Grâce à la définition du modèle de données GraphQL que nous venons de créer, Amplify génère le code côté client (c.-à-d. code Swift) pour représenter les données dans notre application.

    Pour générer le code, exécutez la commande suivante dans votre terminal :

    amplify codegen models

    Des fichiers Java sont ainsi créés dans le répertoire java/com/amplifyframework.datastore.generated.model, comme on le voit ci-dessous :

    ➜  Android Getting Started git:(master) ✗ ls -al app/src/main/java/com/amplifyframework/datastore/generated/model 
    total 24
    drwxr-xr-x  4 stormacq  admin   128 Oct  7 15:27 .
    drwxr-xr-x  3 stormacq  admin    96 Oct  7 15:27 ..
    -rw-r--r--  1 stormacq  admin  1412 Oct  7 15:27 AmplifyModelProvider.java
    -rw-r--r--  1 stormacq  admin  7153 Oct  7 15:27 NoteData.java

    Les fichiers sont automatiquement importés dans votre projet.

  • Déployer le service d'API et la base de données

    Pour déployer l'API backend et la base de données que nous venons de créer, ouvrez votre terminal et exécutez cette commande :

    amplify push
    # press Y when asked to continue
    
    ? Are you sure you want to continue? accept the default Y and press enter
    ? Do you want to generate code for your newly created GraphQL API type N and press enter

    Ce message de confirmation s'affiche après quelques minutes :

    ✔ All resources are updated in the cloud
    
    GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
  • Ajouter la bibliothèque client d'API 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 {
        implementation 'com.amplifyframework:aws-api:1.4.0'
        implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'
    }
  • Initialiser les bibliothèques Amplify à l'exécution

    Ouvrez Backend.kt et ajoutez une ligne dans la séquence d'initialisation d'Amplify en suivant la méthode initialize(). Le bloc try/catch complet doit ressembler à ceci :

    try {
        Amplify.addPlugin(AWSCognitoAuthPlugin())
        Amplify.addPlugin(AWSApiPlugin())
        Amplify.configure(applicationContext)
    
        Log.i(TAG, "Initialized Amplify")
    } catch (e: AmplifyException) {
        Log.e(TAG, "Could not initialize Amplify", e)
    }
  • Ajouter un pont entre le modèle de données GraphQL et le modèle d'application

    Notre projet dispose déjà d'un modèle de données pour représenter une note. Dans ce tutoriel, nous continuerons d'utiliser ce modèle et allons fournir un moyen simple pour convertir un objet NoteData en Note. Ouvrez UserData.kt et ajoutez deux composants : une propriété dynamique renvoyant un objet NoteData à partir d'un élément UserData.Note, et l'inverse : une méthode statique qui accepte un objet NoteData d'API et renvoie un élément Userdata.Note.

    Dans la classe de données Note, ajoutez ces lignes :

    // return an API NoteData from this Note object
    val data : NoteData
        get() = NoteData.builder()
                .name(this.name)
                .description(this.description)
                .image(this.imageName)
                .id(this.id)
                .build()
    
    // 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)
            // some additional code will come here later
            return result
        }
    }     

    Pensez à importer la classe NoteData à partir du code généré.

  • Ajouter des méthodes CRUD d'API à la classe Backend

    Nous allons ajouter trois méthodes d'appel de notre API : interroger la Note, créer une nouvelle Note et supprimer une Note. Ces méthodes fonctionnent sur le modèle de données de l'application (Note) pour simplifier l'interaction à partir de l'interface utilisateur. Elles permettent de convertir en toute transparence une Note en objets NoteData GraphQL.

    Ouvrez le fichier Backend.kt et ajoutez cet extrait de code à la fin de la classe Backend :

    fun queryNotes() {
        Log.i(TAG, "Querying notes")
    
        Amplify.API.query(
            ModelQuery.list(NoteData::class.java),
            { response ->
                Log.i(TAG, "Queried")
                for (noteData in response.data) {
                    Log.i(TAG, noteData.name)
                    // TODO should add all the notes at once instead of one by one (each add triggers a UI refresh)
                    UserData.addNote(UserData.Note.from(noteData))
                }
            },
            { error -> Log.e(TAG, "Query failure", error) }
        )
    }
    
    fun createNote(note : UserData.Note) {
        Log.i(TAG, "Creating notes")
    
        Amplify.API.mutate(
            ModelMutation.create(note.data),
            { response ->
                Log.i(TAG, "Created")
                if (response.hasErrors()) {
                    Log.e(TAG, response.errors.first().message)
                } else {
                    Log.i(TAG, "Created Note with id: " + response.data.id)
                }
            },
            { error -> Log.e(TAG, "Create failed", error) }
        )
    }
    
    fun deleteNote(note : UserData.Note?) {
    
        if (note == null) return
    
        Log.i(TAG, "Deleting note $note")
    
        Amplify.API.mutate(
            ModelMutation.delete(note.data),
            { response ->
                Log.i(TAG, "Deleted")
                if (response.hasErrors()) {
                    Log.e(TAG, response.errors.first().message)
                } else {
                    Log.i(TAG, "Deleted Note $response")
                }
            },
            { error -> Log.e(TAG, "Delete failed", error) }
        )
    }

    Pensez à importer ModelQuery, ModelMutation et NoteData à partir du code généré.

    Enfin, nous devons appeler l'API pour interroger la liste de notes de l'utilisateur connecté au démarrage de l'application.

    Dans le fichier Backend.kt, modifiez la méthode updateUserData(withSignInStatus: Boolean) comme suit :

    // change our internal state and query list of notes 
    private fun updateUserData(withSignedInStatus : Boolean) {
        UserData.setSignedIn(withSignedInStatus)
    
        val notes = UserData.notes().value
        val isEmpty = notes?.isEmpty() ?: false
    
        // query notes when signed in and we do not have Notes yet
        if (withSignedInStatus && isEmpty ) {
            this.queryNotes()
        } else {
            UserData.resetNotes()
        }
    }

    Maintenant, il ne nous reste plus qu'à créer un élément d'interface utilisateur permettant de créer une nouvelle note et de supprimer une note de la liste.

  • Ajouter un bouton pour ajouter une note

    Maintenant que le backend et le modèle de données sont en place, il nous faut permettre aux utilisateurs de créer et de supprimer une note.

    a. Dans Android Studio, sous res/layout, créez une nouvelle disposition : cliquez sur le bouton droit de la souris sur layout (disposition) et choisissez New, (Nouveau) puis Layout Resource File (Fichier de ressource de disposition). Nommez ce fichier activity_add_note et acceptez toutes les autres valeurs par défaut. Cliquez sur OK.

    AndroidAppTutorial_Modiule4_Image1

    Ouvrez le fichier activity_add_note que nous venons de créer, puis remplacez le code généré par l'extrait ci-dessous :

    <?xml version="1.0" encoding="utf-8"?>
    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:fillViewport="true">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:padding="8dp">
    
            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:text="Create a New Note"
                android:textSize="10pt" />
    
            <EditText
                android:id="@+id/name"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:hint="name"
                android:inputType="text"
                android:lines="5" />
    
            <EditText
                android:id="@+id/description"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="8dp"
                android:hint="description"
                android:inputType="textMultiLine"
                android:lines="3" />
    
            <Space
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="1" />
    
            <Button
                android:id="@+id/addNote"
                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 Note" />
    
            <Button
                android:id="@+id/cancel"
                style="?android:attr/buttonStyleSmall"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:backgroundTint="#FFC107"
                android:text="Cancel" />
    
        </LinearLayout>
    </ScrollView>

    Cette disposition très simple permet de saisir un titre et une description de note.

    b. Ajoutez une classe AddNoteActivity.

    Sous java/com.example.androidgettingstarted, créez un nouveau fichier Kotlin AddActivityNote.kt. Ouvrez-le puis ajoutez ce code : 

    package com.example.androidgettingstarted
    
    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import kotlinx.android.synthetic.main.activity_add_note.*
    import java.util.*
    
    class AddNoteActivity : AppCompatActivity()  {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_add_note)
    
            cancel.setOnClickListener {
                this.finish()
            }
    
            addNote.setOnClickListener {
    
                // create a note object
                val note = UserData.Note(
                    UUID.randomUUID().toString(),
                    name?.text.toString(),
                    description?.text.toString()
                )
    
                // store it in the backend
                Backend.createNote(note)
    
                // add it to UserData, this will trigger a UI refresh
                UserData.addNote(note)
    
                // close activity
                this.finish()
            }
        }
    
        companion object {
            private const val TAG = "AddNoteActivity"
        }
    }    

    Enfin, sous manifests (manifestes), ouvrez AndroidManifest.xml et ajoutez cet élément d'activité où vous le souhaitez dans le nœud d'application.

    <activity
        android:name=".AddNoteActivity"
        android:label="Add Note"
        android:theme="@style/Theme.GettingStartedAndroid.NoActionBar">
        <meta-data
            android:name="android.support.PARENT_ACTIVITY"
            android:value="com.example.androidgettingstarted.MainActivity" />
    </activity>

    c. Ajoutez un bouton d'action flottant (FloatingActionButton) « Add Note » (Ajouter une note) dans l'activité principale. Sous res/layout, ouvrez activity_main.xml et ajoutez cet extrait de code au-dessus du bouton d'action flottant existant.

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fabAdd"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:visibility="invisible"
        android:src="@drawable/ic_baseline_post_add"
        app:fabCustomSize="60dp"
        app:fabSize="auto"/>

    Ajoutez une icône « Add Note » (Ajouter une note) dans res/drawable. Cliquez avec le bouton droit de la souris sur drawable, sélectionnez New (Nouveau), puis Vector Asset (Ressource vectorielle). Saisissez le nom ic_baseline_add et choisissez l'icône Ajouter depuis le Clip Art. Cliquez sur Next (Suivant) puis Finish (Terminer).

    AndroidAppTutorial_Modiule4_Image2

    d. Ajoutez du code pour gérer le bouton « Add Note ».

    Afin de rendre pleinement fonctionnel le bouton « Add Note »; nous devons le faire apparaître ou disparaître en fonction de la valeur isSignedIn. Nous devons en outre ajouter du code afin de gérer le comportement du bouton lors de son utilisation.

    Ouvrez mainActivity.kt et ajoutez cet extrait à la fin de la méthode onCreate() :

    // register a click listener
    fabAdd.setOnClickListener {
        startActivity(Intent(this, AddNoteActivity::class.java))
    }

    Ensuite, toujours dans la méthode onCreate(), remplacez UserData.isSignedIn.observe par :

    UserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->
        // update UI
        Log.i(TAG, "isSignedIn changed : $isSignedUp")
    
        //animation inspired by https://www.11zon.com/zon/android/multiple-floating-action-button-android.php
        if (isSignedUp) {
            fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)
            Log.d(TAG, "Showing fabADD")
            fabAdd.show()
            fabAdd.animate().translationY(0.0F - 1.1F * fabAuth.customSize)
        } else {
            fabAuth.setImageResource(R.drawable.ic_baseline_lock)
            Log.d(TAG, "Hiding fabADD")
            fabAdd.hide()
            fabAdd.animate().translationY(0.0F)
        }
    })    

    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.

    Quand vous exécutez l'application, un bouton « Add Note » (Ajouter une note) apparaîtra ou disparaîtra lorsqu'un utilisateur se connecte ou se déconnecte. Vous pouvez désormais ajouter une note.

  • Ajouter un comportement « balayer pour supprimer »

    Le comportement « balayer pour supprimer » peut être créé en ajoutant un gestionnaire tactile à la liste de notes. Le gestionnaire tactile fait apparaître l'arrière-plan rouge ainsi que l'icône Supprimer. Il appelle la méthode Backend.delete() lorsque le bouton est relâché.

    a. Créez une nouvelle classe SimpleTouchCallback. Sous java/com, cliquez avec le bouton droit de la souris sur example.androidgettingstarted, sélectionnez New (Nouveau) puis Kotlin File (Fichier Kotlin). Saisissez le nom SwipeCallback.

    AndroidAppTutorial_Modiule4_Image3

    Collez le code ci-dessous dans ce nouveau fichier :

    package com.example.androidgettingstarted
    
    import android.graphics.Canvas
    import android.graphics.Color
    import android.graphics.drawable.ColorDrawable
    import android.graphics.drawable.Drawable
    import android.util.Log
    import android.widget.Toast
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.content.ContextCompat
    import androidx.recyclerview.widget.ItemTouchHelper
    import androidx.recyclerview.widget.RecyclerView
    
    
    // https://stackoverflow.com/questions/33985719/android-swipe-to-delete-recyclerview
    class SwipeCallback(private val activity: AppCompatActivity): ItemTouchHelper.SimpleCallback(
        0,
        ItemTouchHelper.LEFT
    ) {
    
        private val TAG: String = "SimpleItemTouchCallback"
        private val icon: Drawable? = ContextCompat.getDrawable(
            activity,
            R.drawable.ic_baseline_delete_sweep
        )
        private val background: ColorDrawable = ColorDrawable(Color.RED)
    
        override fun onChildDraw(
            c: Canvas,
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            dX: Float,
            dY: Float,
            actionState: Int,
            isCurrentlyActive: Boolean
        ) {
            super.onChildDraw(
                c,
                recyclerView,
                viewHolder,
                dX,
                dY,
                actionState,
                isCurrentlyActive
            )
            val itemView = viewHolder.itemView
            val backgroundCornerOffset = 20
            val iconMargin = (itemView.height - icon!!.intrinsicHeight) / 2
            val iconTop = itemView.top + (itemView.height - icon.intrinsicHeight) / 2
            val iconBottom = iconTop + icon.intrinsicHeight
            val iconRight: Int = itemView.right - iconMargin
            if (dX < 0) {
                val iconLeft: Int = itemView.right - iconMargin - icon.intrinsicWidth
                icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)
                background.setBounds(
                    itemView.right + dX.toInt() - backgroundCornerOffset,
                    itemView.top, itemView.right, itemView.bottom
                )
                background.draw(c)
                icon.draw(c)
            } else {
                background.setBounds(0, 0, 0, 0)
                background.draw(c)
            }
        }
    
        override fun onMove(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            target: RecyclerView.ViewHolder
        ): Boolean {
            Toast.makeText(activity, "Moved", Toast.LENGTH_SHORT).show()
            return false
        }
    
        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
    
            Toast.makeText(activity, "deleted", Toast.LENGTH_SHORT).show()
    
            //Remove swiped item from list and notify the RecyclerView
            Log.d(TAG, "Going to remove ${viewHolder.adapterPosition}")
    
            // get the position of the swiped item in the list
            val position = viewHolder.adapterPosition
    
            // remove to note from the userdata will refresh the UI
            val note = UserData.deleteNote(position)
    
            // async remove from backend
            Backend.deleteNote(note)
        }
    }

    Les lignes de code importantes se trouvent dans la méthode onSwiped(). Celle-ci est appelée à la fin du geste de balayage. Nous récupérons la position de l'élément balayé dans la liste, puis supprimons la note correspondante de la structure UserData (l'UI est mise à jour) et du backend cloud.

    b. Maintenant que la classe est créée, nous allons ajouter une icône « Supprimer » dans res/drawable. Cliquez avec le bouton droit de la souris sur drawable, sélectionnez New (Nouveau), puis Vector Asset (Ressource vectorielle). Saisissez le nom ic_baseline_delete_sweep et choisissez l'icône « delete sweep » depuis le Clip Art. Cliquez sur Next (Suivant) puis Finish (Terminer).

    AndroidAppTutorial_Modiule4_Image4

    c. Collez le code ci-dessous dans ce nouveau fichier afin d'ajouter le gestionnaire du geste « balayer pour supprimer » à la vue RecyclerView.

    Sous java/com/example.androidgettingstarted, ouvrez MainActivity.kt et ajoutez ces deux lignes de code dans setupRecyclerView :

    // add a touch gesture handler to manager the swipe to delete gesture
    val itemTouchHelper = ItemTouchHelper(SwipeCallback(this))
    itemTouchHelper.attachToRecyclerView(recyclerView)
  • 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 vous êtes toujours connecté, l'application démarre à partir d'une liste vide. Elle comporte désormais un bouton « Add Note » permettant d'ajouter une note. Appuyez sur le symbole du bouton en question, saisissez un titre, une description, puis appuyez sur le bouton Add Note. La note devrait apparaître dans la liste.

    Vous pouvez désormais supprimer une note en balayant sa ligne vers la gauche.

    Voici la procédure complète.

    AndroidAppTutorial_Modiule4_Image5
    AndroidAppTutorial_Modiule4_Image6
    AndroidAppTutorial_Modiule4_Image7
    AndroidAppTutorial_Modiule4_Image8

Conclusion

Vous avez créé une application Android ! En utilisant AWS Amplify, vous avez ajouté une API GraphQL et configuré des fonctionnalités de création, de lecture et de suppression dans votre application.

Dans le prochain module, nous ajouterons une interface utilisateur ainsi que des règles de gestion des images.

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

Augmenter la capacité de stockage