Introducción a AWS
Crear una aplicación Android
Crear una aplicación Android sencilla con AWS Amplify

agregar api y base de datos
Módulo 4: agregar una API de GraphQL y base de datos
En este módulo usará las CLI y bibliotecas de Amplify para configurar y agregar la API de GraphQL a su aplicación.
Introducción
Ahora que hemos creado y configurado la aplicación con autenticación de usuario, agreguemos una API y operaciones de crear, leer, actualizar, eliminar (CRUD) en una base de datos.
En este módulo, agregará una API a la aplicación mediante la CLI y las bibliotecas de Amplify. La API que creará es una API GraphQL que aprovecha AWS AppSync (un servicio de GraphQL administrado) que está respaldado por Amazon DynamoDB (una base de datos de NoSQL). Para obtener una introducción a GraphQL, visite esta página.
La aplicación que creará es para tomar notas que permite a los usuarios crear, eliminar y enumerar notas. Este ejemplo otorga una buena idea de cómo crear muchos tipos de aplicaciones CRUD+L (crear, leer, actualizar, eliminar y enumerar) populares.
Lo que aprenderá
- Crear e implementar una API de GraphQL
- Escribir un código front-end para interactuar con la API
Conceptos clave
API: proporciona una interfaz de programación que permite la comunicación y las interacciones entre diversos intermediarios de software.
GraphQL: una implementación de API del lado del servidor e idioma de consulta basada en una representación tipada de su aplicación. Esta representación de API se declara utilizando un esquema basado en el sistema de tipo GraphQL. (Para obtener más información GraphQL, visite esta página).
Tiempo de realización
20 minutos
Servicios utilizados
Implementación
-
Crear un servicio y una base de datos de API GraphQL
Para crear la API GraphQL y su base de datos de respaldo, abra una Terminal y ejecute este comando desde el directorio de su proyecto.
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
El editor de texto predeterminado que elige cuando inicializa el proyecto (amplify init) se abre con un esquema de datos creado previamente.
Elimine el esquema y reemplácelo con nuestro esquema GraphQL de aplicación.
type NoteData @model @auth (rules: [ { allow: owner } ]) { id: ID! name: String! description: String image: String }
El modelo de datos se compone de una clase NoteData y cuatro propiedades: el ID y el nombre son obligatorios y la descripción y la imagen son cadenas opcionales.
El transformador @model indica que queremos crear una base de datos para almacenar los datos.
El transformador @auth agrega reglas de autenticación para permitir el acceso a estos datos. Para este proyecto, queremos que solo el propietario de NoteData tenga acceso a ellos.
Una vez hecho esto, no olvide guardar, luego regrese a su terminal para decirle a la CLI de Amplify que ha terminado.
? Press enter to continue, press enter.
Luego de unos segundos, debería ver un mensaje de éxito:
GraphQL schema compiled successfully.
-
Generar código del lado del cliente
En función de la definición del modelo de datos GraphQL que acabamos de crear, Amplify genera código del lado del cliente (es decir, código Swift) para representar los datos en nuestra aplicación.
Para generar el código, en su terminal, ejecute el siguiente comando:
amplify codegen models
Esto crea archivos Java en el directorio java/com/amplifyframework.datastore.generated.model, como puede ver con:
➜ 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
Los archivos se importan automáticamente a su proyecto.
-
Implementar la base de datos y el servicio de la API
Para implementar la API de backend y la base de datos que acabamos de crear, diríjase a su terminal y ejecute el 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
Luego de unos minutos, debería ver un mensaje de éxito:
✔ All resources are updated in the cloud GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
-
Agregar biblioteca de cliente de API al proyecto de Android Studio
Antes de ir al código, regrese a Android Studio, agregue la siguiente dependencia a build.gradle de su módulo junto con las otras implementaciones amplifyframework que agregó antes y haga clic en Sync Now (Sincronizar ahora) cuando se le solicite.
dependencies { implementation 'com.amplifyframework:aws-api:1.4.0' implementation 'com.amplifyframework:aws-auth-cognito:1.4.0' }
-
Inicializar bibliotecas de Amplify en el tiempo de ejecución
Abra Backend.kt y agregue una línea en la secuencia de inicialización de Amplify en el método initialize(). El bloque de prueba/captura completo debería verse así:
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) }
-
Agregar vínculos entre el modelo de datos GraphQL y el modelo de la aplicación
Nuestro proyecto ya tiene un modelo de datos para representar una nota. En este tutorial continuaremos usando ese modelo y proporcionaremos una manera fácil de convertir NoteData en Note. Abra UserData.kt y agregue dos componentes: una propiedad dinámica que devuelva un objeto NoteData de un UserData.Note y lo contrario: un método estático que acepte una API NoteData y devuelva una Userdata.Note.
Dentro de la clase de datos Note, agregue lo siguiente:
// 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 } }
Asegúrese de importar la clase NoteData del código generado.
-
Agregar métodos API CRUD a la clase Backend
Agreguemos tres métodos para llamar a la API: un método para consultar el Note, un método para crear un nuevo Note y un método para eliminar el Note. Tenga en cuenta que estos métodos funcionan en el modelo de datos de la aplicación (Note) a fin de facilitar la interacción desde la interfaz de usuario. Estos métodos convierten de forma transparente a Nota en objetos de NoteData de GraphQL.
Abra el archivo Backend.kt y agregue el siguiente fragmento al final de la clase 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) } ) }
Asegúrese de importar las clases ModelQuery, ModelMutation y NoteData del código generado.
Por último, debemos llamar a la API a fin de consultar la lista de Note para el usuario registrado actualmente cuando se inicie la aplicación.
En Backend.ktfile, actualice el método updateUserData(withSignInStatus: Boolean) para que se vea así:
// 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() } }
Ahora, lo único que falta es crear una parte de la interfaz de usuario para crear un nuevo Note y eliminar un Note de la lista.
-
Agregar un botón de editar para agregar una nota
Ahora que las partes del backend y del modelo de datos están en su lugar, el último paso de esta sección es permitir a los usuarios crear nuevos Note y eliminarlos.
a. En Android Studio, en res/layout, cree un diseño nuevo: haga clic con el botón derecho en el diseño y seleccione New (Nuevo), luego Layout Resource File (Archivo de recurso de diseño). Nómbrelo activity_add_note y acepte todos los demás valores predeterminados. Haga clic en OK (Aceptar).
Abra el archivo activity_add_note que acaba de crear y reemplace el código generado con lo siguiente:
<?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>
Este es un diseño muy simple que permite ingresar el título y la descripción de Note.
b. Agregue una clase AddNoteActivity.
En java/com.example.androidgettingstarted, cree un nuevo archivo kotlin AddActivityNote.kt, ábralo y agregue este código:
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" } }
Por último, en manifiestos, abra AndroidManifest.xml y agregue este elemento de actividad en cualquier lugar dentro del nodo de la aplicación.
<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. Agregue un FloatingActionButton de “Add Note” (Agregar nota) en la actividad principal. En res/layout, abra activity_main.xml y agregue este sobre el botón de acción flotante existente.
<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"/>
Agregue un ícono de “Add Note” (Agregar nota) en res/drawable. Haga clic con el botón derecho en drawable (dibujable), seleccione New (Nuevo), luego Vector Asset (Activo de vector). Escriba ic_baseline_add como nombre y elija el ícono de agregar desde imágenes prediseñadas. Haga clic en Next (Siguiente) y en Finish (Finalizar).
d. Agregue códigos para manejar el botón “Add Note” (Agregar nota).
Las últimas dos cosas que faltan para tener un botón de “Add” (Agregar) completamente funcional es hacer que el botón aparezca o desaparezca según el valor isSignedIn y, por supuesto, agregar un código para que maneje los clics en el botón.
Abra mainActivity.kt y agregue esto al final del método onCreate():
// register a click listener fabAdd.setOnClickListener { startActivity(Intent(this, AddNoteActivity::class.java)) }
A continuación, todavía en el método onCreate(), reemplace UserData.isSignedIn.observe con lo siguiente:
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) } })
Para verificar que todo funcione como se espera, cree el proyecto. Haga clic en el menú Build (Crear) y seleccione Make Project (Crear proyecto) o, en Mac, presione ⌘F9. No debería haber ningún error.
Cuando ejecute la aplicación, verá que el botón “Add Note” (Agregar nota) aparece cuando el usuario inicia sesión y desaparece cuando el usuario cierra la sesión. Ahora puede agregar una nota.
-
Agregar un comportamiento de deslizar para eliminar
Se puede agregar el comportamiento de deslizar para eliminar si se suma un controlador de toques a la lista de Note. El controlador de toques se encarga de dibujar el fondo rojo y el ícono de eliminar y de llamar al método Backend.delete() cuando se lanza el toque.
a. Cree una nueva clase SimpleTouchCallback. En java/com, haga clic derecho en example.androidgettingstarted, seleccione New (Nuevo) y luego Kotlin File (Archivo Kotlin), escriba SwipeCallback como nombre.
Pegue el siguiente código en ese archivo nuevo:
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) } }
Las líneas de código importantes están en el método onSwiped(). Se lo llama a este método cuando finaliza el gesto de deslizar. Recopilamos la posición en la lista del elemento deslizado y eliminamos la nota correspondiente de la estructura de UserData (esto actualiza la UI) y del backend en la nube.
b. Ahora que tenemos una clase, vamos a agregar un ícono de “Delete” (Eliminar) en res/drawable. Haga clic con el botón derecho en drawable (dibujable), seleccione New (Nuevo), luego Vector Asset (Activo de vector). Escriba ic_baseline_delete_sweep como nombre y elija el ícono “delete sweep” (eliminar deslizamiento) en imágenes prediseñadas. Haga clic en Next (Siguiente) y en Finish (Finalizar).
c. Pegue el siguiente código en ese nuevo archivo: Agregue el controlador de movimientos deslizar para eliminar en RecyclerView.
En java/com/example.androidgettingstarted, abra MainActivity.kt y agregue estas dos líneas de código en setupRecyclerView:
// add a touch gesture handler to manager the swipe to delete gesture val itemTouchHelper = ItemTouchHelper(SwipeCallback(this)) itemTouchHelper.attachToRecyclerView(recyclerView)
-
Crear y probar
Para verificar que todo funcione como se espera, cree y ejecute el proyecto. Haga clic en el icono Run (Ejecutar) ▶️️ en la barra de herramientas o escriba ^ R. No debería haber ningún error.
La aplicación comienza en la lista vacía, si asume que su sesión aún sigue abierta. Ahora tiene un botón de “Add Note” (Agregar nota) para agregar una nota. Seleccione el cartel de Add Note (Agregar nota), escriba un título y una descripción, presione el botón Add Note(Agregar nota) y la nota debería aparecer en la lista.
Puede eliminar una nota si desliza una fila hacia la izquierda.
Este es el flujo completo.
Conclusión
¡Ha creado una aplicación Android! Mediante AWS Amplify, agregó una API de GraphQL y configuró una funcionalidad para crear, leer y eliminar en su aplicación.
En el siguiente módulo, agregaremos una interfaz de usuario y comportamientos para administrar imágenes.