AWS 入門

建置 Android 應用程式

使用 AWS Amplify 建立簡單的 Android 應用程式

單元 4:新增 GraphQL API 和資料庫

在本單元中,您將使用 Amplify CLI 和程式庫來設定 GraphQL API,並將其新增到您的應用程式。

簡介

現在,我們已經透過使用者身份驗證建立並設定該應用程式,我們來新增一個 API 並對資料庫進行建立、讀取、更新、刪除 (CRUD) 操作。

在本單元中,您將使用 Amplify CLI 和庫將 API 新增到您的應用程式中。您將建立的 API 是 GraphQL API,它利用了由 Amazon DynamoDB (NoSQL 資料庫) 支援的 AWS AppSync (受管的 GraphQL 服務)。如需有關 GraphQL 的介紹,請瀏覽本頁

您將要建立的應用程式是一個記事應用程式,使用者可以用它來建立、刪除和列出筆記。透過本範例,您能夠很好地了解如何建立許多熱門的 CRUD+L 類型 (建立、讀取、更新、刪除和列出) 應用程式。

您將學到的內容

  • 建立和部署 GraphQL API
  • 編寫與 API 互動的前端程式碼

主要概念

API – 提供程式開發界面,允許多個軟體仲介之間的通訊和互動。

GraphQL – 以應用程式具類型表示為基礎的查詢語言和伺服器端 API 實作。此 API 表示通過以 GraphQL 類型系統為基礎的結構描述進行聲明。(如需了解有關 GraphQL 的更多資訊,請參閱本頁。)

 完成時間

20 分鐘

 使用的服務

實作

  • 建立 GraphQL API 服務和資料庫

    若要建立 GraphQL API 及其支援資料庫,請開啟終端機,然後從您的專案目錄中執行此命令

    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

    初始化專案 (amplify init) 時,選取的預設文字編輯器將以預先建置的資料結構描述開啟。

    刪除結構描述,並用我們的應用程式 GraphQL 結構描述將其取代

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

    資料模型由一個類別 NoteData 和 4 個屬性組成:ID 和名稱為必要,描述和影像為選用字串。

    @model 轉換器表示我們要建立一個資料庫來存放資料。

    @auth 轉換器新增身份驗證規則,以允許存取該資料。對於此專案,我們只想要 NoteData 的擁有者可以存取。

    完成後,別忘了儲存,然後返回終端機,告訴 Amplify CLI 您已完成。

    ? Press enter to continue, press enter.

    幾秒鐘後,您應看到一則成功訊息︰

    GraphQL schema compiled successfully.
  • 產生用戶端側程式碼

    根據我們剛建立的 GraphQL 資料模型定義,Amplify 將產生用戶端側程式碼 (即 Swift 程式碼),以表示我們應用程式中的資料。

    若要產生程式碼,請前往您的終端機,執行以下命令:

    amplify codegen models

    這會在 java/com/amplifyframework.datastore.generated.model 目錄中建立 Java 檔案,如您所見:

    ➜  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

    檔案會自動匯入您的專案中。

  • 部署 API 服務和資料庫

    若要部署我們剛建立的後端 API 和資料庫,請前往您的終端機並執行命令

    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

    幾分鐘後,您應會看到一則成功訊息:

    ✔ All resources are updated in the cloud
    
    GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
  • 將 API 用戶端程式庫新增至 Android Studio 專案

    在前往程式碼之前,請返回 Android Studio,將以下相依項以及之前新增的其他 'amplifyframework' 實作,新增至單元的 build.gradle 中,並在提示時按一下 Sync Now (立即同步)

    dependencies {
        implementation 'com.amplifyframework:aws-api:1.4.0'
        implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'
    }
  • 在執行時間初始化 Amplify 程式庫

    開啟 Backend.kt,並在 initialize() 方法的 Amplify 初始化序列中新增一行。完整的 try/catch 區塊應如下所示:

    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)
    }
  • 在 GraphQL 資料模型與 App 模型間新增橋接

    我們的專案已有一個資料模型來表示附註。在本教學中,我們將繼續使用該模型,並提供一種將 NoteData 轉換為附註的簡便方法。開啟 UserData.kt 並新增兩個元件:動態屬性,該屬性從 UserData.Note 返回 NoteData 物件;以及對應項:靜態方法,該方法接受 API NoteData 並返回 Userdata.Note。

    在資料類別附註內,新增以下內容:

    // 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
        }
    }     

    確保從產生的程式碼匯入 NoteData 類別。

  • 將 API CRUD 方法新增至後端類別

    我們新增 3 種方法來叫用我們的 API:一種用於查詢附註,一種用於建立新的附註,以及一種用於刪除附註。請注意,這些方法適用於應用程式資料模型 (附註),以簡化與使用者界面的互動。這些方法將附註以透明的方式轉換為 GraphQL 的 NoteData 物件。

    開啟 Backend.kt 檔案,然後在後端類別的末尾新增以下程式碼片段:

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

    確保從產生的程式碼中匯入 ModelQuery、ModelMutation 和 NoteData 類別。

    最後,應用程式啟動後,我們必須叫用 API 來查詢目前登入使用者的附註清單。

    在 Backend.kt 檔案中,將 updateUserData(withSignInStatus: Boolean) 方法更新為如下所示:

    // 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()
        }
    }

    現在,剩下的就是建立使用者界面,以建立新的附註並從清單中刪除附註。

  • 新增編輯按鈕以新增附註

    現在已經設置後端和資料模型區塊,這部分的最後一步是允許使用者建立新的附註以及將其刪除。

    a.在 Android Studio 的 res/layout 下,建立新的版面配置:在 layout (版面配置) 上按一下滑鼠右鍵並選取 New (新建),然後選取 Layout Resource File (版面配置資源檔案)。將其命名為 activity_add_note 並接受所有其他預設值。按一下 OK (確定)

    AndroidAppTutorial_Modiule4_Image1

    透過貼上以下內容,開啟剛建立的檔案 activity_add_note 並取代產生的程式碼:

    <?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>

    這是一個非常簡單的版面配置,允許輸入附註標題和描述。

    b.新增 AddNoteActivity 類別。

    在 java/com.example.androidgettingstarted 下建立一個新的 kotlin 檔案 AddActivityNote.kt,將其開啟並新增以下程式碼: 

    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"
        }
    }    

    最後,在資訊清單下,開啟 AndroidManifest.xml 並將此活動元素新增至應用程式節點內的任何位置。

    <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.在主要活動中新增 "Add Note" (新增附註) FloatingActionButton。在 res/layout 下,開啟 activity_main.xml 並將其新增至現有的 Floating Action Button 上方。

    <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"/>

    在 res/drawable 中新增 "Add Note" (新增附註) 圖示。在 drawable 上按一下滑鼠右鍵,選取 New (新建),然後選取 Vector Asset (向量資產)。輸入 ic_baseline_add 作為名稱,然後從美工圖案​​中選取新增圖示。按一下 Next (下一步),然後按一下 Finish (完成)

    AndroidAppTutorial_Modiule4_Image2

    d.新增程式碼以處理 "Add Note" (新增附註) 按鈕。

    設定完整功能的 "Add Button" (新增按鈕) 需要做的最後兩件事是,根據 isSignedIn 值使按鈕出現或消失,顯然,要新增程式碼來處理按鈕的點按。

    開啟 mainActivity.kt,並將其新增至 onCreate() 方法的末尾:

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

    然後,仍然在 onCreate() 方法中,將 UserData.isSignedIn.observe 取代為:

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

    若要驗證是否一切如預期,請建置專案。按一下 Build (建置) 功能表,然後選取 Make Project (製作專案),或者在 Mac 上,鍵入 ⌘F9。應不會發生錯誤。

    執行該應用程式時,您會看到使用者登入時出現 "Add Note" (新增附註) 按鈕,並在使用者登出時消失。您現在可以新增附註。

  • 新增滑動刪除行為

    透過在附註清單中新增觸摸處理程式,可滑動刪除行為。觸摸處理程式負責繪製紅色背景,刪除圖示,並在釋放觸摸時叫用 Backend.delete() 方法。

    a.建立新的類別 SimpleTouchCallback。在 java/com 下的 example.androidgettingstarted 上按一下滑鼠右鍵,選取 New (新建),然後選取 Kotlin File (Kotlin 檔案),並輸入 SwipeCallback 作為名稱。

    AndroidAppTutorial_Modiule4_Image3

    將以下程式碼貼至該新檔案中:

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

    重要的程式碼行即會在 onSwiped() 方法中。滑動手勢完成後,將叫用該方法。我們在清單中收集用於滑動項目的位置,並從 UserData 結構 (這會更新 UI) 和雲端後端中刪除相應的附註。

    b.現在我們有了一個類別,我們在 res/drawable 中新增 "Delete" (刪除) 圖示。在 drawable 上按一下滑鼠右鍵,選取 New (新建),然後選取 Vector Asset (向量資產)。輸入 ic_baseline_delete_sweep 作為名稱,然後從美工圖案​​中選取 "delete sweep" (刪除輕掃) 圖示。按一下 Next (下一步),然後按一下 Finish (完成)

    AndroidAppTutorial_Modiule4_Image4

    c.將以下程式碼貼至該新檔案中:將滑動刪除手勢處理程式新增至 RecyclerView。

    在 java/com/example.androidgettingstarted 下,開啟 MainActivity.kt 並在 setupRecyclerView 中新增以下兩行程式碼:

    // add a touch gesture handler to manager the swipe to delete gesture
    val itemTouchHelper = ItemTouchHelper(SwipeCallback(this))
    itemTouchHelper.attachToRecyclerView(recyclerView)
  • 建立和測試

    若要驗證是否一切如預期,請建置並執行專案。按一下工具列中的 Run (執行) 圖示 ▶️,或鍵入 ^ R。應不會發生錯誤。

    假設您仍處於登入狀態,該應用程式將從空白清單開始。其現在有一個 Add Note (新增附註) 按鈕,可用於新增附註。點按 Add Note (新增附註) 符號,輸入標題,輸入描述,點按 Add Note (新增附註) 按鈕,附註應出現在清單中。

    您可以向左滑動以刪除附註。

    這就是完整的流程。

    AndroidAppTutorial_Modiule4_Image5
    AndroidAppTutorial_Modiule4_Image6
    AndroidAppTutorial_Modiule4_Image7
    AndroidAppTutorial_Modiule4_Image8

結論

您現在已經建立了一個 Android 應用程式! 透過使用 AWS Amplify,您為您的應用程式新增了 GraphQL API,並設定了建立、讀取和刪除功能。

在下一個單元中,我們將新 UI 和行為來管理圖片。

這個單元對您是否有幫助?

感謝您
請告訴我們您喜歡的部分。
關閉
抱歉,讓您失望
是有內容過時、令人困擾,或不準確嗎? 請提供意見回饋,協助我們改進此教學課程。
關閉

新增儲存體