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 (確定)。
透過貼上以下內容,開啟剛建立的檔案 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 (完成)。
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 作為名稱。
將以下程式碼貼至該新檔案中:
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 (完成)。
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 (新增附註) 按鈕,附註應出現在清單中。
您可以向左滑動以刪除附註。
這就是完整的流程。