模块 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) 将使用预构建数据 schema 打开。
删除此 schema,并使用我们的应用程序 GraphQL schema 替换:
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 { 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 数据模型和应用程序模型之间添加桥接
我们的项目已经有一个数据模型来表示备注。在此教程中,我们将继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 UserData.kt 并添加两个组件:一个动态属性,从 UserData.Note 返回 NoteData 对象;一个相反静态方法,接收 API NoteData 并返回 Userdata.Note。
在数据类 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:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型 (Note),以便通过用户界面轻松交互。这些方法可以透明地将 Note 转换为 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,以查询当前登录用户的 Note 列表。
在 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() } }
现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。
-
添加“Edit”按钮以添加备注
现在,后端和数据模型已到位,本节的最后一步是让用户创建新的 Note 然后将其删除。
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>
这是一个非常简单的布局,可以只输入 Note 标题和描述。
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.在“Main Activity”中添加一个“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 作为名称,并从“Clip Art”中选择添加图标。单击 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”按钮会在用户登录时显示,在用户注销时消失。您现在可以添加备注了。
-
添加“滑动删除”行为
可以通过在 Note 列表中添加触摸处理程序来添加“滑动删除”行为。触摸处理程序负责绘制红色背景、删除图标,并在释放触摸时调用 Backend.delete() 方法。
a.创建一个新类 SimpleTouchCallback。在 java/com 下,右键单击 example.androidgettingstarted,依次选择 New、Kotlin File,然后输入 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 结构(这将更新用户界面)和云后端中删除。
b.现在,我们已经有了类,让我们在 res/drawable 中添加“删除”图标。右键单击“drawable”,选择 New,然后选择 Vector Asset。输入 ic_baseline_delete_sweep 作为名称,并从“Clip Art”中选择“删除滑动”图标。单击 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)
-
构建和测试
要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。
假设您仍处于登录状态,应用程序会在一个空列表上启动。它现在具有一个用于添加 Note 的“Add Note”按钮。单击“Add Note”符号、输入标题和描述、单击“Add Note”按钮,随后备注应显示在列表中。
您可以通过向左滑动一行来删除 Note。
以下是完整流程。
结论
现在您已创建了一个 Android 应用程序,并使用 AWS Amplify 添加了 GraphQL API,以及在应用程序中配置了创建、读取和删除功能。
在下一个模块中,我们将添加 UI 和行为来管理图片。