Conceitos básicos da AWS

Criar uma aplicação Android

Criar uma aplicação Android simples usando o AWS Amplify

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.

    AndroidAppTutorial_Modiule4_Image1

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

    AndroidAppTutorial_Modiule4_Image2

    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.

    AndroidAppTutorial_Modiule4_Image3

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

    AndroidAppTutorial_Modiule4_Image4

    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.

    AndroidAppTutorial_Modiule4_Image5
    AndroidAppTutorial_Modiule4_Image6
    AndroidAppTutorial_Modiule4_Image7
    AndroidAppTutorial_Modiule4_Image8

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.

Este módulo foi útil?

Agradecemos a sua atenção
Gostaríamos de saber do que você gostou.
Fechar
Desculpe por ter desapontado
Encontrou algo desatualizado, confuso ou incorreto? Envie seus comentários para nos ajudar a melhorar este tutorial.
Fechar

Adicionar armazenamento