Conceitos básicos da AWS
Criar uma aplicação Android
Criar uma aplicação Android simples usando o AWS Amplify

adicionar api e banco de dados
Módulo 4: adicionar uma API GraphQL e um banco de dados
Neste módulo, você usará as bibliotecas e a CLI do Amplify para configurar e adicionar uma API GraphQL a seu aplicativo.
Introdução
Agora que criamos e configuramos o aplicativo com autenticação de usuário, vamos adicionar uma API e as operações Criar, Ler, Atualizar, Excluir (CRUD) em um banco de dados.
Neste módulo, você adicionará uma API ao aplicativo usando as bibliotecas e a CLI do Amplify. A API que você criará é uma API GraphQL que usa o AWS AppSync (um serviço GraphQL gerenciado), que tem o suporte do Amazon DynamoDB (um banco de dados NoSQL). Para ver uma introdução ao GraphQL, acesse esta página.
O aplicativo que criaremos é um aplicativo de notas que permite aos usuários criar, excluir e listar notas. Este exemplo dá uma boa ideia de como criar vários tipos comuns de aplicações CRUD+L (criar, ler, atualizar, excluir e listar).
O que você aprenderá
- Criar e implantar uma API GraphQL
- Escrever um código front-end para interagir com a API
Conceitos principais
API – Fornece uma interface de programação que permite a comunicação e interação entre vários intermediários do software.
GraphQL – Uma linguagem de consulta e implementação de API no servidor baseada em uma representação de tipo da aplicação. A representação da API é declarada usando um esquema baseado no sistema de tipo do GraphQL. (Para saber mais sobre o GraphQL, acesseesta página.)
Tempo para a conclusão
20 minutos
Serviços usados
Implementação
-
Criar um serviço de API GraphQL e um banco de dados
Para criar a API GraphQL e o banco de dados de suporte, abra um terminal e execute este comando no diretório do projeto:
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
O editor de texto padrão que você escolheu quando inicializou o projeto (amplify init) é aberto com um esquema de dados predefinido.
Exclua o esquema e substitua-o pelo esquema de nosso aplicativo GraphQL:
type NoteData @model @auth (rules: [ { allow: owner } ]) { id: ID! name: String! description: String image: String }
O modelo de dados contém uma classe NoteData e quatro propriedades: o id e o nome são obrigatórios; as strings de descrição e imagem são opcionais.
O transformador @model indica que a intenção de criar um banco de dados para armazenar esses dados.
O transformador @auth adiciona regras de autenticação para permitir o acesso a esses dados. Para este projeto, queremos que apenas o proprietário de NoteData tenha acesso a eles.
Depois de fazer isso, não se esqueça de salvar. Em seguida, volte ao terminal para informar à CLI do Amplify que você terminou.
? Press enter to continue, press enter.
Após alguns segundos, você deve ver uma mensagem de êxito:
GraphQL schema compiled successfully.
-
Gerar código do cliente
Com base na definição de modelo de dados do GraphQL recém-criada, o Amplify gera código do cliente (código Swift) para representar os dados em nosso aplicativo.
Para gerar o código, no terminal,execute o seguinte comando:
amplify codegen models
Isso cria arquivos Java no diretório java/com/amplifyframework.datastore.generated.model, como você pode ver com:
➜ 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
Os arquivos são importados automaticamente para o seu projeto.
-
Implantar o serviço de API e banco de dados
Para implantar a API de back-end e o banco de dados que acabamos de criar, acesse o terminal e execute o 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
Após alguns minutos, você deve ver uma mensagem de êxito:
✔ All resources are updated in the cloud GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
-
Adicionar biblioteca de cliente API ao projeto do Android Studio
Antes de ir para o código, volte ao Android Studio, adicione a seguinte dependência ao `build.gradle` do seu módulo com outras implementações de `amplifyframework` que você adicionou anteriormente e clique em Sync Now (Sincronizar agora) quando solicitado:
dependencies { implementation 'com.amplifyframework:aws-api:1.4.0' implementation 'com.amplifyframework:aws-auth-cognito:1.4.0' }
-
Inicializar bibliotecas do Amplify em tempo de execução
Abra Backend.kt e adicione uma linha na sequência de inicialização do Amplify no método initialize(). O bloco try/catch completo deve ficar assim:
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) }
-
Adicionar elos entre o modelo de dados GraphQL e o modelo do aplicativo
Nosso projeto já tem um modelo de dados para representar uma nota. Neste tutorial, continuaremos a usar esse modelo e oferecer uma forma fácil de converter um NoteData em uma nota. Abra UserData.kt e adicione dois componentes: uma propriedade dinâmica que retorna um objeto NoteData de um UserData.Note, e o oposto: um método estático que aceita um NoteData de API e retorna um Userdata.Note.
Dentro da classe de dados de nota, adicione o seguinte:
// 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 } }
Importe a classe NoteData do código gerado.
-
Adicionar métodos CRUD em API à classe back-end
Vamos adicionar três métodos para chamar nossa API: um para consultar a nota, um para criar uma nova nota e outro para excluir uma nota. Observe que esses métodos funcionam no modelo de dados de aplicativo (nota) para facilitar a interação na interface do usuário. Esses métodos convertem de maneira transparente a nota em objetos NoteData do GraphQL.
Abra o arquivo Backend.kt e adicione o seguinte snippet ao final da 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) } ) }
Importe as classes ModelQuery, ModelMutation e NoteData do código gerado.
Por fim, devemos chamar a API para consultar a lista de notas do usuário conectado no momento quando a aplicação é iniciada.
No arquivo Backend.ktfile, atualize o método updateUserData(withSignInStatus: Boolean) para que fique assim:
// 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() } }
Falta apenas criar uma interface de usuário para criar uma nova nota e excluir uma nota da lista.
-
Adicionar um botão de edição para adicionar nota
Agora que os modelos back-end e de dados estão em vigor, a última etapa nesta seção é permitir que usuários criem uma nova nota e a excluam.
a. No Android Studio, em res/layout, crie um layout novo: clique com o botão direito em layout e selecione New (Novo) e, em seguida, Layout Resource File (Arquivo de recursos de layout). Nomeie-o como activity_add_note e aceite todos os outros valores padrão. Clique em OK.
Abra o arquivo activity_add_note recém-criado e cole o seguinte para substituir o código gerado:
<?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 é um layout muito simples que permite inserir um título e uma descrição de nota.
b. Adicione uma classe AddNoteActivity.
Em java/com.example.androidgettingstarted, crie um novo arquivo kotlin AddActivityNote.kt, abra-o e adicione 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 fim, em manifests (manifestos), abra AndroidManifest.xml e adicione esse elemento de atividade em qualquer lugar dentro do nó da aplicação.
<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. Adicione um FloatingActionButton de "Add Note" (Adicionar nota) na atividade principal. Em res/layout, abra activity_main.xml e adicione isso acima do botão de ação flutuante 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"/>
Adicione um ícone de "Add Note" (Adicionar nota) em res/drawable. Clique com o botão direito em drawable, selecione New (Novo) e depois Vector Asset (Ativo de vetor). Insira ic_baseline_add como o nome e escolha o ícone de adição no Clip Art. Clique em Next (Avançar) e em Finish (Concluir).
d. Adicione o código para operar o botão "Add Note" (Adicionar nota).
As duas últimas tarefas para termos um botão de adição totalmente funcional é condicionar a visibilidade do botão ao valor isSignedIn e, obviamente, adicionar código para processar os toques no botão.
Abra mainActivity.kt e o adicione ao final do método onCreate():
// register a click listener fabAdd.setOnClickListener { startActivity(Intent(this, AddNoteActivity::class.java)) }
Em seguida, ainda no método onCreate() , substitua UserData.isSignedIn.observe por:
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 se tudo funciona conforme o esperado, compile o projeto. Clique no menu Build (Compilar) e selecione Make Project (Criar projeto) ou, no Mac, digite ⌘F9. Não deve aparecer nenhum erro.
Quando você executar a aplicação, o botão "Add Note" (Adicionar nota) será exibido quando o usuário fizer login e desaparecerá quando o usuário sair. Agora você pode adicionar uma nota.
-
Adicionar um comportamento de deslizar para excluir
O comportamento de deslizar para excluir pode ser adicionado. Basta acrescentar um processador de toques à lista da nota. O processador de toques é responsável pelo fundo vermelho, pelo ícone de exclusão e por chamar o método Backend.delete() quando o toque for liberado.
a. Crie uma nova classe SimpleTouchCallback. Em java/com, clique com o botão direito em example.androidgettingstarted, selecione New (Novo) e Kotlin File (Arquivo Kotlin). Em seguida, insira SwipeCallback como o nome.
Cole o código abaixo nesse novo arquivo:
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) } }
As linhas de código importantes estão no método onSwiped(). Esse método é chamado quando o gesto de deslizar termina. Coletamos a posição na lista do item deslizado e removemos a nota correspondente da estrutura UserData (isso atualiza a IU) e do back-end da nuvem.
b. Agora que temos uma classe, vamos adicionar um ícone "Delete" (Excluir) em res/drawable. Clique com o botão direito em drawable, selecione New (Novo) e depois Vector Asset (Ativo de vetor). Insira ic_baseline_delete_sweep como o nome e escolha o ícone "delete sweep" (deslizar para excluir) do Clip Art. Clique em Next (Avançar) e em Finish (Concluir).
c. Cole o código abaixo nesse novo arquivo: Adicione ao RecyclerView o processador de gestos de deslizar para excluir.
Em java/com/example.androidgettingstarted, abra MainActivity.kt e adicione estas duas linhas de código em setupRecyclerView:
// add a touch gesture handler to manager the swipe to delete gesture val itemTouchHelper = ItemTouchHelper(SwipeCallback(this)) itemTouchHelper.attachToRecyclerView(recyclerView)
-
Compilar e testar
Para verificar se tudo funciona conforme esperado, compile e rode o projeto. Clique no ícone Run (Executar) ▶️ na barra de ferramentas ou digite ^ R. Não deve aparecer nenhum erro.
Supondo que você ainda esteja conectado, o aplicativo iniciará a lista vazia. Ele agora tem um botão Add Note (Adicionar nota) para adicionar uma nota. Toque no sinal Add Note (Adicionar nota), insira um título e uma descrição, toque no botão Add Note (Adicionar nota) e a nota deve aparecer na lista.
Você pode excluir a nota deslizando uma linha para a esquerda.
Aqui está o fluxo completo.
Conclusão
Você criou um aplicativo Android! Usando o AWS Amplify, você adicionou uma API GraphQL e configurou as funções de criação, leitura e exclusão no seu aplicativo.
No próximo módulo, adicionaremos interface do usuário e comportamento para gerenciar fotos.