Nozioni di base su AWS
Creazione di un'applicazione Android
Creazione di un'applicazione Android semplice utilizzando AWS Amplify

aggiungi api e database
Modulo 4: Aggiunta di un'API GraphQL e di un database
In questo modulo verrà utilizzata l'interfaccia a riga di comando (CLI) di Amplify per configurare e aggiungere un'API GraphQL alla tua app.
Introduzione
Ora che abbiamo creato e configurato l'applicazione con l'autenticazione utente, aggiungiamo un'API e le operazioni CRUD (Crea, Leggi, Aggiorna, Elimina) in un database.
In questo modulo aggiungerai un'API alla nostra app usando l'interfaccia a riga di comando (CLI) e le librerie di Amplify. L'API che creerai è un'API GraphQL che sfrutta AWS AppSync (un servizio GraphQL gestito) che è gestito da Amazon DynamoDB (un database NoSQL). Per un'introduzione a GraphQL, visita questa pagina.
L'applicazione che creerai sarà un'app di note che consentirà agli utenti di creare, eliminare ed elencare note. Questo esempio ti darà un'idea su come creare molti tipi famosi di applicazioni CRUD+L (crea, leggi, aggiorna, elimina ed elenca).
Avrai modo di approfondire i seguenti aspetti
- Creazione e distribuzione di un'API GraphQL
- Scrittura di un codice front-end per interagire con l'API
Concetti chiave
API: fornisce un'interfaccia di programmazione che consente la comunicazione e le interazioni tra più intermediari software.
GraphQL: un linguaggio di query e un'implementazione API lato server basati su una rappresentazione tipizzata dell'applicazione. Questa rappresentazione API viene dichiarata usando uno schema basato sul tipo di sistema GraphQL. (Per ulteriori informazioni su GraphQL, visita questa pagina.)
Tempo richiesto per il completamento
20 minuti
Servizi utilizzati
Implementazione
-
Creazione di un servizio API GraphQL e di un database
Per creare l'API GraphQL e il relativo database di supporto, apri un terminale ed esegui questo comando dalla directory del progetto:
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'editor di testo predefinito che hai selezionato quando hai inizializzato il progetto (Amplify init) apre uno schema di dati precompilato.
Elimina lo schema e sostituiscilo con lo schema GraphQL della nostra applicazione:
type NoteData @model @auth (rules: [ { allow: owner } ]) { id: ID! name: String! description: String image: String }
Il modello di dati è composto da una classe NoteData e da quattro proprietà: ID e nome sono obbligatorie, descrizione e immagine sono stringhe facoltative.
Il Transformer @model indica che vogliamo creare un database per archiviarvi i dati.
Il Transformer @auth aggiunge le regole di autenticazione per consentire l'accesso a questi dati. Per questo progetto, vogliamo che l'accesso sia consentito solo al proprietario dei NoteData.
Una volta completato, non dimenticarti di salvare, quindi torna al tuo terminale per dire ad Amplify CLI che hai finito.
? Press enter to continue, press enter.
Dopo alcuni secondi, dovresti visualizzare un messaggio di corretta esecuzione:
GraphQL schema compiled successfully.
-
Generazione del codice lato client
Sulla base della definizione del modello di dati GraphQL appena creato, Amplify genera un codice lato client (ossia codice Swift) per rappresentare i dati nella nostra applicazione.
Per generare il codice, esegui il comando seguente nel tuo terminale:
amplify codegen models
Come puoi vedere, il comando crea file Java nella directory java/com/amplifyframework.datastore.generated.model:
➜ 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
I file vengono importati automaticamente nel tuo progetto.
-
Distribuzione del database e del servizio API
Per distribuire il database e l'API di back-end appena creati, apri il terminale ed esegui il comando:
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
Dopo alcuni minuti, dovresti visualizzare un messaggio di corretta esecuzione:
✔ All resources are updated in the cloud GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
-
Aggiunta della libreria client API 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 { implementation 'com.amplifyframework:aws-api:1.4.0' implementation 'com.amplifyframework:aws-auth-cognito:1.4.0' }
-
Inizializzazione delle librerie Amplify al momento del runtime
Apri Backend.swift e aggiungi una riga nella sequenza di inizializzazione di Amplify nel metodo initialize(). Il blocco try/catch completo dovrebbe avere il seguente aspetto:
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) }
-
Aggiunta del ponte tra il modello di dati GraphQL e il modello dell'applicazione
Il nostro progetto contiene già un modello di dati per rappresentare una nota. In questo tutorial continueremo a utilizzare quel modello e a fornire un modo facile per convertire un NoteData in una nota. Apri UserData.kt e aggiungi due componenti: una proprietà dinamica che restituisce un oggetto NoteData da un elemento UserData.Note e l'opposto, un metodo statico che accetta un'API NoteData e restituisce un elemento Userdata.Note.
All'interno della classe di dati Note, aggiungi quanto segue:
// 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 } }
Assicurati di importare la classe NoteData dal codice generato.
-
Aggiunta di metodi CRUD API alla classe Backend
Aggiungiamo tre metodi per chiamare la nostra API: un metodo per eseguire una query della nota, un metodo per creare una nuova nota e un metodo per eliminare una nota. Tieni presente che questi metodi funzionano nel modello di dati dell'applicazione (Note) per semplificare l'interazione dall'interfaccia utente. Questi metodi convertono la nota in oggetti NoteData di GraphQL in modo trasparente.
Apri il file Backend.swift e aggiungi il seguente frammento alla fine della 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) } ) }
Assicurati di importare la classe ModelQuery, ModelMutation e NoteData dal codice generato.
Infine, dobbiamo chiamare l'API per eseguire una query dell'elenco di note per l'utente attualmente connesso quando l'applicazione viene avviata.
Nel file Backend.swift, aggiorna il metodo updateUserData(withSignInStatus:Boolean) perché abbia il seguente aspetto:
// 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() } }
Ora, non ci rimane che creare un pezzo dell'interfaccia utente per creare una nuova nota e per eliminare una nota dall'elenco.
-
Aggiunta di un pulsante Edit (Modifica) per aggiungere una nota
Ora che i pezzi del back-end e del modello di dati sono sistemati, l'ultima fase in questa sezione è consentire agli utenti di creare una nuova nota e di eliminarli.
a. In Android Studio, in res/layout, crea un nuovo layout: fai clic con il tasto destro del mouse su layout e seleziona New (Nuovo), quindi Layout Resource File (File risorse di layout). Denomina il file activity_add_note e accetta tutti gli altri valori predefiniti. Fai clic su OK.
Apri il file activity_add_note appena creato e sostituisci il codice generato incollando quanto segue:
<?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>
Si tratta di un layout molto semplice che consente di inserire un titolo e una descrizione della nota.
b. Aggiungi una classe AddNoteActivity.
In java/com.example.androidgettingstarted, crea un nuovo file kotlin AddActivityNote.kt, aprilo e aggiungi questo codice:
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" } }
Infine, in manifests, apri il file AndroidManifest.xml e aggiungi questo elemento di attività in qualsiasi punto del nodo dell'applicazione.
<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. Aggiungi un FloatingActionButton "Add Note (Aggiungi nota)" nell'attività principale. In res/layout, apri il file activity_main.xml e aggiungi il Floating Action Button in alto a quello esistente.
<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"/>
Aggiungi un'icona "Add Note (Aggiungi nota)" in res/drawable. Fai clic con il tasto destro del mouse su drawable, seleziona New (Nuovo), quindi Vector Asset (Asset vettore). Inserisci ic_baseline_add come nome e scegli l'icona Aggiungi dalla Clip Art. Fai clic su Next (Avanti), quindi su Finish (Fine).
d. Aggiungi il codice per gestire il pulsante "Add Note (Aggiungi nota)".
Le ultime due cose da fare per avere un "pulsante Aggiungi" completamente funzionante sono far apparire o scomparire il pulsante secondo il valore isSignedIn, e, ovviamente, aggiungere il codice per gestire i tocchi sul pulsante.
Apri mainActivity.kt e aggiungi quanto segue alla fine del metodo onCreate():
// register a click listener fabAdd.setOnClickListener { startActivity(Intent(this, AddNoteActivity::class.java)) }
Quindi, sempre nel metodo onCreate(), sostituisci UserData.isSignedIn.observe con questo codice:
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) } })
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.
Quando esegui l'applicazione, vedrai che il pulsante "Add Note (Aggiungi nota)" appare quando un utente esegue l'accesso e scompare quando un utente si scollega. Puoi ora aggiungere una nota.
-
Aggiunta del comportamento Scorri per eliminare
Il comportamento Scorri per eliminare può essere aggiunto aggiungendo un gestore di tocchi all'elenco di note. Il gestore di tocchi ha la funzione di disegnare lo sfondo rosso, l'icona di eliminazione e di chiamare il metodo Backend.delete() quando il tocco viene rilasciato.
a. Crea una nuova classe SimpleTouchCallback. In java/com, fai clic con il tasto destro del mouse su example.androidgettingstarted, seleziona New (Nuovo), quindi Kotlin File (File Kotlin) e inserisci SwipeCallback come nome.
Incolla il codice in basso nel nuovo file:
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) } }
Le righe di codice importanti si trovano nel metodo onSwiped(). Quando metodo viene chiamato quando termina il gesto di scorrimento. Inseriamo la posizione nell'elenco relativo all'elemento di scorrimento e rimuoviamo la nota corrispondente dalla struttura UserData (questa operazione aggiorna l'interfaccia utente) e dal back-end del cloud.
b. Ora che abbiamo una classe, aggiungiamo un'icona "Delete (Elimina)" in res/drawable. Fai clic con il tasto destro del mouse su drawable, seleziona New (Nuovo), quindi Vector Asset (Asset vettore). Inserisci ic_baseline_delete_sweep come nome e scegli l'icona "delete sweep" dalla Clip Art. Fai clic su Next (Avanti), quindi su Finish (Fine).
c. Incolla il codice in basso nel nuovo file. Aggiungi il comportamento di scorrimento per eliminare il gestore di gesti in RecyclerView.
In java/com/example.androidgettingstarted, apri MainActivity.kt e aggiungi queste due righe di codice in setupRecyclerView:
// add a touch gesture handler to manager the swipe to delete gesture val itemTouchHelper = ItemTouchHelper(SwipeCallback(this)) itemTouchHelper.attachToRecyclerView(recyclerView)
-
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 inizia nell'elenco vuoto. Ora è presente un pulsante Add note (Aggiungi nota) per aggiungere una nota. Tocca il segno Add Note (Aggiungi nota), inserisci un titolo, una descrizione, tocca il pulsante Add Note (Aggiungi nota) e la nota apparirà nell'elenco.
Puoi eliminare la nota facendo scorrere la riga verso sinistra.
Questo è il flusso completo.
Conclusione
Hai creato un'app Android. Usando AWS Amplify hai aggiunto un'API GraphQL e hai configurato le funzionalità di creazione, lettura ed eliminazione nella tua app.
Nel prossimo modulo, aggiungeremo l'interfaccia utente e il comportamento per gestire le immagini.