使用 Amazon Amplify 快速创建简单的 Android 应用程序

Android
Android Studio
Node.js
0
0
{"value":"### **背景:**\n亚马逊云科技提供了100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 首年12个月免费,5GB标准存储容量。\n[https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el](https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el)\n大家好,我是坚果,由于最近一直疫情居家,所以想到外边转转,但是确实出不去,那么作为程序员的我们肯定也不能闲着,于是想做一个很简单的旅行应用,具有基础的增删改查的功能。以及身份验证!这个时候考虑到 Amazon 具有高响应,高可用等特点,这个时候你只要 Amazon 结合一定的 Android 基础,就可以很方便快捷的拥有自己的应用程序。而且由于 Amazon 具有全球优势的特点,以及 Amazon Amplify 是一组位于云端的工具和[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)服务都让他拥有了一定的优势,这样一来技术选型接确定了,那么说了这么多,如何结合 Amazon 很快的创建属于自己的 Android 应用程序呢?只需要简单的五个步骤就可以。接下来开始正文\n开始之前看一下具体你要具备哪些条件:\n### **先决条件**\n- [Android Studio 4.x](https://developer.android.com/studio) 或更高版本\n- 一个至少具有[这些权限](https://github.com/sebsto/amplify-ios-getting-started/blob/master/amplify-policy.json)的 [Amazon](https://aws.amazon.com/cn/getting-started/hands-on/build-android-app-amplify/?trk=6c2fcc87-4e1c-4db1-bedd-836152ec5ac8&sc_channel=el) 账户\n- [Node.js](https://nodejs.org/en/) 10 或更高版本\n- Amazon 命令行界面 [Amazon CLI 2.0.x](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html) 或更高版本。\n 检查 noidejs 版本,node -v,发现版本是16.13.0,符合条件 \nandroid studio 版本查看方式\n点击 help-about,如图所示:版本符合要求\n\n![image.png](https://dev-media.amazoncloud.cn/a47f4ae90660444fba425493fd56337b_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/09dbecd7ea1d4e598f17798315f155ee_image.png)\n\n接下来构建您的首个 Android 应用程序。\n### **概览**\n在本文中,您将使用 Amazon Amplify 创建一个简单的 Android 应用程序,Amazon Amplify 是一组位于云端的工具和[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)服务。在第一个单元中,您将构建一个简单的 Android 应用程序。在其余单元中,您将使用 Amplify 命令行接口 (Amplify CLI) 初始化一个本地应用程序、添加用户身份验证、添加一个 GraphQL API 和一个数据库以存储您的数据,并更新您的应用程序以存储图像。\n本文将引导您完成创建上面讨论的简单 Android 应用程序。\n本文分为五个简短单元。您必须按顺序完成每个单元才能进入下一单元。\n\n![image.png](https://dev-media.amazoncloud.cn/ee205bcc78724130a798c70e10f1fee6_image.png)\n\n使用 Amazon Amplify 创建简单的 Android 应用qq\n 1.构建 Android 应用程序\n 在 Android 模拟器中创建 Android 应用程序并测试\n 2.初始化本地应用程序\n 使用 Amazon Amplify 初始化本地应用程序\n 3.添加身份验证\n 添加身份验证到您的应用程序中\n 4.添加API和数据库\n 创建 GraphQL API\n 5.添加存储\n 添加存储到您的应用程序中\n\n![image.png](https://dev-media.amazoncloud.cn/2484d943c9c146f89de1a07cc587cb95_image.png)\n\n您将使用终端和 Google 的 [Android Studio](https://developer.android.com/studio) IDE 来构建此 Android 应用程序。\n### **1.创建和部署 Android 应用程序**\n在这,您将创建 Android 应用程序并使用 Amazon Amplify 的 Web 托管服务将其部署到云\n\n![image.png](https://dev-media.amazoncloud.cn/8c442d8140d44705a0ff7f51cb67e8f4_image.png)\n\n#### **1.1简介**\nAmazon Amplify 提供基于 Git 的工作流,用于创建、管理、集成和部署 Web 和移动应用程序的[无服务器](https://aws.amazon.com/cn/serverless/?trk=cndc-detail)后端。Amplify CLI 提供了一个简单的基于文本的用户界面,用于预置和管理后端服务,如用于您的应用程序的用户身份验证或 REST 或 GraphQL API。使用 Amplify 库可以轻松地将这些后端服务与应用程序中的几行代码集成。\n#### **1.2实施**\n##### **1.2.1.创建 Android 项目**\n启动 Android Studio,然后选择 New Project\n\n![image.png](https://dev-media.amazoncloud.cn/3092c1b798444ae28d5ac860d2966403_image.png) \n\n在 **Phone and Tablet** 下,选择 **Basic Activity**,然后单击 **Next**:\n\n![image.png](https://dev-media.amazoncloud.cn/3794940a560d4ad5a96868f197cbdf6b_image.png)\n\n为您的项目键入名称,例如 **Android Amazon Started**。确保语言为 **Kotlin**,开发工具包最低为 **API 26: Android 8.0 (oreo)**,然后单击 **Finish**:\n\n![image.png](https://dev-media.amazoncloud.cn/2bca974217564e55a0d401a7308204de_image.png)\n\n现在,项目的框架已经存在,我们需要完成 4 个步骤来让我们的基本应用程序运行:\n1. 删除不需要的类和文件,并添加插件\n2. 创建类以保留数据结构\n3. 创建视图以在列表中保留单个备注\n4. 修改 MainActivity 以显示备注列表\n\n##### **1.2.2.删除不需要的类和文件,并添加插件**\n在“res/layout”和 java/com.example.androidgettingstarted 下,删除四个**片段**文件(选择这 4 个文件,右键单击,然后从上下文菜单中选择 **Delete** ):\n\n![image.png](https://dev-media.amazoncloud.cn/e26a3abb225445d3a37394de4ddb0604_image.png)\n\n在 **Gradle Scripts**下,打开 **build.gradle (Module:app)**,并添加 Kotlin 扩展插件。\nKotlin \n```\\nplugins {\\n id 'com.android.application'\\n id 'kotlin-android'\\n id 'kotlin-android-extensions' // <== add this line\\n}\\n```\n![image.png](https://dev-media.amazoncloud.cn/86c68dd2c20d46ec95ef825b6b45bd4e_image.png)\n\n##### **1.2.3.创建类以保留数据结构**\nUserData 类可保留用户状态:一个 isSignedIn 标记和一个备注值列表。\n要创建新类,请右键单击 java/com.example/androidgettingstarted,然后选择 **New -> Kotlin file/class**。键入 **UserData** 作为名称。\n\n![image.png](https://dev-media.amazoncloud.cn/5c748cf53d864bfdadb004699c99e3de_image.png)\n\n将以下代码粘贴到新创建的文件 (UserData.kt) 中 \nKotlin\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.graphics.Bitmap\\nimport android.util.Log\\nimport androidx.lifecycle.LiveData\\nimport androidx.lifecycle.MutableLiveData\\n\\n// a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)\\nobject UserData {\\n\\n private const val TAG = \\"UserData\\"\\n\\n //\\n // observable properties\\n //\\n\\n // signed in status\\n private val _isSignedIn = MutableLiveData<Boolean>(false)\\n var isSignedIn: LiveData<Boolean> = _isSignedIn\\n\\n fun setSignedIn(newValue : Boolean) {\\n // use postvalue() to make the assignation on the main (UI) thread\\n _isSignedIn.postValue(newValue)\\n }\\n\\n // the notes\\n private val _notes = MutableLiveData<MutableList<Note>>(mutableListOf())\\n\\n // please check https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedata\\n private fun <T> MutableLiveData<T>.notifyObserver() {\\n this.postValue(this.value)\\n }\\n fun notifyObserver() {\\n this._notes.notifyObserver()\\n }\\n\\n fun notes() : LiveData<MutableList<Note>> = _notes\\n fun addNote(n : Note) {\\n val notes = _notes.value\\n if (notes != null) {\\n notes.add(n)\\n _notes.notifyObserver()\\n } else {\\n Log.e(TAG, \\"addNote : note collection is null !!\\")\\n }\\n }\\n fun deleteNote(at: Int) : Note? {\\n val note = _notes.value?.removeAt(at)\\n _notes.notifyObserver()\\n return note\\n }\\n\\n fun resetNotes() {\\n this._notes.value?.clear() //used when signing out\\n _notes.notifyObserver()\\n }\\n\\n\\n // a note data class\\n data class Note(val id: String, val name: String, val description: String, var imageName: String? = null) {\\n override fun toString(): String = name\\n\\n // bitmap image\\n var image : Bitmap? = null\\n\\n }\\n}\\n```\n**我们刚刚添加了哪些内容?**\n- UserData 类负责保留用户数据,即一个 isSignedIn 标记用于跟踪当前的身份验证状态和备注对象列表。\n- 这两个属性是根据 LiveData 发布/订阅框架来实现的。它允许图形用户界面 (GUI) 订阅更改并做出相应反应。\n- 我们还添加了一个备注数据类,仅用于保留单个备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。我们将在后续单元中介绍 Image。\n- 为 UserData 对象实施了单态设计模式,因为此模式允许只使用 UserData 即可从应用程序的任何位置引用此对象。\n##### **1.2.4.为列表中的但各单元格添加GUI**\n滚动列表中的单个单元格称为 RecyclerView,因为当用户上下滚动屏幕,屏幕上再也看不到视图时,可以回收视图。\n与常规视图一样,我们也创建了布局 XML 文件和 Kotlin 类。单个单元格类似以下内容:\n\n![image.png](https://dev-media.amazoncloud.cn/4e951e9df2354c448190f35b1c682eb4_image.png)\n\n要创建布局文件,请右键单击“res/layout”,然后选择 **New -> Layout Resource File**。键入 content_note 作为名称,并保留所有其他值,因为我们将直接编辑 XML 文件\n\n![image.png](https://dev-media.amazoncloud.cn/8a746fdec5bc41328a04d19c26a4d081_image.png)\n\n打开新创建的文件的 **Code** 视图。\n\n![image.png](https://dev-media.amazoncloud.cn/f7f0f58455cb4df2b74832687c562ede_image.png)\n\n在新创建的文件 (content_note.xml) 的“Code”视图中,通过粘贴以下代码来替代所生成的代码:\nKotlin\n```\\n<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>\\n<LinearLayout xmlns:android=\\"http://schemas.android.com/apk/res/android\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:orientation=\\"horizontal\\"\\n android:paddingVertical=\\"16dp\\">\\n\\n <ImageView\\n android:id=\\"@+id/image\\"\\n android:layout_width=\\"100dp\\"\\n android:layout_height=\\"match_parent\\"\\n android:padding=\\"8dp\\"\\n android:scaleType=\\"centerCrop\\"\\n android:src=\\"@drawable/ic_launcher_background\\" />\\n\\n <LinearLayout\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_gravity=\\"center\\"\\n android:layout_marginLeft=\\"5dp\\"\\n android:orientation=\\"vertical\\">\\n\\n <TextView\\n android:id=\\"@+id/name\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:text=\\"Title\\"\\n android:textSize=\\"20sp\\"\\n android:textStyle=\\"bold\\" />\\n\\n <TextView\\n android:id=\\"@+id/description\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:text=\\"Title\\"\\n android:textSize=\\"15sp\\" />\\n\\n </LinearLayout>\\n</LinearLayout>\\n```\n最后,创建 Kotlin 类:右键单击 java/com.example/androidgettingstarted,然后选择 **New -> Kotlin file/class**。键入 **NoteRecyclerViewAdapter** 作为名称。\n将以下代码粘贴到新创建的文件 (NoteRecyclerViewAdapter.kt) 中\nKotlin \n```\\npackage com.example.androidgettingstarted\\n\\nimport android.view.LayoutInflater\\nimport android.view.View\\nimport android.view.ViewGroup\\nimport android.widget.ImageView\\nimport android.widget.TextView\\nimport androidx.recyclerview.widget.RecyclerView\\n\\n// this is a single cell (row) in the list of Notes\\nclass NoteRecyclerViewAdapter(\\n private val values: MutableList<UserData.Note>?) :\\n RecyclerView.Adapter<NoteRecyclerViewAdapter.ViewHolder>() {\\n\\n override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\\n val view = LayoutInflater.from(parent.context)\\n .inflate(R.layout.content_note, parent, false)\\n return ViewHolder(view)\\n }\\n\\n override fun onBindViewHolder(holder: ViewHolder, position: Int) {\\n\\n val item = values?.get(position)\\n holder.nameView.text = item?.name\\n holder.descriptionView.text = item?.description\\n\\n if (item?.image != null) {\\n holder.imageView.setImageBitmap(item.image)\\n }\\n }\\n\\n override fun getItemCount() = values?.size ?: 0\\n\\n inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {\\n val imageView: ImageView = view.findViewById(R.id.image)\\n val nameView: TextView = view.findViewById(R.id.name)\\n val descriptionView: TextView = view.findViewById(R.id.description)\\n }\\n}\\n```\n**我们刚刚添加了哪些内容?**\n上述代码包含\n- 一个布局 xml 文件,该文件说明表示备注的单元格上的组件布局。它显示图像、备注标题和备注说明。\n- 一个支持 Kotlin 类。它在创建时接收备注数据对象,并将单个值分配给其相对应的视图(图像、标题和说明)\n##### **1.2.5.更新主要活动**\n现在,我们已经有了数据类(UserData 和备注)和单个备注的视图 (NoteRecyclerViewAdapter),让我们在主要活动上创建备注列表。\n从 Android Studio 左侧的文件列表,打开 res/layout/content_main.xml,并将代码替换为以下内容:\nXML\n```\\n<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>\\n<FrameLayout xmlns:android=\\"http://schemas.android.com/apk/res/android\\"\\n xmlns:app=\\"http://schemas.android.com/apk/res-auto\\"\\n xmlns:tools=\\"http://schemas.android.com/tools\\"\\n android:id=\\"@+id/frameLayout\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"match_parent\\"\\n >\\n\\n <androidx.recyclerview.widget.RecyclerView\\n android:id=\\"@+id/item_list\\"\\n android:name=\\"com.example.myapplication.ItemListFragment\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"match_parent\\"\\n android:layout_marginTop=\\"60dp\\"\\n\\n android:paddingHorizontal=\\"8dp\\"\\n android:paddingVertical=\\"8dp\\"\\n app:layoutManager=\\"LinearLayoutManager\\"\\n tools:context=\\".MainActivity\\"\\n tools:listitem=\\"@layout/content_note\\" />\\n\\n</FrameLayout>\\n```\n从 Android Studio 左侧的文件列表,打开 java/com.example/androidgettingstarted/MainActivity.kt,并将代码替换为以下内容:\nXML\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.os.Bundle\\nimport android.util.Log\\nimport androidx.appcompat.app.AppCompatActivity\\nimport androidx.lifecycle.Observer\\nimport androidx.recyclerview.widget.ItemTouchHelper\\nimport androidx.recyclerview.widget.RecyclerView\\nimport kotlinx.android.synthetic.main.activity_main.*\\nimport kotlinx.android.synthetic.main.content_main.*\\n\\nclass MainActivity : AppCompatActivity() {\\n\\n override fun onCreate(savedInstanceState: Bundle?) {\\n super.onCreate(savedInstanceState)\\n setContentView(R.layout.activity_main)\\n setSupportActionBar(toolbar)\\n\\n // prepare our List view and RecyclerView (cells)\\n setupRecyclerView(item_list)\\n }\\n\\n // recycler view is the list of cells\\n private fun setupRecyclerView(recyclerView: RecyclerView) {\\n\\n // update individual cell when the Note data are modified\\n UserData.notes().observe(this, Observer<MutableList<UserData.Note>> { notes ->\\n Log.d(TAG, \\"Note observer received \${notes.size} notes\\")\\n\\n // let's create a RecyclerViewAdapter that manages the individual cells\\n recyclerView.adapter = NoteRecyclerViewAdapter(notes)\\n })\\n }\\n\\n companion object {\\n private const val TAG = \\"MainActivity\\"\\n }\\n}\\n```\n**我们刚刚添加了哪些内容?**\n- 主要布局是一个 RecyclerView,用于管理我们之前创建的单个单元格列表\n- 主要活动类可观察备注列表的变化,并创建一个 NoteRecyclerViewAdapter 以创建单个单元格。\n##### **1.2.6.验证生成依赖项**\n- 在 Gradle Scripts 下,打开 build.gradle (Module:app),并验证所生成的依赖关系是否正确。需要选中 \nlibraries versions。\nKotlin\n```\\ngradle\\ndependencies {\\n implementation \\"org.jetbrains.kotlin:kotlin-stdlib:\$kotlin_version\\"\\n implementation 'androidx.core:core-ktx:1.3.2'\\n implementation 'androidx.appcompat:appcompat:1.2.0'\\n implementation 'com.google.android.material:material:1.2.1'\\n implementation 'androidx.constraintlayout:constraintlayout:2.0.2'\\n implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'\\n implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'\\n testImplementation 'junit:junit:4.+'\\n androidTestImplementation 'androidx.test.ext:junit:1.1.2'\\n androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\\n}\\n```\n##### **1.2.7.构建和测试**\n- 现在,请在模拟器中构建并启动应用程序。单击工具栏中的运行图标 ▶️ 或按 ^ R。\n\n![image.png](https://dev-media.amazoncloud.cn/87a6ad453d4d460aa2c3778bcc5062ea_image.png)\n\n片刻之后,应用程序会在 Android 模拟器中启动,初始屏幕为空。\n\n![image.png](https://dev-media.amazoncloud.cn/f09db6d5333a424ea387659236ef98f6_image.png)\n\n在此阶段,没有要在运行时呈现的数据。到此您已成功创建 Android 应用程序。接下来开始使用 Amplify 进行构建!\n### **2.初始化 Amplify**\n在此单元中,您将安装并配置 Amplify CLI。\n\n![image.png](https://dev-media.amazoncloud.cn/854fccc737b24fd09e6ba661c03406c3_image.png)\n\n#### **2.1简介**\n现在我们已创建一个 Android 应用程序,我们想要继续开发并添加新功能。\n要开始在应用程序中使用 Amazon Amplify,必须安装 Amplify 命令行,初始化 Amplify 项目目录,将项目配置为使用 Amplify 库,并在运行时初始化 Amplify 库。\n#### **2.2实施**\n##### **2.2.1安装 Amplify CLI**\nAmazon Amplify CLI 取决于 Node.js,没有安装的化,请安装。\n要安装 Amazon Amplify CLI,请打开一个终端,然后**输入以下命令**:\nXML\n```\\n## Install Amplify CLI\\nnpm install -g @aws-amplify/cli\\n```\n![image.png](https://dev-media.amazoncloud.cn/9ff3927ea71f4080805c63e66a2e93f2_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/3fa058c3818e415bb10ff863ac0c1e2e_image.png)\n查看版本:\n\n![image.png](https://dev-media.amazoncloud.cn/d3e0b4ed2bb246d4ba0f0ff51ace1029_image.png)\nXML\n```\\n## Verify installation and version\\namplify --version\\n\\n 7.6.26\\n```\n##### **2.2.2初始化 Amplify 后端**\n要创建后端基本结构,首先需要初始化 Amplify 项目目录并创建云后端。\n打开项目所在目录,输入cmd,即可打开终端\n\n![image.png](https://dev-media.amazoncloud.cn/518a4a0c0f5542a3a56ad685079f8650_image.png)\n\n![image.png](https://dev-media.amazoncloud.cn/f221c0299308436380a7de2015ee1124_image.png)\n验证您是否在正确的目录中,它应该如下所示:\nXML\n```\\nF:\\\\workspace\\\\AndroidASW>tree\\n卷 开发环境 的文件夹 PATH 列表\\n卷序列号为 D4D7-D5B3\\nF:.\\n├─.gradle\\n│ ├─7.0.2\\n│ │ ├─dependencies-accessors\\n│ │ ├─fileChanges\\n│ │ ├─fileHashes\\n│ │ └─vcsMetadata-1\\n│ ├─buildOutputCleanup\\n│ ├─checksums\\n│ └─vcs-1\\n├─.idea\\n│ ├─libraries\\n│ └─modules\\n│ └─app\\n├─app\\n│ ├─libs\\n│ └─src\\n│ ├─androidTest\\n│ │ └─java\\n│ │ └─com\\n│ │ └─example\\n│ │ └─androidasw\\n│ ├─main\\n│ │ ├─java\\n│ │ │ └─com\\n│ │ │ └─example\\n│ │ │ └─androidasw\\n│ │ └─res\\n│ │ ├─drawable\\n│ │ ├─drawable-v24\\n│ │ ├─layout\\n│ │ ├─menu\\n│ │ ├─mipmap-anydpi-v26\\n│ │ ├─mipmap-hdpi\\n│ │ ├─mipmap-mdpi\\n│ │ ├─mipmap-xhdpi\\n│ │ ├─mipmap-xxhdpi\\n│ │ ├─mipmap-xxxhdpi\\n│ │ ├─navigation\\n│ │ ├─values\\n│ │ ├─values-land\\n│ │ ├─values-night\\n│ │ ├─values-w1240dp\\n│ │ └─values-w600dp\\n│ └─test\\n│ └─java\\n│ └─com\\n│ └─example\\n│ └─androidasw\\n└─gradle\\n └─wrapper\\n\\nF:\\\\workspace\\\\AndroidASW>\\n```\n接下来,在亚马逊服务管理后台创建对应的用户,并添加权限类型,具体如下图所示:\n##### **2.2.3控制台相关操作**\n**[第一步注册](https://portal.aws.amazon.com/billing/signup?type=resubscribe#/paymentinformation)**\n\n![image.png](https://dev-media.amazoncloud.cn/be189597410f469dbc440aab8f2310f8_image.png)\n\n[https://aws.amazon.com/cn/free/?trk=95502bdb-28e0-4dc1-895c-2e975a171d36&sc_channel=ba&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=all&awsf.Free%20Tier%20Categories=all](https://aws.amazon.com/cn/free/?trk=95502bdb-28e0-4dc1-895c-2e975a171d36&sc_channel=ba&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=all&awsf.Free%20Tier%20Categories=all)\n**登录控制台**\n\n![image.png](https://dev-media.amazoncloud.cn/b4664aea104b4304ba4be59142e994ce_image.png)\n\n添加权限\n\n![image.png](https://dev-media.amazoncloud.cn/e278f8491a444689b895bf10ee4aa172_image.png)\n\n点击“下一步”,选择“直接附加现有策略”,一直“下一步\",后会提示创建用户成功。\n\n**添加用户**\n\n![image.png](https://dev-media.amazoncloud.cn/a81fb1f130e54e32b6f48104a9ceffd7_image.png)\n\n到此,我们也就创建完成。接下来继续下面的步骤。\n初始化 Amplify 项目结构和配置文件。**运行以下命令**:\namplify init\nJavaScript\n```\\n\\n? Enter a name for your project (androidgettingstarted): accept the default, press enter\\n? Enter a name for the environment (dev): accept the default, press enter\\n? Choose your default editor: use the arrow key to select your favorite text editor an press enter\\n? Choose the type of app that you're building: android is already selected, press enter\\n? Where is your Res directory: accept the default, press enter\\n? Do you want to use an AWS profile?, Y, press enter\\n? Please choose the profile you want to use: use the arrow keys to select your profile and press enter.\\n```\n如果没有配置文件,可使用 Amazon CLI 键入命令 aws configure --profile <name> 创建一个。\\nAmplify 在云中初始化您的项目,可能需要几分钟。几分钟后,您应该会看到如下消息:\\n\\n![image.png](https://dev-media.amazoncloud.cn/be6f4d491d5d4ab59aac77454cb772e4_image.png)\\n\\n##### **2.2.4将 Amplify 库添加到项目中**\\nAmplify for Android 是作为 Apache Maven 软件包分发的。在本部分中,您会将软件包和其他必需的指令添加到构建配置中。\\n返回 Android Studio,展开 Gradle Scripts 并打开 build.gradle (Project: Android_Getting_Started)。在 buildscript 和 allprojects 数据块的 repositories 数据块内添加行 mavenCentral()。\\nJavaScript\\n```\\nbuildscript {\\n ext.kotlin_version = \\"1.4.10\\"\\n repositories {\\n google()\\n jcenter()\\n\\n // Add this line into `repositories` in `buildscript`\\n mavenCentral()\\n }\\n dependencies {\\n classpath \\"com.android.tools.build:gradle:4.0.1\\"\\n classpath \\"org.jetbrains.kotlin:kotlin-gradle-plugin:\$kotlin_version\\"\\n\\n // NOTE: Do not place your application dependencies here; they belong\\n // in the individual module build.gradle files\\n }\\n}\\n\\nallprojects {\\n repositories {\\n google()\\n jcenter()\\n\\n // Add this line into `repositories` in `buildscript`\\n mavenCentral()\\n }\\n}\\n```\\n在 **Gradle Scripts** 下,打开 **build.gradle (Module:app)**,并在 implementations 数据块中添加 Amplify 框架核心依赖项。\\nJavaScript\\n```\\ndependencies {\\n implementation fileTree(dir: \\"libs\\", include: [\\"*.jar\\"])\\n implementation \\"org.jetbrains.kotlin:kotlin-stdlib:\$kotlin_version\\"\\n implementation 'androidx.core:core-ktx:1.3.2'\\n implementation 'androidx.appcompat:appcompat:1.2.0'\\n implementation 'com.google.android.material:material:1.2.1'\\n implementation 'androidx.constraintlayout:constraintlayout:2.0.1'\\n implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'\\n implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'\\n testImplementation 'junit:junit:4.13'\\n androidTestImplementation 'androidx.test.ext:junit:1.1.2'\\n androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\\n\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n}\\n```\\n如果您使用 Java 或目标 Android SDK 21 或更早版本进行开发,请[查看文档](https://docs.amplify.aws/lib/project-setup/create-application/q/platform/android#n2-install-amplify-libraries)了解其他配置更改。\\n现在,运行 **Gradle Sync**。\\n\\n![image.png](https://dev-media.amazoncloud.cn/99e6a70cb30044e5ba1901d3a1b16e16_image.png)\\n\\n过一会儿您将看到\\nBUILD SUCCESSFUL in 1s\\n\\n![image.png](https://dev-media.amazoncloud.cn/5e582ca7acd04bac88a06fd591bf1062_image.png)\\n\\n##### **2.2.5在运行时初始化 Amplify**\\n我们来创建一个后端类对代码进行分组,以便与后端交互。我使用[单例设计模式](https://en.wikipedia.org/wiki/Singleton_pattern),使其通过应用程序轻松可用,并确保 Amplify 库仅初始化一次。\\n类初始化程序负责初始化 Amplify 库。\\n在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Backend.kt,打开它并添加以下代码:\\nJavaScript\\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.content.Context\\nimport android.util.Log\\nimport com.amplifyframework.AmplifyException\\nimport com.amplifyframework.core.Amplify\\n\\nobject Backend {\\n\\n private const val TAG = \\"Backend\\"\\n\\n fun initialize(applicationContext: Context) : Backend {\\n try {\\n Amplify.configure(applicationContext)\\n Log.i(TAG, \\"Initialized Amplify\\")\\n } catch (e: AmplifyException) {\\n Log.e(TAG, \\"Could not initialize Amplify\\", e)\\n }\\n return this\\n }\\n}\\n```\\n应用程序启动时,初始化单例 Backend 对象。\\n在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Application.kt,打开它并添加以下代码:\\nJavaScript\\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.app.Application\\n\\nclass AndroidGettingStartedApplication : Application() {\\n\\n override fun onCreate() {\\n super.onCreate()\\n\\n // initialize Amplify when application is starting\\n Backend.initialize(applicationContext)\\n }\\n}\\n```\\n在“manifests”下,打开 AndroidManifest.xml,并将应用程序类的名称添加到 <application> 元素。\\nJavaScript\\n```\\n<!-- add the android:name attribute to the application node -->\\n <application\\n android:name=\\"AndroidGettingStartedApplication\\"\\n android:allowBackup=\\"true\\"\\n android:icon=\\"@mipmap/ic_launcher\\"\\n android:label=\\"@string/app_name\\"\\n android:roundIcon=\\"@mipmap/ic_launcher_round\\"\\n android:supportsRtl=\\"true\\"\\n android:theme=\\"@style/Theme.GettingStartedAndroid\\">\\n...\\n```\\n打开此文件后,请添加一些在本教程的后续步骤中应用程序需要的权限:\\nXML\\n```\\n<!-- add these nodes between manifest and application -->\\n <uses-permission android:name=\\"android.permission.INTERNET\\"/>\\n <uses-permission android:name=\\"android.permission.ACCESS_NETWORK_STATE\\"/>\\n <uses-permission android:name=\\"android.permission.READ_EXTERNAL_STORAGE\\"/>\\n```\\n##### **2.2.6验证您的设置**\\n要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。\\n要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的 运行 图标 ▶️ 或按 ^ R。\\n\\n![image.png](https://dev-media.amazoncloud.cn/e3a0c6bfc2d74c0791e675b22265fcab_image.png)\\n\\n应该不会出现错误。\\nBUILD SUCCESSFUL in 6s\\n23 actionable tasks: 8 executed, 15 up-to-date\\n\\n### **3.添加身份验证**\\n在此单元中,您将使用 Amplify CLI 和库配置和添加身份验证到您的应用程序中。\\n\\n![image.png](https://dev-media.amazoncloud.cn/c89263a43dc040729742921e6169b259_image.png)\\n\\n#### **3.1简介**\\n您将添加的下一个功能是用户身份验证。在此单元中,您将了解如何使用 Amplify CLI 和库对用户进行身份验证,以利用托管用户身份提供商 [Amazon Cognito](https://aws.amazon.com/cn/cognito/)。\\n您还将了解如何使用 Cognito 托管用户界面展示整个用户身份验证流,从而使用户只需几行代码即可注册、登录和重置密码。\\n使用“托管用户界面”意味着应用程序利用 Cognito 网页登录和注册用户界面流。应用程序的用户将重定向到 Cognito 托管的网页,并在登录后重定向回应用程序。当然,Cognito 和 Amplify 也支持本机 UI。\\n#### **3.2实施**\\n##### **3.2.1创建身份验证服务**\\n要创建身份验证服务,请打开一个终端,然后执行以下命令:\\namplify add auth\\n当您看到此消息时,即表示配置成功(资源的确切名称将有所不同):\\nSuccessfully added resource androidgettingstartedfc5a4717 locally\\n##### **3.2.2部署身份验证服务**\\n现在,已在本地配置身份验证服务,我们可以将它部署到云:在终端中,请在项目目录中执行以下命令:\\namplify push\\n\\n#press Y when asked to continue\\n片刻之后,您应看到以下消息:\\nJavaScript\\n```\\n✔ All resources are updated in the cloud\\n\\nHosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/\\nTest Your Hosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/login?respons\\n```\\n##### **3.2.3向项目添加 Amplify 身份验证库**\\n在转到代码之前,请先返回 Android Studio,将以下依赖项连同您之前添加的其他 \\n```amplifyframework``` 实现一起,添加到您的单元的 \\n```build.gradle```,然后在看到提示时,单击 **Sync Now**:\\nJavaScript\\n```\\ndependencies {\\n ...\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n}\\n```\\n##### **3.2.4在运行时配置Amplify身份验证库**\\n返回 Android Studio,打开 Backend.kt 文件。在后端类中,向我们在上一部分(在 initialize() 方法中)添加的 Amplify 初始化代码**添加一行**。\\n完整代码块应如下所示:\\t\\nJavaScript\\n```\\n// inside Backend class\\nfun initialize(applicationContext: Context) : Backend {\\n try {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, \\"Initialized Amplify\\")\\n } catch (e: AmplifyException) {\\n Log.e(TAG, \\"Could not initialize Amplify\\", e)\\n }\\n return this\\n}\\n```\\n请不要忘记添加导入语句,Android Studio 会自动为您完成此操作(在 Mac 上,在代码编辑器检测到的每个错误上,同时按 Alt 和 Enter 键)。\\n像在上一步骤中那样,为每个缺少的类定义添加所需的导入语句(在红色单词上同时按 Alt 和 Enter 键)。\\n要验证一切是否都按预期运行,请构建项目。单击 **Build** 菜单,并选择 **Make Project**,或者,在 Mac 上按 **⌘F9**。应该不会出现错误。\\n##### **3.2.5在运行时触发身份验证**\\n其余代码更改会跟踪用户的状态(他们是否已登录?)并在用户单击锁定图标时触发“SignIn/SignUp”用户界面。\\n###### **a.添加 signIn 和 signOut 方法**\\n在后端类中的任意位置,添加以下四种方法:\\nJava\\n```\\nprivate fun updateUserData(withSignedInStatus : Boolean) {\\n UserData.setSignedIn(withSignedInStatus)\\n}\\n\\nfun signOut() {\\n Log.i(TAG, \\"Initiate Signout Sequence\\")\\n\\n Amplify.Auth.signOut(\\n { Log.i(TAG, \\"Signed out!\\") },\\n { error -> Log.e(TAG, error.toString()) }\\n )\\n}\\n\\nfun signIn(callingActivity: Activity) {\\n Log.i(TAG, \\"Initiate Signin Sequence\\")\\n\\n Amplify.Auth.signInWithWebUI(\\n callingActivity,\\n { result: AuthSignInResult -> Log.i(TAG, result.toString()) },\\n { error: AuthException -> Log.e(TAG, error.toString()) }\\n )\\n}\\n```\\n然后为每个缺少的类定义添加所需的导入语句(在红色单词上按 Alt 和 Enter 键)。当您可以在多个类之间进行选择时,请务必从 Amplify 包中选择一个,如下面的屏幕截图所示。\\n\\n![image.png](https://dev-media.amazoncloud.cn/7b53ca6149004caea5522c1627342ef7_image.png)\\n\\n请注意,我们没有在这些方法中更新 UserData.isSignedIn 标记,该操作将在下一节中完成。\\n###### **b.添加身份验证中心侦听器**\\n为了跟踪身份验证状态的变化,我们添加了代码以订阅由 Amplify 发送的身份验证事件。我们在 Backend.initialize() 方法中初始化该中心。\\n在收到身份验证事件时,我们将调用 updateUserData() 方法。此方法可使 UserData 对象保持同步。UserData.isSignedIn 属性是 LiveData<Boolean>,这意味着当值更改时,订阅此属性的观察者将收到通知。我们使用此机制来自动刷新用户界面。\\n我们还添加了代码以在应用程序启动时检查以前的身份验证状态。当应用程序启动时,它会检查 Cognito 会话是否已存在,并相应地更新 UserData。\\n在 Backend.initialize() 中,在 try/catch 块之后和 return 语句之前**添加以下代码**。\\nJava\\n```\\n// in Backend.initialize() function, after the try/catch block but before the return statement \\n\\nLog.i(TAG, \\"registering hub event\\")\\n\\n// listen to auth event\\nAmplify.Hub.subscribe(HubChannel.AUTH) { hubEvent: HubEvent<*> ->\\n\\n when (hubEvent.name) {\\n InitializationStatus.SUCCEEDED.toString() -> {\\n Log.i(TAG, \\"Amplify successfully initialized\\")\\n }\\n InitializationStatus.FAILED.toString() -> {\\n Log.i(TAG, \\"Amplify initialization failed\\")\\n }\\n else -> {\\n when (AuthChannelEventName.valueOf(hubEvent.name)) {\\n AuthChannelEventName.SIGNED_IN -> {\\n updateUserData(true)\\n Log.i(TAG, \\"HUB : SIGNED_IN\\")\\n }\\n AuthChannelEventName.SIGNED_OUT -> {\\n updateUserData(false)\\n Log.i(TAG, \\"HUB : SIGNED_OUT\\")\\n }\\n else -> Log.i(TAG, \\"\\"\\"HUB EVENT:\${hubEvent.name}\\"\\"\\")\\n }\\n }\\n }\\n}\\n\\nLog.i(TAG, \\"retrieving session status\\")\\n\\n// is user already authenticated (from a previous execution) ?\\nAmplify.Auth.fetchAuthSession(\\n { result ->\\n Log.i(TAG, result.toString())\\n val cognitoAuthSession = result as AWSCognitoAuthSession\\n // update UI\\n this.updateUserData(cognitoAuthSession.isSignedIn)\\n when (cognitoAuthSession.identityId.type) {\\n AuthSessionResult.Type.SUCCESS -> Log.i(TAG, \\"IdentityId: \\" + cognitoAuthSession.identityId.value)\\n AuthSessionResult.Type.FAILURE -> Log.i(TAG, \\"IdentityId not present because: \\" + cognitoAuthSession.identityId.error.toString())\\n }\\n },\\n { error -> Log.i(TAG, error.toString()) }\\n)\\n```\\n要验证一切是否都按预期运行,请构建项目。单击 **Build**菜单,并选择 **Make Project**,或者,在 Mac 上按 **⌘F9**。应该不会出现错误。\\n###### **c.更新用户界面代码**\\n代码中的最后一个更改与用户界面相关,我们将 [FloatingActionButton](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton) 添加到主要活动中。\\n在“res/layout”下,打开 activity_main.xml,并将现有的 FloatingActionButton **替换**为以下内容:\\nJava\\n```\\n<com.google.android.material.floatingactionbutton.FloatingActionButton\\n android:id=\\"@+id/fabAuth\\"\\n android:layout_width=\\"wrap_content\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_alignParentRight=\\"true\\"\\n android:layout_gravity=\\"bottom|end\\"\\n android:layout_margin=\\"@dimen/fab_margin\\"\\n android:src=\\"@drawable/ic_baseline_lock\\"\\n app:fabCustomSize=\\"60dp\\"\\n app:fabSize=\\"auto\\"\\n />\\n```\\n在“res/drawable”下添加一个锁状图标。右键单击“drawable”,选择 **New**,然后选择 **Vector Asset**。从 Clilp Art 中选择锁状图标,然后输入 **ic_baseline_lock**(不含 _24)作为名称,并从 Clip Art 中选择闭合的锁状图标。单击 **Next**,然后单击 **Finish**。\\n对打开的锁状图标重复相同的操作。\\n\\n![image.png](https://dev-media.amazoncloud.cn/e1b2deee21d64c8f8b4594009c28c550_image.png)\\n\\n执行完以上操作后,您的“drawable”目录下应具有以下文件:\\n\\n![image.png](https://dev-media.amazoncloud.cn/0573d92a6f824f9c934c437f4a3d2284_image.png)\\n\\n现在,在代码中链接新创建的按钮。在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并添加以下代码。\\nJava\\n```\\n// anywhere in the MainActivity class\\nprivate fun setupAuthButton(userData: UserData) {\\n\\n // register a click listener\\n fabAuth.setOnClickListener { view ->\\n\\n val authButton = view as FloatingActionButton\\n\\n if (userData.isSignedIn.value!!) {\\n authButton.setImageResource(R.drawable.ic_baseline_lock_open)\\n Backend.signOut()\\n } else {\\n authButton.setImageResource(R.drawable.ic_baseline_lock_open)\\n Backend.signIn(this)\\n }\\n }\\n}\\n```\\n还是在 MainActivity 中,在 onCreate() 方法的末尾**添加以下代码**:\\nJava\\n```\\nsetupAuthButton(UserData)\\n\\nUserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->\\n // update UI\\n Log.i(TAG, \\"isSignedIn changed : \$isSignedUp\\")\\n\\n if (isSignedUp) {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)\\n } else {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock)\\n }\\n})\\n```\\n以上代码会针对 Userdata.isSignedIn 值注册观察者。当 isSignedIn 值更改时调用闭包。现在,我们只是更改锁状图标:当用户通过身份验证时为打开的锁,当用户没有会话时为闭合的锁。\\n要验证一切是否都按预期运行,请构建项目。单击 **Build** 菜单,并选择 **Make Project**,或者,在 Mac 上按 **⌘F9**。应该不会出现错误。\\n###### **d.更新 AndroidManifest.xml 和 MainActivity**\\n最后,我们必须确保在 Cognito 托管用户界面提供的 Web 身份验证序列结束时启动我们的应用程序。我们在清单文件中添加一个新的活动。当接收到 gettingstarted URI 方案时,将调用该活动。\\n在 Android Studio 中的“manifests”下,打开 AndroidManifest.xml,并在应用程序元素中添加以下活动。\\nJava\\n```\\n <activity\\nandroid:name=\\"com.amazonaws.mobileconnectors.cognitoauth.activities.CustomTabsRedirectActivity\\">\\n <intent-filter>\\n <action android:name=\\"android.intent.action.VIEW\\" />\\n\\n <category android:name=\\"android.intent.category.DEFAULT\\" />\\n <category android:name=\\"android.intent.category.BROWSABLE\\" />\\n\\n <data android:scheme=\\"gettingstarted\\" />\\n </intent-filter>\n </activity>\\n```\\n在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并在类中的任意位置添加以下代码。\\nJava\\n```\\n// MainActivity.kt\\n// receive the web redirect after authentication\\noverride fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\\n super.onActivityResult(requestCode, resultCode, data)\\n Backend.handleWebUISignInResponse(requestCode, resultCode, data)\\n}\\n在 java/com.example.androidgettingstarted/ 下,打开 Backend.kt 并在类中的任意位置添加以下代码。\\n// Backend.kt\\n// pass the data from web redirect to Amplify libs \\nfun handleWebUISignInResponse(requestCode: Int, resultCode: Int, data: Intent?) {\\n Log.d(TAG, \\"received requestCode : \$requestCode and resultCode : \$resultCode\\")\\n if (requestCode == AWSCognitoAuthPlugin.WEB_UI_SIGN_IN_ACTIVITY_CODE) {\\n Amplify.Auth.handleWebUISignInResponse(data)\\n }\\n}\\n```\\n###### **e.构建和测试**\\n要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。应用程序将启动,且屏幕右下角会显示一个闭合的锁状浮动按钮。\\n以下是完整的注册流程。\\n\\n![image.png](https://dev-media.amazoncloud.cn/fbce7916dc2f489893479b94cb8994a5_image.png)\\n![image.png](https://dev-media.amazoncloud.cn/8ff2e17421eb4e9b8c072b04455dc579_image.png)\\n![image.png](https://dev-media.amazoncloud.cn/d51b7e3254954a2ab6ae88a4000dc5b5_image.png)\\n![image.png](https://dev-media.amazoncloud.cn/a96f27872fab47f7b4b20a98ae638b7c_image.png)\\n![image.png](https://dev-media.amazoncloud.cn/dd8be465bd5d4385b2289426d23a550d_image.png)\\n\\n### **4.添加 GraphQL API 和数据库**\\n在此单元中,您将使用 Amplify CLI 和库配置和添加 GraphQL API 到您的应用程序中。\\n\\n![image.png](https://dev-media.amazoncloud.cn/c82a789162a14adc9ec4c608735bf542_image.png)\\n\\n#### **4.1简介**\\n现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作。\\n在此单元中,您将使用 Amplify CLI 和库将 API 添加到您的应用程序中。您将创建的 API 是 [GraphQL](https://graphql.org/) API,它利用 [Amazon DynamoDB](https://aws.amazon.com/cn/dynamodb/)(NoSQL 数据库)支持的 [Amazon AppSync](https://aws.amazon.com/cn/appsync/)(托管 GraphQL 服务)。有关 GraphQL 的介绍,请[访问此页面](https://graphql.org/learn/)。\\n您将构建的应用程序是备注记录应用程序,用户可使用它创建、删除和列出备注。本示例将让您了解如何构建很多常见类型的 CRUD+L(创建、读取、更新、删除和列出)应用程序。\\n#### **4.2实施**\\n##### **4.2.1创建 GraphQL API 服务和数据库**\\n要创建 GraphQL API 及其后备数据库,请打开一个终端,然后从项目目录中**执行此命令**:\\namplify add api\\n初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。\\n**删除**此 schema,并使用我们的应用程序 GraphQL schema **替换**:\\nJava\\n```\\ntype NoteData\\n@model\\n@auth (rules: [ { allow: owner } ]) {\\n id: ID!\\n name: String!\\n description: String\\n image: String\\n}\\n```\\n数据模型由一个类 NoteData 和 4 个属性组成:ID 和名称是必填项,描述和图像是选填字符串。\\n@model 转换器指示我们需要创建数据库来存储数据。\\n@auth 转换器添加了身份验证规则,以允许访问此数据。对于此项目,我们希望仅 NoteData 的拥有者有访问它们的权限。\\n完成后,请不要忘记**保存**,然后返回您的终端以告知 Amplify CLI 您已完成。\\n? Press enter to continue, press enter.\\n几秒钟后,您应该会看到一条成功消息:\\nGraphQL schema compiled successfully.\\n##### **4.2.2生成客户端代码**\\n根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。\\n要生成代码,请在终端**执行以下命令**:\\namplify codegen models\\n这将在 java/com/amplifyframework.datastore.generated.model 目录中创建 Java 文件,如以下情况所示:\\nJava\\n```\\n➜ Android Getting Started git:(master) ✗ ls -al app/src/main/java/com/amplifyframework/datastore/generated/model \\ntotal 24\\ndrwxr-xr-x 4 stormacq admin 128 Oct 7 15:27 .\\ndrwxr-xr-x 3 stormacq admin 96 Oct 7 15:27 ..\\n-rw-r--r-- 1 stormacq admin 1412 Oct 7 15:27 AmplifyModelProvider.java\\n-rw-r--r-- 1 stormacq admin 7153 Oct 7 15:27 NoteData.java\\n```\\n这些文件会自动导入到您的项目中。\\n##### **4.2.2部署 API 服务和数据库**\\n要部署我们刚刚创建的后端 API 和数据库,请转至您的终端,然后**执行命令**:\\nJava\\n```\\namplify push\\n# press Y when asked to continue\\n\\n? Are you sure you want to continue? accept the default Y and press enter\\n? Do you want to generate code for your newly created GraphQL API type N and press enter\\n```\\n几分钟后,您应该会看到一条成功消息:\\n✔ All resources are updated in the cloud\\n\\nGraphQL endpoint: [https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql]( https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql)\\n##### **4.2.4将API客户端库添加到 Android Studio 项目**\\n在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他“amplifyframework”实现一起添加到您的单元的 build.gradle,然后在看到提示时单击 **Sync Now**。\\nJava\\n```\\ndependencies {\\n implementation 'com.amplifyframework:aws-api:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n}\\n```\\n##### **4.2.5在运行时初始化 Amplify 库**\\n打开 Backend.kt,然后在 initialize() 方法的 Amplify 初始化序列中添加一行。完整的尝试/捕获代码块应如下所示:\\nJava\\n```\\ntry {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.addPlugin(AWSApiPlugin())\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, \\"Initialized Amplify\\")\\n} catch (e: AmplifyException) {\\n Log.e(TAG, \\"Could not initialize Amplify\\", e)\\n}\\n```\\n##### **4.2.6在 GraphQL 数据模型和应用程序模型之间添加桥接**\\n我们的项目已经有一个数据模型来表示备注。在此教程中,我们将继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 UserData.kt 并添加两个组件:一个动态属性,从 UserData.Note 返回 NoteData 对象;一个相反静态方法,接收 API NoteData 并返回 Userdata.Note。\\n在数据类 Note 中,添加以下内容:\\nJava\\n```\\n// return an API NoteData from this Note object\\nval data : NoteData\\n get() = NoteData.builder()\\n .name(this.name)\\n .description(this.description)\\n .image(this.imageName)\\n .id(this.id)\\n .build()\\n\\n// static function to create a Note from a NoteData API object\\ncompanion object {\\n fun from(noteData : NoteData) : Note {\\n val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)\\n // some additional code will come here later\\n return result\\n }\\n} \\n```\\n确保通过生成的代码导入 NoteData 类。\\n##### **4.2.7将 API CRUD 方法添加到后端类**\\n我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型 (Note),以便通过用户界面轻松交互。这些方法可以透明地将 Note 转换为 GraphQL 的 NoteData 对象。\\n**打开** Backend.kt 文件,然后在后端类末尾**添加**以下代码段:\\nJava\\n```\\nfun queryNotes() {\\n Log.i(TAG, \\"Querying notes\\")\\n\\n Amplify.API.query(\\n ModelQuery.list(NoteData::class.java),\\n { response ->\\n Log.i(TAG, \\"Queried\\")\\n for (noteData in response.data) {\\n Log.i(TAG, noteData.name)\\n // TODO should add all the notes at once instead of one by one (each add triggers a UI refresh)\\n UserData.addNote(UserData.Note.from(noteData))\\n }\\n },\\n { error -> Log.e(TAG, \\"Query failure\\", error) }\\n )\\n}\\n\\nfun createNote(note : UserData.Note) {\\n Log.i(TAG, \\"Creating notes\\")\\n\\n Amplify.API.mutate(\\n ModelMutation.create(note.data),\\n { response ->\\n Log.i(TAG, \\"Created\\")\\n if (response.hasErrors()) {\\n Log.e(TAG, response.errors.first().message)\\n } else {\\n Log.i(TAG, \\"Created Note with id: \\" + response.data.id)\\n }\\n },\\n { error -> Log.e(TAG, \\"Create failed\\", error) }\\n )\\n}\\n\\nfun deleteNote(note : UserData.Note?) {\\n\\n if (note == null) return\\n\\n Log.i(TAG, \\"Deleting note \$note\\")\\n\\n Amplify.API.mutate(\\n ModelMutation.delete(note.data),\\n { response ->\\n Log.i(TAG, \\"Deleted\\")\\n if (response.hasErrors()) {\\n Log.e(TAG, response.errors.first().message)\\n } else {\\n Log.i(TAG, \\"Deleted Note \$response\\")\\n }\\n },\\n { error -> Log.e(TAG, \\"Delete failed\\", error) }\\n )\\n}\\n确保通过生成的代码导入 ModelQuery、ModelMutation 和 NoteData 类。\\n最后,我们必须在应用程序启动时调用此 API,以查询当前登录用户的 Note 列表。\\n在 Backend.kt 文件中,更新 updateUserData(withSignInStatus: Boolean) 方法,使其看起来类似以下内容:\\n// change our internal state and query list of notes \\nprivate fun updateUserData(withSignedInStatus : Boolean) {\\n UserData.setSignedIn(withSignedInStatus)\\n\\n val notes = UserData.notes().value\\n val isEmpty = notes?.isEmpty() ?: false\\n\\n // query notes when signed in and we do not have Notes yet\\n if (withSignedInStatus && isEmpty ) {\\n this.queryNotes()\\n } else {\\n UserData.resetNotes()\\n }\\n}\\n```\\n现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。\\n##### **4.2.8添加“Edit\\"按钮以添加备注**\\n现在,后端和数据模型已到位,本节的最后一步是让用户创建新的 Note 然后将其删除。\\n###### **a.在 Android Studio 中的 res/layout 下,创建一个新布局:**\\n右键单击 **layout**,选择“New”,然后选择 **Layout Resource File**。将此文件命名为 activity_add_note 并接受所有其他默认值。单击 **OK**。\\n\\n![image.png](https://dev-media.amazoncloud.cn/94ea6d84d209458eb7b08114cdd4dc2c_image.png)\\n\\n打开刚刚创建的文件 activity_add_note,然后通过粘贴以下内容**替换**所生成的代码:\\nJava\\n```\\n<?xml version=\\"1.0\\" encoding=\\"utf-8\\"?>\\n<ScrollView xmlns:android=\\"http://schemas.android.com/apk/res/android\\"\\n xmlns:app=\\"http://schemas.android.com/apk/res-auto\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"match_parent\\"\\n android:fitsSystemWindows=\\"true\\"\\n android:fillViewport=\\"true\\">\\n\\n <LinearLayout\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:orientation=\\"vertical\\"\\n android:padding=\\"8dp\\">\\n\\n <TextView\\n android:id=\\"@+id/title\\"\\n android:layout_width=\\"wrap_content\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_marginTop=\\"8dp\\"\\n android:text=\\"Create a New Note\\"\\n android:textSize=\\"10pt\\" />\\n\\n <EditText\\n android:id=\\"@+id/name\\"\\n android:layout_width=\\"fill_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_marginTop=\\"8dp\\"\\n android:hint=\\"name\\"\\n android:inputType=\\"text\\"\\n android:lines=\\"5\\" />\\n\\n <EditText\\n android:id=\\"@+id/description\\"\\n android:layout_width=\\"fill_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_marginBottom=\\"8dp\\"\\n android:hint=\\"description\\"\\n android:inputType=\\"textMultiLine\\"\\n android:lines=\\"3\\" />\\n\\n <Space\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"0dp\\"\\n android:layout_weight=\\"1\\" />\\n\\n <Button\\n android:id=\\"@+id/addNote\\"\\n style=\\"?android:attr/buttonStyleSmall\\"\\n android:layout_width=\\"fill_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_gravity=\\"center_horizontal\\"\\n android:backgroundTint=\\"#009688\\"\\n android:text=\\"Add Note\\" />\\n\\n <Button\\n android:id=\\"@+id/cancel\\"\\n style=\\"?android:attr/buttonStyleSmall\\"\\n android:layout_width=\\"fill_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_gravity=\\"center_horizontal\\"\\n android:backgroundTint=\\"#FFC107\\"\\n android:text=\\"Cancel\\" />\\n\\n </LinearLayout>\n</ScrollView>\\n```\\n这是一个非常简单的布局,可以只输入 Note 标题和描述。\\n###### **b.添加 AddNoteActivity 类。**\\n在 java/com.example.androidgettingstarted 下,创建一个新的 Kotlin 文件 AddActivityNote.kt,然后打开此文件并添加以下代码: \\nJava\\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.os.Bundle\\nimport androidx.appcompat.app.AppCompatActivity\\nimport kotlinx.android.synthetic.main.activity_add_note.*\\nimport java.util.*\\n\\nclass AddNoteActivity : AppCompatActivity() {\\n\\n override fun onCreate(savedInstanceState: Bundle?) {\\n super.onCreate(savedInstanceState)\\n setContentView(R.layout.activity_add_note)\\n\\n cancel.setOnClickListener {\\n this.finish()\\n }\\n\\n addNote.setOnClickListener {\\n\\n // create a note object\\n val note = UserData.Note(\\n UUID.randomUUID().toString(),\\n name?.text.toString(),\\n description?.text.toString()\\n )\\n\\n // store it in the backend\\n Backend.createNote(note)\\n\\n // add it to UserData, this will trigger a UI refresh\\n UserData.addNote(note)\\n\\n // close activity\\n this.finish()\\n }\\n }\\n\\n companion object {\\n private const val TAG = \\"AddNoteActivity\\"\\n }\\n} \\n```\\n最后,在清单下打开 AndroidManifest.xml,并在应用程序节点的任何位置添加以下活动元素。\\nJava\\n```\\n<activity\\n android:name=\\".AddNoteActivity\\"\\n android:label=\\"Add Note\\"\\n android:theme=\\"@style/Theme.GettingStartedAndroid.NoActionBar\\">\\n <meta-data\\n android:name=\\"android.support.PARENT_ACTIVITY\\"\\n android:value=\\"com.example.androidgettingstarted.MainActivity\\" />\\n</activity>\n```\\n###### **c.在“Main Activity”中添加一个“[Add Note”FloatingActionButton](https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton)。** \\n在 res/layout 下,打开 activity_main.xml 并将以下内容添加到现有“Floating Action Button”上方。\\nJava\\n```\n<com.google.android.material.floatingactionbutton.FloatingActionButton\\n android:id=\\"@+id/fabAdd\\"\\n android:layout_width=\\"wrap_content\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_alignParentRight=\\"true\\"\\n android:layout_gravity=\\"bottom|end\\"\\n android:layout_margin=\\"@dimen/fab_margin\\"\\n android:visibility=\\"invisible\\"\\n android:src=\\"@drawable/ic_baseline_post_add\\"\\n app:fabCustomSize=\\"60dp\\"\\n app:fabSize=\\"auto\\"/>\\n```\\n在 res/drawable 中添加一个“Add Note”图标。右键单击“drawable”,选择 **New**,然后选择 **Vector Asset**。输入 **ic_baseline_add** 作为名称,并从“Clip Art”中选择添加图标。单击 **Next**,然后单击 **Finish**。\\n\\n![image.png](https://dev-media.amazoncloud.cn/50c96c12d9c749eaa89a2dedcb2adb36_image.png)\\n\\n###### **d.添加代码以处理“Add Note”按钮。**\\n要拥有功能完全的“Add Button”,还需要完成的最后两项工作是让按钮根据 isSignedIn 值显示或消失,显然,需要添加代码来处理按钮点击操作。\\n打开 mainActivity.kt,并将以下内容添加到 onCreate() 方法的末尾:\\nJava\\n```\\n// register a click listener\\nfabAdd.setOnClickListener {\\n startActivity(Intent(this, AddNoteActivity::class.java))\\n}\\n然后,仍然在 onCreate() 方法中,将 UserData.isSignedIn.observe 替换为以下内容:\\nUserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->\\n // update UI\\n Log.i(TAG, \\"isSignedIn changed : \$isSignedUp\\")\\n\\n //animation inspired by https://www.11zon.com/zon/android/multiple-floating-action-button-android.php\\n if (isSignedUp) {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)\\n Log.d(TAG, \\"Showing fabADD\\")\\n fabAdd.show()\\n fabAdd.animate().translationY(0.0F - 1.1F * fabAuth.customSize)\\n } else {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock)\\n Log.d(TAG, \\"Hiding fabADD\\")\\n fabAdd.hide()\\n fabAdd.animate().translationY(0.0F)\\n }\\n}) \\n```\\n要验证一切是否都按预期运行,请构建项目。单击 **Build** 菜单,并选择 **Make Project**,或者,在 Mac 上按 **⌘F9**。应该不会出现错误。\\n运行应用程序时,您将看到“Add Note”按钮会在用户登录时显示,在用户注销时消失。您现在可以添加备注了。\\n##### **4.2.9添加“滑动删除”行为**\\n可以通过在 Note 列表中添加触摸处理程序来添加“滑动删除”行为。触摸处理程序负责绘制红色背景、删除图标,并在释放触摸时调用 Backend.delete() 方法。\\n###### **a.创建一个新类 SimpleTouchCallback。**\\n在 java/com 下,右键单击 example.androidgettingstarted,依次选择 New、Kotlin File,然后输入 SwipeCallback 作为名称。\\n\\n![image.png](https://dev-media.amazoncloud.cn/5d8a6018e08143b790f21ceb94e3cb8e_image.png)\\n\\n将以下代码粘贴到新文件中:\\nJava\\n```\\npackage com.example.androidgettingstarted\\n\\nimport android.graphics.Canvas\\nimport android.graphics.Color\\nimport android.graphics.drawable.ColorDrawable\\nimport android.graphics.drawable.Drawable\\nimport android.util.Log\\nimport android.widget.Toast\\nimport androidx.appcompat.app.AppCompatActivity\\nimport androidx.core.content.ContextCompat\\nimport androidx.recyclerview.widget.ItemTouchHelper\\nimport androidx.recyclerview.widget.RecyclerView\\n\\n\\n// https://stackoverflow.com/questions/33985719/android-swipe-to-delete-recyclerview\\nclass SwipeCallback(private val activity: AppCompatActivity): ItemTouchHelper.SimpleCallback(\\n 0,\\n ItemTouchHelper.LEFT\\n) {\\n\\n private val TAG: String = \\"SimpleItemTouchCallback\\"\\n private val icon: Drawable? = ContextCompat.getDrawable(\\n activity,\\n R.drawable.ic_baseline_delete_sweep\\n )\\n private val background: ColorDrawable = ColorDrawable(Color.RED)\\n\\n override fun onChildDraw(\\n c: Canvas,\\n recyclerView: RecyclerView,\\n viewHolder: RecyclerView.ViewHolder,\\n dX: Float,\\n dY: Float,\\n actionState: Int,\\n isCurrentlyActive: Boolean\\n ) {\\n super.onChildDraw(\\n c,\\n recyclerView,\\n viewHolder,\\n dX,\\n dY,\\n actionState,\\n isCurrentlyActive\\n )\\n val itemView = viewHolder.itemView\\n val backgroundCornerOffset = 20\\n val iconMargin = (itemView.height - icon!!.intrinsicHeight) / 2\\n val iconTop = itemView.top + (itemView.height - icon.intrinsicHeight) / 2\\n val iconBottom = iconTop + icon.intrinsicHeight\\n val iconRight: Int = itemView.right - iconMargin\\n if (dX < 0) {\\n val iconLeft: Int = itemView.right - iconMargin - icon.intrinsicWidth\\n icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)\\n background.setBounds(\\n itemView.right + dX.toInt() - backgroundCornerOffset,\\n itemView.top, itemView.right, itemView.bottom\\n )\\n background.draw(c)\\n icon.draw(c)\\n } else {\\n background.setBounds(0, 0, 0, 0)\\n background.draw(c)\\n }\\n }\\n\\n override fun onMove(\\n recyclerView: RecyclerView,\\n viewHolder: RecyclerView.ViewHolder,\\n target: RecyclerView.ViewHolder\\n ): Boolean {\\n Toast.makeText(activity, \\"Moved\\", Toast.LENGTH_SHORT).show()\\n return false\\n }\\n\\n override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {\\n\\n Toast.makeText(activity, \\"deleted\\", Toast.LENGTH_SHORT).show()\\n\\n //Remove swiped item from list and notify the RecyclerView\\n Log.d(TAG, \\"Going to remove \${viewHolder.adapterPosition}\\")\\n\\n // get the position of the swiped item in the list\\n val position = viewHolder.adapterPosition\\n\\n // remove to note from the userdata will refresh the UI\\n val note = UserData.deleteNote(position)\\n\\n // async remove from backend\\n Backend.deleteNote(note)\\n }\\n}\\n```\\n重要的代码行位于 onSwiped() 方法中。当滑动手势完成时,将调用此方法。我们将收集列表中滑动项的位置,并会将相应备注从 UserData 结构(这将更新用户界面)和云后端中删除。\\n###### **b.现在,我们已经有了类,让我们在 res/drawable 中添加“删除”图标。**\\n右键单击“drawable”,选择 **New**,然后选择 **Vector Asset**。输入 **ic_baseline_delete_sweep** 作为名称,并从“Clip Art”中选择“删除滑动”图标。单击 **Next**,然后单击 **Finish**。\\n\\n![image.png](https://dev-media.amazoncloud.cn/4faf844970ba4d76ab20066f2972963a_image.png)\\n\\n###### **c.将以下代码粘贴到新文件中:为 RecyclerView 添加“滑动删除”手势处理程序。**\\n在 java/com/example.androidgettingstarted 下,打开 MainActivity.kt,并将下面两行代码添加到 setupRecyclerView 中:\\nJava\\n```\\n// add a touch gesture handler to manager the swipe to delete gesture\\nval itemTouchHelper = ItemTouchHelper(SwipeCallback(this))\\nitemTouchHelper.attachToRecyclerView(recyclerView)\\n```\\n##### **4.2.10构建和测试**\\n要验证一切是否都按预期运行,请构建并**运行**项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。\\n假设您仍处于登录状态,应用程序会在一个空列表上启动。它现在具有一个用于添加 Note 的“Add Note”按钮。**单击“Add Note”符号、输入标题和描述、单击“Add Note”按钮**,随后备注应显示在列表中。\\n您可以通过向左滑动一行来删除 Note。\\n\\n### **5.添加图像存储功能**\\n在本单元中,您将添加存储以及将图像与您的应用程序中的备注关联的功能。\\n\\n![image.png](https://dev-media.amazoncloud.cn/0064920b16b2417b83366906550bc701_image.png)\\n\\n#### **5.1简介**\\n现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本单元中,您将使用 Amplify CLI 和库来创建利用 [Amazon S3](https://aws.amazon.com/cn/s3/) 的存储服务。最后,您将更新 Android 应用程序以启用图像上传、获取和渲染。\\n\\n#### **5.2实施**\\n##### **5.2.1创建存储服务**\\n要添加图像存储功能,我们将使用 Amplify 存储类别:\\namplify add storage\\n一段时间后,您将看到:\\nJava\\n```\\nSuccessfully added resource image locally\\n```\\n##### **5.2.2部署存储服务**\\n要部署我们刚刚创建的存储服务,请转至您的终端,然后执行以下命令:\\namplify push\\n按 **Y** 确认,一段时间后,您将看到:\\nJava\\n```\\n✔ Successfully pulled backend environment amplify from the cloud.\\n```\\n##### **5.2.3向 Android Studio 项目添加 Amplify 存储库**\\n在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他 \\n```amplifyframework``` 实现一起添加到您的单元的 \\n```build.gradle```,然后在看到提示时单击 **Sync Now**:\\nJava\\n```\\ndependencies {\\n ...\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n implementation 'com.amplifyframework:aws-api:1.4.0'\\n implementation 'com.amplifyframework:aws-storage-s3:1.4.0'\\n}\\n```\\n##### **5.2.4在运行时初始化 Amplify 存储插件**\\n返回 Android Studio,在 java/example.androidgettingstarted 下,打开 Backend.kit,并在 initialize() 方法中的 Amplify 初始化序列中添加一行。完整代码块应如下所示:\\nJava\\n```\\ntry {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.addPlugin(AWSApiPlugin())\\n Amplify.addPlugin(AWSS3StoragePlugin())\\n\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, \\"Initialized Amplify\\")\\n} catch (e: AmplifyException) {\\n Log.e(TAG, \\"Could not initialize Amplify\\", e)\\n}\\n```\\n##### **5.2.5将 Image CRUD 方法添加到后端类**\\n依然在 Backend.kt 中。在后端类的任何位置,添加以下三个方法,用于从存储中上传、下载和删除图像:\\nJava\\n```\\nfun storeImage(filePath: String, key: String) {\\n val file = File(filePath)\\n val options = StorageUploadFileOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n Amplify.Storage.uploadFile(\\n key,\\n file,\\n options,\\n { progress -> Log.i(TAG, \\"Fraction completed: \${progress.fractionCompleted}\\") },\\n { result -> Log.i(TAG, \\"Successfully uploaded: \\" + result.key) },\\n { error -> Log.e(TAG, \\"Upload failed\\", error) }\\n )\\n}\\n\\nfun deleteImage(key : String) {\\n\\n val options = StorageRemoveOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n Amplify.Storage.remove(\\n key,\\n options,\\n { result -> Log.i(TAG, \\"Successfully removed: \\" + result.key) },\\n { error -> Log.e(TAG, \\"Remove failure\\", error) }\\n )\\n}\\n\\nfun retrieveImage(key: String, completed : (image: Bitmap) -> Unit) {\\n val options = StorageDownloadFileOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n val file = File.createTempFile(\\"image\\", \\".image\\")\\n\\n Amplify.Storage.downloadFile(\\n key,\\n file,\\n options,\\n { progress -> Log.i(TAG, \\"Fraction completed: \${progress.fractionCompleted}\\") },\\n { result ->\\n Log.i(TAG, \\"Successfully downloaded: \${result.file.name}\\")\\n val imageStream = FileInputStream(file)\\n val image = BitmapFactory.decodeStream(imageStream)\\n completed(image)\\n },\\n { error -> Log.e(TAG, \\"Download Failure\\", error) }\\n )\\n}\\n```\\n这三种方法仅调用其 Amplify 对应项。Amplify 存储有三个文件保护级别:\\n- **公有**:所有用户均可访问\\n- **受保护**:所有用户均可读取,但只有创建用户可写入\\n- **私有**:只有创建用户可读可写\\n对于此应用程序,我们希望仅备注拥有者可使用图像,我们将使用 StorageAccessLevel.PRIVATE 属性。\\n##### **5.2.6添加 UI 代码以捕获图像**\\n下一步是修改 UI,以允许用户在单击 AddNoteACtivity 上的“Add image”按钮时从手机库中选择图像。\\n必须执行两个更改:更改“Add Note”活动布局以添加“Add image”按钮和图像视图,以及在活动类中添加处理程序代码。\\n在 Android Studio 中的“res/layout”下,打开 activity_add_note.xml 文件,然后**将以下 Button 元素添加到** addNote 按钮的正上方:\\nJava\\n```\\n<!-- after the description EditText -->\\n<com.google.android.material.imageview.ShapeableImageView\\n android:id=\\"@+id/image\\"\\n android:layout_width=\\"match_parent\\"\\n android:layout_height=\\"280dp\\"\\n android:layout_margin=\\"16dp\\"\\n android:scaleType=\\"centerCrop\\" />\\n\\n<!-- after the Space -->\\n<Button\\n android:id=\\"@+id/captureImage\\"\\n style=\\"?android:attr/buttonStyleSmall\\"\\n android:layout_width=\\"fill_parent\\"\\n android:layout_height=\\"wrap_content\\"\\n android:layout_gravity=\\"center_horizontal\\"\\n android:backgroundTint=\\"#009688\\"\\n android:text=\\"Add image\\" />\\n```\\n在 Android Studio 中的 java/example.androidgettingstarted 下,打开 AddNoteACtivity.kt 文件,然后在 onCreate() 方法中**添加以下代码**:\\nJava\\n```\\n// inside onCreate() \\n// Set up the listener for add Image button\\ncaptureImage.setOnClickListener {\\n val i = Intent(\\n Intent.ACTION_GET_CONTENT,\\n MediaStore.Images.Media.EXTERNAL_CONTENT_URI\\n )\\n startActivityForResult(i, SELECT_PHOTO)\\n}\\n\\n// create rounded corners for the image\\nimage.shapeAppearanceModel = image.shapeAppearanceModel\\n .toBuilder()\\n .setAllCorners(CornerFamily.ROUNDED, 150.0f)\\n .build()\\n```\\n在 Intent、MediaStore 和 CornerFamily 上添加所需导入。\\n同时在伴生对象中添加以下常量值:\\n// add this to the companion object \\nprivate const val SELECT_PHOTO = 100\\n最后,添加收到的代码并将所选图像存储到临时文件。\\n将以下代码添加到 AddNoteACtivity 类的任何位置:\\nJava\\n```\\n//anywhere in the AddNoteActivity class\\n\\nprivate var noteImagePath : String? = null\\nprivate var noteImage : Bitmap? = null\\n\\noverride fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) {\\n super.onActivityResult(requestCode, resultCode, imageReturnedIntent)\\n Log.d(TAG, \\"Select photo activity result : \$imageReturnedIntent\\")\\n when (requestCode) {\\n SELECT_PHOTO -> if (resultCode == RESULT_OK) {\\n val selectedImageUri : Uri? = imageReturnedIntent!!.data\\n\\n // read the stream to fill in the preview\\n var imageStream: InputStream? = contentResolver.openInputStream(selectedImageUri!!)\\n val selectedImage = BitmapFactory.decodeStream(imageStream)\\n val ivPreview: ImageView = findViewById<View>(R.id.image) as ImageView\\n ivPreview.setImageBitmap(selectedImage)\\n\\n // store the image to not recreate the Bitmap every time\\n this.noteImage = selectedImage\\n\\n // read the stream to store to a file\\n imageStream = contentResolver.openInputStream(selectedImageUri)\\n val tempFile = File.createTempFile(\\"image\\", \\".image\\")\\n copyStreamToFile(imageStream!!, tempFile)\\n\\n // store the path to create a note\\n this.noteImagePath = tempFile.absolutePath\\n\\n Log.d(TAG, \\"Selected image : \${tempFile.absolutePath}\\")\\n }\\n }\\n}\\n\\nprivate fun copyStreamToFile(inputStream: InputStream, outputFile: File) {\\n inputStream.use { input ->\\n val outputStream = FileOutputStream(outputFile)\\n outputStream.use { output ->\\n val buffer = ByteArray(4 * 1024) // buffer size\\n while (true) {\\n val byteCount = input.read(buffer)\\n if (byteCount < 0) break\\n output.write(buffer, 0, byteCount)\\n }\\n output.flush()\\n output.close()\\n }\\n }\\n}\\n```\\n以上代码将所选图像作为 InputStream 使用两次。第一次,InputStream 会创建一个位图图像以显示在用户界面中,第二次,InputStream 会保存一个临时文件以发送到后端。\\n本单元将浏览一个临时文件,因为 Amplify API 使用 Fileobjects。尽管不是最高效的设计,但此代码十分简单。\\n要验证一切是否都按预期运行,请构建项目。单击 **Build **菜单,并选择 **Make Project**,或者,在 Mac 上按 **⌘F9**。应该不会出现错误。\\n###### **5.2.7创建备注时存储图像**\\n创建备注时,我们从后端调用存储方法。打开 AddNoteActivity.kt 并修改 addNote.setOnClickListener() 方法,以便在创建备注对象后添加以下代码。\\nJava\\n```\\n// add this in AddNoteACtivity.kt, inside the addNote.setOnClickListener() method and after the Note() object is created.\\nif (this.noteImagePath != null) {\\n note.imageName = UUID.randomUUID().toString()\\n //note.setImage(this.noteImage)\\n note.image = this.noteImage\\n\\n // asynchronously store the image (and assume it will work)\\n Backend.storeImage(this.noteImagePath!!, note.imageName!!)\\n}\\n```\\n###### **5.2.8加载备注时加载图像**\\n要加载图像,我们需要修改备注数据类上的静态方法。这样,每当 API 返回的 NoteData 对象转换为 Note 对象时,将同时加载图像。加载图像时,我们会通知 LiveData 的 UserData,以便观察者知晓进行的更改。这将触发用户界面刷新。\\n打开 UserData.kt,然后**修改**备注数据类的伴生对象,如下所示:\\nJava\\n```\\n// static function to create a Note from a NoteData API object\\ncompanion object {\\n fun from(noteData : NoteData) : Note {\\n val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)\\n \\n if (noteData.image != null) {\\n Backend.retrieveImage(noteData.image!!) {\\n result.image = it\\n\\n // force a UI update\\n with(UserData) { notifyObserver() }\\n }\\n }\\n return result\\n }\\n}\\n```\\n###### **5.2.9删除备注时删除图像**\\n最后一步是自己清理,即,当用户删除备注时,从云存储中删除图像。如果清理不是为了节省存储空间,可以出于节省 AWS 费用的目的进行清理,因为 Amazon S3 对存储的数据按 Gb/月收费(前 5Gb 免费,运行本教程不收费)。\\n打开 SwipeCallback.kt,然后在 onSwipe() 方法末尾添加以下代码:\\nif (note?.imageName != null) {\\n //asynchronously delete the image (and assume it will work)\\n Backend.deleteImage(note.imageName!!)\\n}\\n###### **5.2.10构建和测试**\\n要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的**运行**图标 ▶️,或按 ^ **R**。应该不会出现错误。\\n假设您仍在登录中,应用程序将从未从上一部分删除的备注列表开始。再次使用“Add Note”按钮创建备注。这一次,添加从本地图像存储中选择的图片。\\n退出应用程序并重新启动,以验证图像是否正确加载。\\n### **总结**\\n到这里,你已经使用 Amazon Amplify 构建了 Android 应用程序! 并且在应用程序中添加了身份验证,使用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,使用户可以创建和删除备注。您还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。\\n\\n![image.png](https://dev-media.amazoncloud.cn/b34291378370439bb9c9ff5a52855d32_image.png)\\n\\n通过本文相信你对从零使用 Amazon Amplify 创建简单的 Android 应用程序有了一个完整的认识,并且完成了入门。\\n### **最后想说的是**\\n最后想要说的是,一门新技术,新框架,新的解决方案来临的时候努力去学习它,并从中发现机会。本次使用该技术也算是一个新的体验,总体来说,优势很明显,开发速度很快,只要你具有一定的Android基础,那么你就可以很快的利用该产品制作属于自己的一个应用程序。总之,不断的把自己的舒适圈扩大,扩大,再扩大,主动学习和挑战新的东西 。\\n最后,很感谢能够阅读到这里的读者。如果看完觉得好的话,还请轻轻点一下赞或者分享给更多的人,你们的鼓励就是作者继续行文的动力。\\n亚马逊云科技专为开发者们打造了多种学习平台:\\n1. 入门资源中心:从0到1 轻松上手云服务,内容涵盖:成本管理,上手训练,开发资源。[https://aws.amazon.com/cn/getting-started/?nc1=h_ls&trk=32540c74-46f0-46dc-940d-621a1efeedd0&sc_channel=el](https://aws.amazon.com/cn/getting-started/?nc1=h_ls&trk=32540c74-46f0-46dc-940d-621a1efeedd0&sc_channel=el)\\n2. 架构中心:亚马逊云科技架构中心提供了云平台参考架构图表、经过审查的架构解决方案、Well-Architected 最佳实践、模式、图标等。[https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&trk=3fa608de-d954-4355-a20a-324daa58bbeb&sc_channel=el](https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&trk=3fa608de-d954-4355-a20a-324daa58bbeb&sc_channel=e)\\n3. 构建者库:了解亚马逊云科技如何构建和运营软件。[https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&cards-body.sort-order=desc&awsf.filter-content-category=*all&awsf.filter-content-type=*all&awsf.filter-content-level=*all&trk=835e6894-d909-4691-aee1-3831428c04bd&sc_channel=el](https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&cards-body.sort-order=desc&awsf.filter-content-category=*all&awsf.filter-content-type=*all&awsf.filter-content-level=*all&trk=835e6894-d909-4691-aee1-3831428c04bd&sc_channel=el)\\n4. 用于在亚马逊云科技平台上开发和管理应用程序的工具包:[https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&trk=972c69e1-55ec-43af-a503-d458708bb645&sc_channel=el](https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&trk=972c69e1-55ec-43af-a503-d458708bb645&sc_channel=el)\\n最后也给大家带来了专属福利\\n### **【专属福利】**\\n 福利一:100余种产品免费套餐。其中,计算资源 Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。\\n[https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el](https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all&trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&sc_channel=el)","render":"<h3><a id=\\"_0\\"></a><strong>背景:</strong></h3>\\n<p>亚马逊云科技提供了100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。<br />\\n<a href=\\"https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el</a><br />\\n大家好,我是坚果,由于最近一直疫情居家,所以想到外边转转,但是确实出不去,那么作为程序员的我们肯定也不能闲着,于是想做一个很简单的旅行应用,具有基础的增删改查的功能。以及身份验证!这个时候考虑到 Amazon 具有高响应,高可用等特点,这个时候你只要 Amazon 结合一定的 Android 基础,就可以很方便快捷的拥有自己的应用程序。而且由于 Amazon 具有全球优势的特点,以及 Amazon Amplify 是一组位于云端的工具和无服务器服务都让他拥有了一定的优势,这样一来技术选型接确定了,那么说了这么多,如何结合 Amazon 很快的创建属于自己的 Android 应用程序呢?只需要简单的五个步骤就可以。接下来开始正文<br />\\n开始之前看一下具体你要具备哪些条件:</p>\n<h3><a id=\\"_5\\"></a><strong>先决条件</strong></h3>\\n<ul>\\n<li><a href=\\"https://developer.android.com/studio\\" target=\\"_blank\\">Android Studio 4.x</a> 或更高版本</li>\\n<li>一个至少具有<a href=\\"https://github.com/sebsto/amplify-ios-getting-started/blob/master/amplify-policy.json\\" target=\\"_blank\\">这些权限</a>的 <a href=\\"https://aws.amazon.com/cn/getting-started/hands-on/build-android-app-amplify/?trk=6c2fcc87-4e1c-4db1-bedd-836152ec5ac8&amp;sc_channel=el\\" target=\\"_blank\\">Amazon</a> 账户</li>\\n<li><a href=\\"https://nodejs.org/en/\\" target=\\"_blank\\">Node.js</a> 10 或更高版本</li>\\n<li>Amazon 命令行界面 <a href=\\"https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html\\" target=\\"_blank\\">Amazon CLI 2.0.x</a> 或更高版本。<br />\\n检查 noidejs 版本,node -v,发现版本是16.13.0,符合条件<br />\\nandroid studio 版本查看方式<br />\\n点击 help-about,如图所示:版本符合要求</li>\n</ul>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/a47f4ae90660444fba425493fd56337b_image.png\\" alt=\\"image.png\\" /></p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/09dbecd7ea1d4e598f17798315f155ee_image.png\\" alt=\\"image.png\\" /></p>\n<p>接下来构建您的首个 Android 应用程序。</p>\n<h3><a id=\\"_19\\"></a><strong>概览</strong></h3>\\n<p>在本文中,您将使用 Amazon Amplify 创建一个简单的 Android 应用程序,Amazon Amplify 是一组位于云端的工具和无服务器服务。在第一个单元中,您将构建一个简单的 Android 应用程序。在其余单元中,您将使用 Amplify 命令行接口 (Amplify CLI) 初始化一个本地应用程序、添加用户身份验证、添加一个 GraphQL API 和一个数据库以存储您的数据,并更新您的应用程序以存储图像。<br />\\n本文将引导您完成创建上面讨论的简单 Android 应用程序。<br />\\n本文分为五个简短单元。您必须按顺序完成每个单元才能进入下一单元。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/ee205bcc78724130a798c70e10f1fee6_image.png\\" alt=\\"image.png\\" /></p>\n<p>使用 Amazon Amplify 创建简单的 Android 应用qq<br />\\n1.构建 Android 应用程序<br />\\n在 Android 模拟器中创建 Android 应用程序并测试<br />\\n2.初始化本地应用程序<br />\\n使用 Amazon Amplify 初始化本地应用程序<br />\\n3.添加身份验证<br />\\n添加身份验证到您的应用程序中<br />\\n4.添加API和数据库<br />\\n创建 GraphQL API<br />\\n5.添加存储<br />\\n添加存储到您的应用程序中</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/2484d943c9c146f89de1a07cc587cb95_image.png\\" alt=\\"image.png\\" /></p>\n<p>您将使用终端和 Google 的 <a href=\\"https://developer.android.com/studio\\" target=\\"_blank\\">Android Studio</a> IDE 来构建此 Android 应用程序。</p>\\n<h3><a id=\\"1_Android__41\\"></a><strong>1.创建和部署 Android 应用程序</strong></h3>\\n<p>在这,您将创建 Android 应用程序并使用 Amazon Amplify 的 Web 托管服务将其部署到云</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/8c442d8140d44705a0ff7f51cb67e8f4_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"11_46\\"></a><strong>1.1简介</strong></h4>\\n<p>Amazon Amplify 提供基于 Git 的工作流,用于创建、管理、集成和部署 Web 和移动应用程序的无服务器后端。Amplify CLI 提供了一个简单的基于文本的用户界面,用于预置和管理后端服务,如用于您的应用程序的用户身份验证或 REST 或 GraphQL API。使用 Amplify 库可以轻松地将这些后端服务与应用程序中的几行代码集成。</p>\n<h4><a id=\\"12_48\\"></a><strong>1.2实施</strong></h4>\\n<h5><a id=\\"121_Android__49\\"></a><strong>1.2.1.创建 Android 项目</strong></h5>\\n<p>启动 Android Studio,然后选择 New Project</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/3092c1b798444ae28d5ac860d2966403_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 <strong>Phone and Tablet</strong> 下,选择 <strong>Basic Activity</strong>,然后单击 <strong>Next</strong>:</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/3794940a560d4ad5a96868f197cbdf6b_image.png\\" alt=\\"image.png\\" /></p>\n<p>为您的项目键入名称,例如 <strong>Android Amazon Started</strong>。确保语言为 <strong>Kotlin</strong>,开发工具包最低为 <strong>API 26: Android 8.0 (oreo)</strong>,然后单击 <strong>Finish</strong>:</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/2bca974217564e55a0d401a7308204de_image.png\\" alt=\\"image.png\\" /></p>\n<p>现在,项目的框架已经存在,我们需要完成 4 个步骤来让我们的基本应用程序运行:</p>\n<ol>\\n<li>删除不需要的类和文件,并添加插件</li>\n<li>创建类以保留数据结构</li>\n<li>创建视图以在列表中保留单个备注</li>\n<li>修改 MainActivity 以显示备注列表</li>\n</ol>\\n<h5><a id=\\"122_68\\"></a><strong>1.2.2.删除不需要的类和文件,并添加插件</strong></h5>\\n<p>在“res/layout”和 java/com.example.androidgettingstarted 下,删除四个<strong>片段</strong>文件(选择这 4 个文件,右键单击,然后从上下文菜单中选择 <strong>Delete</strong> ):</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/e26a3abb225445d3a37394de4ddb0604_image.png\\" alt=\\"image.png\\" /></p>\n<p>在 <strong>Gradle Scripts</strong>下,打开 <strong>build.gradle (Module:app)</strong>,并添加 Kotlin 扩展插件。<br />\\nKotlin</p>\n<pre><code class=\\"lang-\\">plugins {\\n id 'com.android.application'\\n id 'kotlin-android'\\n id 'kotlin-android-extensions' // &lt;== add this line\\n}\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/86c68dd2c20d46ec95ef825b6b45bd4e_image.png\\" alt=\\"image.png\\" /></p>\n<h5><a id=\\"123_84\\"></a><strong>1.2.3.创建类以保留数据结构</strong></h5>\\n<p>UserData 类可保留用户状态:一个 isSignedIn 标记和一个备注值列表。<br />\\n要创建新类,请右键单击 java/com.example/androidgettingstarted,然后选择 <strong>New -&gt; Kotlin file/class</strong>。键入 <strong>UserData</strong> 作为名称。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/5c748cf53d864bfdadb004699c99e3de_image.png\\" alt=\\"image.png\\" /></p>\n<p>将以下代码粘贴到新创建的文件 (UserData.kt) 中<br />\\nKotlin</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.graphics.Bitmap\\nimport android.util.Log\\nimport androidx.lifecycle.LiveData\\nimport androidx.lifecycle.MutableLiveData\\n\\n// a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)\\nobject UserData {\\n\\n private const val TAG = &quot;UserData&quot;\\n\\n //\\n // observable properties\\n //\\n\\n // signed in status\\n private val _isSignedIn = MutableLiveData&lt;Boolean&gt;(false)\\n var isSignedIn: LiveData&lt;Boolean&gt; = _isSignedIn\\n\\n fun setSignedIn(newValue : Boolean) {\\n // use postvalue() to make the assignation on the main (UI) thread\\n _isSignedIn.postValue(newValue)\\n }\\n\\n // the notes\\n private val _notes = MutableLiveData&lt;MutableList&lt;Note&gt;&gt;(mutableListOf())\\n\\n // please check https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedata\\n private fun &lt;T&gt; MutableLiveData&lt;T&gt;.notifyObserver() {\\n this.postValue(this.value)\\n }\\n fun notifyObserver() {\\n this._notes.notifyObserver()\\n }\\n\\n fun notes() : LiveData&lt;MutableList&lt;Note&gt;&gt; = _notes\\n fun addNote(n : Note) {\\n val notes = _notes.value\\n if (notes != null) {\\n notes.add(n)\\n _notes.notifyObserver()\\n } else {\\n Log.e(TAG, &quot;addNote : note collection is null !!&quot;)\\n }\\n }\\n fun deleteNote(at: Int) : Note? {\\n val note = _notes.value?.removeAt(at)\\n _notes.notifyObserver()\\n return note\\n }\\n\\n fun resetNotes() {\\n this._notes.value?.clear() //used when signing out\\n _notes.notifyObserver()\\n }\\n\\n\\n // a note data class\\n data class Note(val id: String, val name: String, val description: String, var imageName: String? = null) {\\n override fun toString(): String = name\\n\\n // bitmap image\\n var image : Bitmap? = null\\n\\n }\\n}\\n</code></pre>\\n<p><strong>我们刚刚添加了哪些内容?</strong></p>\\n<ul>\\n<li>UserData 类负责保留用户数据,即一个 isSignedIn 标记用于跟踪当前的身份验证状态和备注对象列表。</li>\n<li>这两个属性是根据 LiveData 发布/订阅框架来实现的。它允许图形用户界面 (GUI) 订阅更改并做出相应反应。</li>\n<li>我们还添加了一个备注数据类,仅用于保留单个备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。我们将在后续单元中介绍 Image。</li>\n<li>为 UserData 对象实施了单态设计模式,因为此模式允许只使用 UserData 即可从应用程序的任何位置引用此对象。</li>\n</ul>\\n<h5><a id=\\"124GUI_166\\"></a><strong>1.2.4.为列表中的但各单元格添加GUI</strong></h5>\\n<p>滚动列表中的单个单元格称为 RecyclerView,因为当用户上下滚动屏幕,屏幕上再也看不到视图时,可以回收视图。<br />\\n与常规视图一样,我们也创建了布局 XML 文件和 Kotlin 类。单个单元格类似以下内容:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/4e951e9df2354c448190f35b1c682eb4_image.png\\" alt=\\"image.png\\" /></p>\n<p>要创建布局文件,请右键单击“res/layout”,然后选择 <strong>New -&gt; Layout Resource File</strong>。键入 content_note 作为名称,并保留所有其他值,因为我们将直接编辑 XML 文件</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/8a746fdec5bc41328a04d19c26a4d081_image.png\\" alt=\\"image.png\\" /></p>\n<p>打开新创建的文件的 <strong>Code</strong> 视图。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/f7f0f58455cb4df2b74832687c562ede_image.png\\" alt=\\"image.png\\" /></p>\n<p>在新创建的文件 (content_note.xml) 的“Code”视图中,通过粘贴以下代码来替代所生成的代码:<br />\\nKotlin</p>\n<pre><code class=\\"lang-\\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\\n&lt;LinearLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:orientation=&quot;horizontal&quot;\\n android:paddingVertical=&quot;16dp&quot;&gt;\\n\\n &lt;ImageView\\n android:id=&quot;@+id/image&quot;\\n android:layout_width=&quot;100dp&quot;\\n android:layout_height=&quot;match_parent&quot;\\n android:padding=&quot;8dp&quot;\\n android:scaleType=&quot;centerCrop&quot;\\n android:src=&quot;@drawable/ic_launcher_background&quot; /&gt;\\n\\n &lt;LinearLayout\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_gravity=&quot;center&quot;\\n android:layout_marginLeft=&quot;5dp&quot;\\n android:orientation=&quot;vertical&quot;&gt;\\n\\n &lt;TextView\\n android:id=&quot;@+id/name&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:text=&quot;Title&quot;\\n android:textSize=&quot;20sp&quot;\\n android:textStyle=&quot;bold&quot; /&gt;\\n\\n &lt;TextView\\n android:id=&quot;@+id/description&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:text=&quot;Title&quot;\\n android:textSize=&quot;15sp&quot; /&gt;\\n\\n &lt;/LinearLayout&gt;\\n&lt;/LinearLayout&gt;\\n</code></pre>\\n<p>最后,创建 Kotlin 类:右键单击 java/com.example/androidgettingstarted,然后选择 <strong>New -&gt; Kotlin file/class</strong>。键入 <strong>NoteRecyclerViewAdapter</strong> 作为名称。<br />\\n将以下代码粘贴到新创建的文件 (NoteRecyclerViewAdapter.kt) 中<br />\\nKotlin</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.view.LayoutInflater\\nimport android.view.View\\nimport android.view.ViewGroup\\nimport android.widget.ImageView\\nimport android.widget.TextView\\nimport androidx.recyclerview.widget.RecyclerView\\n\\n// this is a single cell (row) in the list of Notes\\nclass NoteRecyclerViewAdapter(\\n private val values: MutableList&lt;UserData.Note&gt;?) :\\n RecyclerView.Adapter&lt;NoteRecyclerViewAdapter.ViewHolder&gt;() {\\n\\n override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\\n val view = LayoutInflater.from(parent.context)\\n .inflate(R.layout.content_note, parent, false)\\n return ViewHolder(view)\\n }\\n\\n override fun onBindViewHolder(holder: ViewHolder, position: Int) {\\n\\n val item = values?.get(position)\\n holder.nameView.text = item?.name\\n holder.descriptionView.text = item?.description\\n\\n if (item?.image != null) {\\n holder.imageView.setImageBitmap(item.image)\\n }\\n }\\n\\n override fun getItemCount() = values?.size ?: 0\\n\\n inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {\\n val imageView: ImageView = view.findViewById(R.id.image)\\n val nameView: TextView = view.findViewById(R.id.name)\\n val descriptionView: TextView = view.findViewById(R.id.description)\\n }\\n}\\n</code></pre>\\n<p><strong>我们刚刚添加了哪些内容?</strong><br />\\n上述代码包含</p>\n<ul>\\n<li>一个布局 xml 文件,该文件说明表示备注的单元格上的组件布局。它显示图像、备注标题和备注说明。</li>\n<li>一个支持 Kotlin 类。它在创建时接收备注数据对象,并将单个值分配给其相对应的视图(图像、标题和说明)</li>\n</ul>\\n<h5><a id=\\"125_271\\"></a><strong>1.2.5.更新主要活动</strong></h5>\\n<p>现在,我们已经有了数据类(UserData 和备注)和单个备注的视图 (NoteRecyclerViewAdapter),让我们在主要活动上创建备注列表。<br />\\n从 Android Studio 左侧的文件列表,打开 res/layout/content_main.xml,并将代码替换为以下内容:<br />\\nXML</p>\n<pre><code class=\\"lang-\\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\\n&lt;FrameLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;\\n xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;\\n xmlns:tools=&quot;http://schemas.android.com/tools&quot;\\n android:id=&quot;@+id/frameLayout&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;match_parent&quot;\\n &gt;\\n\\n &lt;androidx.recyclerview.widget.RecyclerView\\n android:id=&quot;@+id/item_list&quot;\\n android:name=&quot;com.example.myapplication.ItemListFragment&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;match_parent&quot;\\n android:layout_marginTop=&quot;60dp&quot;\\n\\n android:paddingHorizontal=&quot;8dp&quot;\\n android:paddingVertical=&quot;8dp&quot;\\n app:layoutManager=&quot;LinearLayoutManager&quot;\\n tools:context=&quot;.MainActivity&quot;\\n tools:listitem=&quot;@layout/content_note&quot; /&gt;\\n\\n&lt;/FrameLayout&gt;\\n</code></pre>\\n<p>从 Android Studio 左侧的文件列表,打开 java/com.example/androidgettingstarted/MainActivity.kt,并将代码替换为以下内容:<br />\\nXML</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.os.Bundle\\nimport android.util.Log\\nimport androidx.appcompat.app.AppCompatActivity\\nimport androidx.lifecycle.Observer\\nimport androidx.recyclerview.widget.ItemTouchHelper\\nimport androidx.recyclerview.widget.RecyclerView\\nimport kotlinx.android.synthetic.main.activity_main.*\\nimport kotlinx.android.synthetic.main.content_main.*\\n\\nclass MainActivity : AppCompatActivity() {\\n\\n override fun onCreate(savedInstanceState: Bundle?) {\\n super.onCreate(savedInstanceState)\\n setContentView(R.layout.activity_main)\\n setSupportActionBar(toolbar)\\n\\n // prepare our List view and RecyclerView (cells)\\n setupRecyclerView(item_list)\\n }\\n\\n // recycler view is the list of cells\\n private fun setupRecyclerView(recyclerView: RecyclerView) {\\n\\n // update individual cell when the Note data are modified\\n UserData.notes().observe(this, Observer&lt;MutableList&lt;UserData.Note&gt;&gt; { notes -&gt;\\n Log.d(TAG, &quot;Note observer received \${notes.size} notes&quot;)\\n\\n // let's create a RecyclerViewAdapter that manages the individual cells\\n recyclerView.adapter = NoteRecyclerViewAdapter(notes)\\n })\\n }\\n\\n companion object {\\n private const val TAG = &quot;MainActivity&quot;\\n }\\n}\\n</code></pre>\\n<p><strong>我们刚刚添加了哪些内容?</strong></p>\\n<ul>\\n<li>主要布局是一个 RecyclerView,用于管理我们之前创建的单个单元格列表</li>\n<li>主要活动类可观察备注列表的变化,并创建一个 NoteRecyclerViewAdapter 以创建单个单元格。</li>\n</ul>\\n<h5><a id=\\"126_345\\"></a><strong>1.2.6.验证生成依赖项</strong></h5>\\n<ul>\\n<li>在 Gradle Scripts 下,打开 build.gradle (Module:app),并验证所生成的依赖关系是否正确。需要选中<br />\\nlibraries versions。<br />\\nKotlin</li>\n</ul>\\n<pre><code class=\\"lang-\\">gradle\\ndependencies {\\n implementation &quot;org.jetbrains.kotlin:kotlin-stdlib:\$kotlin_version&quot;\\n implementation 'androidx.core:core-ktx:1.3.2'\\n implementation 'androidx.appcompat:appcompat:1.2.0'\\n implementation 'com.google.android.material:material:1.2.1'\\n implementation 'androidx.constraintlayout:constraintlayout:2.0.2'\\n implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'\\n implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'\\n testImplementation 'junit:junit:4.+'\\n androidTestImplementation 'androidx.test.ext:junit:1.1.2'\\n androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\\n}\\n</code></pre>\\n<h5><a id=\\"127_364\\"></a><strong>1.2.7.构建和测试</strong></h5>\\n<ul>\\n<li>现在,请在模拟器中构建并启动应用程序。单击工具栏中的运行图标 ▶️ 或按 ^ R。</li>\n</ul>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/87a6ad453d4d460aa2c3778bcc5062ea_image.png\\" alt=\\"image.png\\" /></p>\n<p>片刻之后,应用程序会在 Android 模拟器中启动,初始屏幕为空。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/f09db6d5333a424ea387659236ef98f6_image.png\\" alt=\\"image.png\\" /></p>\n<p>在此阶段,没有要在运行时呈现的数据。到此您已成功创建 Android 应用程序。接下来开始使用 Amplify 进行构建!</p>\n<h3><a id=\\"2_Amplify_374\\"></a><strong>2.初始化 Amplify</strong></h3>\\n<p>在此单元中,您将安装并配置 Amplify CLI。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/854fccc737b24fd09e6ba661c03406c3_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"21_379\\"></a><strong>2.1简介</strong></h4>\\n<p>现在我们已创建一个 Android 应用程序,我们想要继续开发并添加新功能。<br />\\n要开始在应用程序中使用 Amazon Amplify,必须安装 Amplify 命令行,初始化 Amplify 项目目录,将项目配置为使用 Amplify 库,并在运行时初始化 Amplify 库。</p>\n<h4><a id=\\"22_382\\"></a><strong>2.2实施</strong></h4>\\n<h5><a id=\\"221_Amplify_CLI_383\\"></a><strong>2.2.1安装 Amplify CLI</strong></h5>\\n<p>Amazon Amplify CLI 取决于 Node.js,没有安装的化,请安装。<br />\\n要安装 Amazon Amplify CLI,请打开一个终端,然后<strong>输入以下命令</strong>:<br />\\nXML</p>\n<pre><code class=\\"lang-\\">## Install Amplify CLI\\nnpm install -g @aws-amplify/cli\\n</code></pre>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/9ff3927ea71f4080805c63e66a2e93f2_image.png\\" alt=\\"image.png\\" /></p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/3fa058c3818e415bb10ff863ac0c1e2e_image.png\\" alt=\\"image.png\\" /><br />\\n查看版本:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/d3e0b4ed2bb246d4ba0f0ff51ace1029_image.png\\" alt=\\"image.png\\" /><br />\\nXML</p>\n<pre><code class=\\"lang-\\">## Verify installation and version\\namplify --version\\n\\n 7.6.26\\n</code></pre>\\n<h5><a id=\\"222_Amplify__404\\"></a><strong>2.2.2初始化 Amplify 后端</strong></h5>\\n<p>要创建后端基本结构,首先需要初始化 Amplify 项目目录并创建云后端。<br />\\n打开项目所在目录,输入cmd,即可打开终端</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/518a4a0c0f5542a3a56ad685079f8650_image.png\\" alt=\\"image.png\\" /></p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/f221c0299308436380a7de2015ee1124_image.png\\" alt=\\"image.png\\" /><br />\\n验证您是否在正确的目录中,它应该如下所示:<br />\\nXML</p>\n<pre><code class=\\"lang-\\">F:\\\\workspace\\\\AndroidASW&gt;tree\\n卷 开发环境 的文件夹 PATH 列表\\n卷序列号为 D4D7-D5B3\\nF:.\\n├─.gradle\\n│ ├─7.0.2\\n│ │ ├─dependencies-accessors\\n│ │ ├─fileChanges\\n│ │ ├─fileHashes\\n│ │ └─vcsMetadata-1\\n│ ├─buildOutputCleanup\\n│ ├─checksums\\n│ └─vcs-1\\n├─.idea\\n│ ├─libraries\\n│ └─modules\\n│ └─app\\n├─app\\n│ ├─libs\\n│ └─src\\n│ ├─androidTest\\n│ │ └─java\\n│ │ └─com\\n│ │ └─example\\n│ │ └─androidasw\\n│ ├─main\\n│ │ ├─java\\n│ │ │ └─com\\n│ │ │ └─example\\n│ │ │ └─androidasw\\n│ │ └─res\\n│ │ ├─drawable\\n│ │ ├─drawable-v24\\n│ │ ├─layout\\n│ │ ├─menu\\n│ │ ├─mipmap-anydpi-v26\\n│ │ ├─mipmap-hdpi\\n│ │ ├─mipmap-mdpi\\n│ │ ├─mipmap-xhdpi\\n│ │ ├─mipmap-xxhdpi\\n│ │ ├─mipmap-xxxhdpi\\n│ │ ├─navigation\\n│ │ ├─values\\n│ │ ├─values-land\\n│ │ ├─values-night\\n│ │ ├─values-w1240dp\\n│ │ └─values-w600dp\\n│ └─test\\n│ └─java\\n│ └─com\\n│ └─example\\n│ └─androidasw\\n└─gradle\\n └─wrapper\\n\\nF:\\\\workspace\\\\AndroidASW&gt;\\n</code></pre>\\n<p>接下来,在亚马逊服务管理后台创建对应的用户,并添加权限类型,具体如下图所示:</p>\n<h5><a id=\\"223_472\\"></a><strong>2.2.3控制台相关操作</strong></h5>\\n<p><strong><a href=\\"https://portal.aws.amazon.com/billing/signup?type=resubscribe#/paymentinformation\\" target=\\"_blank\\">第一步注册</a></strong></p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/be189597410f469dbc440aab8f2310f8_image.png\\" alt=\\"image.png\\" /></p>\n<p><a href=\\"https://aws.amazon.com/cn/free/?trk=95502bdb-28e0-4dc1-895c-2e975a171d36&amp;sc_channel=ba&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=all&amp;awsf.Free%20Tier%20Categories=all\\" target=\\"_blank\\">https://aws.amazon.com/cn/free/?trk=95502bdb-28e0-4dc1-895c-2e975a171d36&amp;sc_channel=ba&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=all&amp;awsf.Free%20Tier%20Categories=all</a><br />\\n<strong>登录控制台</strong></p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/b4664aea104b4304ba4be59142e994ce_image.png\\" alt=\\"image.png\\" /></p>\n<p>添加权限</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/e278f8491a444689b895bf10ee4aa172_image.png\\" alt=\\"image.png\\" /></p>\n<p>点击“下一步”,选择“直接附加现有策略”,一直“下一步&quot;,后会提示创建用户成功。</p>\n<p><strong>添加用户</strong></p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/a81fb1f130e54e32b6f48104a9ceffd7_image.png\\" alt=\\"image.png\\" /></p>\n<p>到此,我们也就创建完成。接下来继续下面的步骤。<br />\\n初始化 Amplify 项目结构和配置文件。<strong>运行以下命令</strong>:<br />\\namplify init<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">\\n? Enter a name for your project (androidgettingstarted): accept the default, press enter\\n? Enter a name for the environment (dev): accept the default, press enter\\n? Choose your default editor: use the arrow key to select your favorite text editor an press enter\\n? Choose the type of app that you're building: android is already selected, press enter\\n? Where is your Res directory: accept the default, press enter\\n? Do you want to use an AWS profile?, Y, press enter\\n? Please choose the profile you want to use: use the arrow keys to select your profile and press enter.\\n</code></pre>\\n<p>如果没有配置文件,可使用 Amazon CLI 键入命令 aws configure --profile &lt;name&gt; 创建一个。<br />\\nAmplify 在云中初始化您的项目,可能需要几分钟。几分钟后,您应该会看到如下消息:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/be6f4d491d5d4ab59aac77454cb772e4_image.png\\" alt=\\"image.png\\" /></p>\n<h5><a id=\\"224_Amplify__511\\"></a><strong>2.2.4将 Amplify 库添加到项目中</strong></h5>\\n<p>Amplify for Android 是作为 Apache Maven 软件包分发的。在本部分中,您会将软件包和其他必需的指令添加到构建配置中。<br />\\n返回 Android Studio,展开 Gradle Scripts 并打开 build.gradle (Project: Android_Getting_Started)。在 buildscript 和 allprojects 数据块的 repositories 数据块内添加行 mavenCentral()。<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">buildscript {\\n ext.kotlin_version = &quot;1.4.10&quot;\\n repositories {\\n google()\\n jcenter()\\n\\n // Add this line into `repositories` in `buildscript`\\n mavenCentral()\\n }\\n dependencies {\\n classpath &quot;com.android.tools.build:gradle:4.0.1&quot;\\n classpath &quot;org.jetbrains.kotlin:kotlin-gradle-plugin:\$kotlin_version&quot;\\n\\n // NOTE: Do not place your application dependencies here; they belong\\n // in the individual module build.gradle files\\n }\\n}\\n\\nallprojects {\\n repositories {\\n google()\\n jcenter()\\n\\n // Add this line into `repositories` in `buildscript`\\n mavenCentral()\\n }\\n}\\n</code></pre>\\n<p>在 <strong>Gradle Scripts</strong> 下,打开 <strong>build.gradle (Module:app)</strong>,并在 implementations 数据块中添加 Amplify 框架核心依赖项。<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">dependencies {\\n implementation fileTree(dir: &quot;libs&quot;, include: [&quot;*.jar&quot;])\\n implementation &quot;org.jetbrains.kotlin:kotlin-stdlib:\$kotlin_version&quot;\\n implementation 'androidx.core:core-ktx:1.3.2'\\n implementation 'androidx.appcompat:appcompat:1.2.0'\\n implementation 'com.google.android.material:material:1.2.1'\\n implementation 'androidx.constraintlayout:constraintlayout:2.0.1'\\n implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'\\n implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'\\n testImplementation 'junit:junit:4.13'\\n androidTestImplementation 'androidx.test.ext:junit:1.1.2'\\n androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'\\n\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n}\\n</code></pre>\\n<p>如果您使用 Java 或目标 Android SDK 21 或更早版本进行开发,请<a href=\\"https://docs.amplify.aws/lib/project-setup/create-application/q/platform/android#n2-install-amplify-libraries\\" target=\\"_blank\\">查看文档</a>了解其他配置更改。<br />\\n现在,运行 <strong>Gradle Sync</strong>。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/99e6a70cb30044e5ba1901d3a1b16e16_image.png\\" alt=\\"image.png\\" /></p>\n<p>过一会儿您将看到<br />\\nBUILD SUCCESSFUL in 1s</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/5e582ca7acd04bac88a06fd591bf1062_image.png\\" alt=\\"image.png\\" /></p>\n<h5><a id=\\"225_Amplify_574\\"></a><strong>2.2.5在运行时初始化 Amplify</strong></h5>\\n<p>我们来创建一个后端类对代码进行分组,以便与后端交互。我使用<a href=\\"https://en.wikipedia.org/wiki/Singleton_pattern\\" target=\\"_blank\\">单例设计模式</a>,使其通过应用程序轻松可用,并确保 Amplify 库仅初始化一次。<br />\\n类初始化程序负责初始化 Amplify 库。<br />\\n在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Backend.kt,打开它并添加以下代码:<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.content.Context\\nimport android.util.Log\\nimport com.amplifyframework.AmplifyException\\nimport com.amplifyframework.core.Amplify\\n\\nobject Backend {\\n\\n private const val TAG = &quot;Backend&quot;\\n\\n fun initialize(applicationContext: Context) : Backend {\\n try {\\n Amplify.configure(applicationContext)\\n Log.i(TAG, &quot;Initialized Amplify&quot;)\\n } catch (e: AmplifyException) {\\n Log.e(TAG, &quot;Could not initialize Amplify&quot;, e)\\n }\\n return this\\n }\\n}\\n</code></pre>\\n<p>应用程序启动时,初始化单例 Backend 对象。<br />\\n在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Application.kt,打开它并添加以下代码:<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.app.Application\\n\\nclass AndroidGettingStartedApplication : Application() {\\n\\n override fun onCreate() {\\n super.onCreate()\\n\\n // initialize Amplify when application is starting\\n Backend.initialize(applicationContext)\\n }\\n}\\n</code></pre>\\n<p>在“manifests”下,打开 AndroidManifest.xml,并将应用程序类的名称添加到 &lt;application&gt; 元素。<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">&lt;!-- add the android:name attribute to the application node --&gt;\\n &lt;application\\n android:name=&quot;AndroidGettingStartedApplication&quot;\\n android:allowBackup=&quot;true&quot;\\n android:icon=&quot;@mipmap/ic_launcher&quot;\\n android:label=&quot;@string/app_name&quot;\\n android:roundIcon=&quot;@mipmap/ic_launcher_round&quot;\\n android:supportsRtl=&quot;true&quot;\\n android:theme=&quot;@style/Theme.GettingStartedAndroid&quot;&gt;\\n...\\n</code></pre>\\n<p>打开此文件后,请添加一些在本教程的后续步骤中应用程序需要的权限:<br />\\nXML</p>\n<pre><code class=\\"lang-\\">&lt;!-- add these nodes between manifest and application --&gt;\\n &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;\\n &lt;uses-permission android:name=&quot;android.permission.ACCESS_NETWORK_STATE&quot;/&gt;\\n &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot;/&gt;\\n</code></pre>\\n<h5><a id=\\"226_642\\"></a><strong>2.2.6验证您的设置</strong></h5>\\n<p>要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。<br />\\n要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的 运行 图标 ▶️ 或按 ^ R。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/e3a0c6bfc2d74c0791e675b22265fcab_image.png\\" alt=\\"image.png\\" /></p>\n<p>应该不会出现错误。<br />\\nBUILD SUCCESSFUL in 6s<br />\\n23 actionable tasks: 8 executed, 15 up-to-date</p>\n<h3><a id=\\"3_652\\"></a><strong>3.添加身份验证</strong></h3>\\n<p>在此单元中,您将使用 Amplify CLI 和库配置和添加身份验证到您的应用程序中。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/c89263a43dc040729742921e6169b259_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"31_657\\"></a><strong>3.1简介</strong></h4>\\n<p>您将添加的下一个功能是用户身份验证。在此单元中,您将了解如何使用 Amplify CLI 和库对用户进行身份验证,以利用托管用户身份提供商 <a href=\\"https://aws.amazon.com/cn/cognito/\\" target=\\"_blank\\">Amazon Cognito</a>。<br />\\n您还将了解如何使用 Cognito 托管用户界面展示整个用户身份验证流,从而使用户只需几行代码即可注册、登录和重置密码。<br />\\n使用“托管用户界面”意味着应用程序利用 Cognito 网页登录和注册用户界面流。应用程序的用户将重定向到 Cognito 托管的网页,并在登录后重定向回应用程序。当然,Cognito 和 Amplify 也支持本机 UI。</p>\n<h4><a id=\\"32_661\\"></a><strong>3.2实施</strong></h4>\\n<h5><a id=\\"321_662\\"></a><strong>3.2.1创建身份验证服务</strong></h5>\\n<p>要创建身份验证服务,请打开一个终端,然后执行以下命令:<br />\\namplify add auth<br />\\n当您看到此消息时,即表示配置成功(资源的确切名称将有所不同):<br />\\nSuccessfully added resource androidgettingstartedfc5a4717 locally</p>\n<h5><a id=\\"322_667\\"></a><strong>3.2.2部署身份验证服务</strong></h5>\\n<p>现在,已在本地配置身份验证服务,我们可以将它部署到云:在终端中,请在项目目录中执行以下命令:<br />\\namplify push</p>\n<p>#press Y when asked to continue<br />\\n片刻之后,您应看到以下消息:<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">✔ All resources are updated in the cloud\\n\\nHosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/\\nTest Your Hosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/login?respons\\n</code></pre>\\n<h5><a id=\\"323_Amplify__680\\"></a><strong>3.2.3向项目添加 Amplify 身份验证库</strong></h5>\\n<p>在转到代码之前,请先返回 Android Studio,将以下依赖项连同您之前添加的其他<br />\\n<code>amplifyframework</code> 实现一起,添加到您的单元的<br />\\n<code>build.gradle</code>,然后在看到提示时,单击 <strong>Sync Now</strong>:<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">dependencies {\\n ...\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n}\\n</code></pre>\\n<h5><a id=\\"324Amplify_693\\"></a><strong>3.2.4在运行时配置Amplify身份验证库</strong></h5>\\n<p>返回 Android Studio,打开 Backend.kt 文件。在后端类中,向我们在上一部分(在 initialize() 方法中)添加的 Amplify 初始化代码<strong>添加一行</strong>。<br />\\n完整代码块应如下所示:\\t<br />\\nJavaScript</p>\n<pre><code class=\\"lang-\\">// inside Backend class\\nfun initialize(applicationContext: Context) : Backend {\\n try {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, &quot;Initialized Amplify&quot;)\\n } catch (e: AmplifyException) {\\n Log.e(TAG, &quot;Could not initialize Amplify&quot;, e)\\n }\\n return this\\n}\\n</code></pre>\\n<p>请不要忘记添加导入语句,Android Studio 会自动为您完成此操作(在 Mac 上,在代码编辑器检测到的每个错误上,同时按 Alt 和 Enter 键)。<br />\\n像在上一步骤中那样,为每个缺少的类定义添加所需的导入语句(在红色单词上同时按 Alt 和 Enter 键)。<br />\\n要验证一切是否都按预期运行,请构建项目。单击 <strong>Build</strong> 菜单,并选择 <strong>Make Project</strong>,或者,在 Mac 上按 <strong>⌘F9</strong>。应该不会出现错误。</p>\\n<h5><a id=\\"325_714\\"></a><strong>3.2.5在运行时触发身份验证</strong></h5>\\n<p>其余代码更改会跟踪用户的状态(他们是否已登录?)并在用户单击锁定图标时触发“SignIn/SignUp”用户界面。</p>\n<h6><a id=\\"a_signIn__signOut__716\\"></a><strong>a.添加 signIn 和 signOut 方法</strong></h6>\\n<p>在后端类中的任意位置,添加以下四种方法:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">private fun updateUserData(withSignedInStatus : Boolean) {\\n UserData.setSignedIn(withSignedInStatus)\\n}\\n\\nfun signOut() {\\n Log.i(TAG, &quot;Initiate Signout Sequence&quot;)\\n\\n Amplify.Auth.signOut(\\n { Log.i(TAG, &quot;Signed out!&quot;) },\\n { error -&gt; Log.e(TAG, error.toString()) }\\n )\\n}\\n\\nfun signIn(callingActivity: Activity) {\\n Log.i(TAG, &quot;Initiate Signin Sequence&quot;)\\n\\n Amplify.Auth.signInWithWebUI(\\n callingActivity,\\n { result: AuthSignInResult -&gt; Log.i(TAG, result.toString()) },\\n { error: AuthException -&gt; Log.e(TAG, error.toString()) }\\n )\\n}\\n</code></pre>\\n<p>然后为每个缺少的类定义添加所需的导入语句(在红色单词上按 Alt 和 Enter 键)。当您可以在多个类之间进行选择时,请务必从 Amplify 包中选择一个,如下面的屏幕截图所示。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/7b53ca6149004caea5522c1627342ef7_image.png\\" alt=\\"image.png\\" /></p>\n<p>请注意,我们没有在这些方法中更新 UserData.isSignedIn 标记,该操作将在下一节中完成。</p>\n<h6><a id=\\"b_748\\"></a><strong>b.添加身份验证中心侦听器</strong></h6>\\n<p>为了跟踪身份验证状态的变化,我们添加了代码以订阅由 Amplify 发送的身份验证事件。我们在 Backend.initialize() 方法中初始化该中心。<br />\\n在收到身份验证事件时,我们将调用 updateUserData() 方法。此方法可使 UserData 对象保持同步。UserData.isSignedIn 属性是 LiveData&lt;Boolean&gt;,这意味着当值更改时,订阅此属性的观察者将收到通知。我们使用此机制来自动刷新用户界面。<br />\\n我们还添加了代码以在应用程序启动时检查以前的身份验证状态。当应用程序启动时,它会检查 Cognito 会话是否已存在,并相应地更新 UserData。<br />\\n在 Backend.initialize() 中,在 try/catch 块之后和 return 语句之前<strong>添加以下代码</strong>。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// in Backend.initialize() function, after the try/catch block but before the return statement \\n\\nLog.i(TAG, &quot;registering hub event&quot;)\\n\\n// listen to auth event\\nAmplify.Hub.subscribe(HubChannel.AUTH) { hubEvent: HubEvent&lt;*&gt; -&gt;\\n\\n when (hubEvent.name) {\\n InitializationStatus.SUCCEEDED.toString() -&gt; {\\n Log.i(TAG, &quot;Amplify successfully initialized&quot;)\\n }\\n InitializationStatus.FAILED.toString() -&gt; {\\n Log.i(TAG, &quot;Amplify initialization failed&quot;)\\n }\\n else -&gt; {\\n when (AuthChannelEventName.valueOf(hubEvent.name)) {\\n AuthChannelEventName.SIGNED_IN -&gt; {\\n updateUserData(true)\\n Log.i(TAG, &quot;HUB : SIGNED_IN&quot;)\\n }\\n AuthChannelEventName.SIGNED_OUT -&gt; {\\n updateUserData(false)\\n Log.i(TAG, &quot;HUB : SIGNED_OUT&quot;)\\n }\\n else -&gt; Log.i(TAG, &quot;&quot;&quot;HUB EVENT:\${hubEvent.name}&quot;&quot;&quot;)\\n }\\n }\\n }\\n}\\n\\nLog.i(TAG, &quot;retrieving session status&quot;)\\n\\n// is user already authenticated (from a previous execution) ?\\nAmplify.Auth.fetchAuthSession(\\n { result -&gt;\\n Log.i(TAG, result.toString())\\n val cognitoAuthSession = result as AWSCognitoAuthSession\\n // update UI\\n this.updateUserData(cognitoAuthSession.isSignedIn)\\n when (cognitoAuthSession.identityId.type) {\\n AuthSessionResult.Type.SUCCESS -&gt; Log.i(TAG, &quot;IdentityId: &quot; + cognitoAuthSession.identityId.value)\\n AuthSessionResult.Type.FAILURE -&gt; Log.i(TAG, &quot;IdentityId not present because: &quot; + cognitoAuthSession.identityId.error.toString())\\n }\\n },\\n { error -&gt; Log.i(TAG, error.toString()) }\\n)\\n</code></pre>\\n<p>要验证一切是否都按预期运行,请构建项目。单击 <strong>Build</strong>菜单,并选择 <strong>Make Project</strong>,或者,在 Mac 上按 <strong>⌘F9</strong>。应该不会出现错误。</p>\\n<h6><a id=\\"c_803\\"></a><strong>c.更新用户界面代码</strong></h6>\\n<p>代码中的最后一个更改与用户界面相关,我们将 <a href=\\"https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton\\" target=\\"_blank\\">FloatingActionButton</a> 添加到主要活动中。<br />\\n在“res/layout”下,打开 activity_main.xml,并将现有的 FloatingActionButton <strong>替换</strong>为以下内容:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">&lt;com.google.android.material.floatingactionbutton.FloatingActionButton\\n android:id=&quot;@+id/fabAuth&quot;\\n android:layout_width=&quot;wrap_content&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_alignParentRight=&quot;true&quot;\\n android:layout_gravity=&quot;bottom|end&quot;\\n android:layout_margin=&quot;@dimen/fab_margin&quot;\\n android:src=&quot;@drawable/ic_baseline_lock&quot;\\n app:fabCustomSize=&quot;60dp&quot;\\n app:fabSize=&quot;auto&quot;\\n /&gt;\\n</code></pre>\\n<p>在“res/drawable”下添加一个锁状图标。右键单击“drawable”,选择 <strong>New</strong>,然后选择 <strong>Vector Asset</strong>。从 Clilp Art 中选择锁状图标,然后输入 <strong>ic_baseline_lock</strong>(不含 _24)作为名称,并从 Clip Art 中选择闭合的锁状图标。单击 <strong>Next</strong>,然后单击 <strong>Finish</strong>。<br />\\n对打开的锁状图标重复相同的操作。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/e1b2deee21d64c8f8b4594009c28c550_image.png\\" alt=\\"image.png\\" /></p>\n<p>执行完以上操作后,您的“drawable”目录下应具有以下文件:</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/0573d92a6f824f9c934c437f4a3d2284_image.png\\" alt=\\"image.png\\" /></p>\n<p>现在,在代码中链接新创建的按钮。在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并添加以下代码。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// anywhere in the MainActivity class\\nprivate fun setupAuthButton(userData: UserData) {\\n\\n // register a click listener\\n fabAuth.setOnClickListener { view -&gt;\\n\\n val authButton = view as FloatingActionButton\\n\\n if (userData.isSignedIn.value!!) {\\n authButton.setImageResource(R.drawable.ic_baseline_lock_open)\\n Backend.signOut()\\n } else {\\n authButton.setImageResource(R.drawable.ic_baseline_lock_open)\\n Backend.signIn(this)\\n }\\n }\\n}\\n</code></pre>\\n<p>还是在 MainActivity 中,在 onCreate() 方法的末尾<strong>添加以下代码</strong>:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">setupAuthButton(UserData)\\n\\nUserData.isSignedIn.observe(this, Observer&lt;Boolean&gt; { isSignedUp -&gt;\\n // update UI\\n Log.i(TAG, &quot;isSignedIn changed : \$isSignedUp&quot;)\\n\\n if (isSignedUp) {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)\\n } else {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock)\\n }\\n})\\n</code></pre>\\n<p>以上代码会针对 Userdata.isSignedIn 值注册观察者。当 isSignedIn 值更改时调用闭包。现在,我们只是更改锁状图标:当用户通过身份验证时为打开的锁,当用户没有会话时为闭合的锁。<br />\\n要验证一切是否都按预期运行,请构建项目。单击 <strong>Build</strong> 菜单,并选择 <strong>Make Project</strong>,或者,在 Mac 上按 <strong>⌘F9</strong>。应该不会出现错误。</p>\\n<h6><a id=\\"d_AndroidManifestxml__MainActivity_868\\"></a><strong>d.更新 AndroidManifest.xml 和 MainActivity</strong></h6>\\n<p>最后,我们必须确保在 Cognito 托管用户界面提供的 Web 身份验证序列结束时启动我们的应用程序。我们在清单文件中添加一个新的活动。当接收到 gettingstarted URI 方案时,将调用该活动。<br />\\n在 Android Studio 中的“manifests”下,打开 AndroidManifest.xml,并在应用程序元素中添加以下活动。<br />\\nJava</p>\n<pre><code class=\\"lang-\\"> &lt;activity\\nandroid:name=&quot;com.amazonaws.mobileconnectors.cognitoauth.activities.CustomTabsRedirectActivity&quot;&gt;\\n &lt;intent-filter&gt;\\n &lt;action android:name=&quot;android.intent.action.VIEW&quot; /&gt;\\n\\n &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;\\n &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; /&gt;\\n\\n &lt;data android:scheme=&quot;gettingstarted&quot; /&gt;\\n &lt;/intent-filter&gt;\\n &lt;/activity&gt;\\n</code></pre>\\n<p>在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并在类中的任意位置添加以下代码。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// MainActivity.kt\\n// receive the web redirect after authentication\\noverride fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {\\n super.onActivityResult(requestCode, resultCode, data)\\n Backend.handleWebUISignInResponse(requestCode, resultCode, data)\\n}\\n在 java/com.example.androidgettingstarted/ 下,打开 Backend.kt 并在类中的任意位置添加以下代码。\\n// Backend.kt\\n// pass the data from web redirect to Amplify libs \\nfun handleWebUISignInResponse(requestCode: Int, resultCode: Int, data: Intent?) {\\n Log.d(TAG, &quot;received requestCode : \$requestCode and resultCode : \$resultCode&quot;)\\n if (requestCode == AWSCognitoAuthPlugin.WEB_UI_SIGN_IN_ACTIVITY_CODE) {\\n Amplify.Auth.handleWebUISignInResponse(data)\\n }\\n}\\n</code></pre>\\n<h6><a id=\\"e_904\\"></a><strong>e.构建和测试</strong></h6>\\n<p>要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。应用程序将启动,且屏幕右下角会显示一个闭合的锁状浮动按钮。<br />\\n以下是完整的注册流程。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/fbce7916dc2f489893479b94cb8994a5_image.png\\" alt=\\"image.png\\" /><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/8ff2e17421eb4e9b8c072b04455dc579_image.png\\" alt=\\"image.png\\" /><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/d51b7e3254954a2ab6ae88a4000dc5b5_image.png\\" alt=\\"image.png\\" /><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/a96f27872fab47f7b4b20a98ae638b7c_image.png\\" alt=\\"image.png\\" /><br />\\n<img src=\\"https://dev-media.amazoncloud.cn/dd8be465bd5d4385b2289426d23a550d_image.png\\" alt=\\"image.png\\" /></p>\n<h3><a id=\\"4_GraphQL_API__914\\"></a><strong>4.添加 GraphQL API 和数据库</strong></h3>\\n<p>在此单元中,您将使用 Amplify CLI 和库配置和添加 GraphQL API 到您的应用程序中。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/c82a789162a14adc9ec4c608735bf542_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"41_919\\"></a><strong>4.1简介</strong></h4>\\n<p>现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作。<br />\\n在此单元中,您将使用 Amplify CLI 和库将 API 添加到您的应用程序中。您将创建的 API 是 <a href=\\"https://graphql.org/\\" target=\\"_blank\\">GraphQL</a> API,它利用 <a href=\\"https://aws.amazon.com/cn/dynamodb/\\" target=\\"_blank\\">Amazon DynamoDB</a>(NoSQL 数据库)支持的 <a href=\\"https://aws.amazon.com/cn/appsync/\\" target=\\"_blank\\">Amazon AppSync</a>(托管 GraphQL 服务)。有关 GraphQL 的介绍,请<a href=\\"https://graphql.org/learn/\\" target=\\"_blank\\">访问此页面</a>。<br />\\n您将构建的应用程序是备注记录应用程序,用户可使用它创建、删除和列出备注。本示例将让您了解如何构建很多常见类型的 CRUD+L(创建、读取、更新、删除和列出)应用程序。</p>\n<h4><a id=\\"42_923\\"></a><strong>4.2实施</strong></h4>\\n<h5><a id=\\"421_GraphQL_API__924\\"></a><strong>4.2.1创建 GraphQL API 服务和数据库</strong></h5>\\n<p>要创建 GraphQL API 及其后备数据库,请打开一个终端,然后从项目目录中<strong>执行此命令</strong>:<br />\\namplify add api<br />\\n初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。<br />\\n<strong>删除</strong>此 schema,并使用我们的应用程序 GraphQL schema <strong>替换</strong>:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">type NoteData\\n@model\\n@auth (rules: [ { allow: owner } ]) {\\n id: ID!\\n name: String!\\n description: String\\n image: String\\n}\\n</code></pre>\\n<p>数据模型由一个类 NoteData 和 4 个属性组成:ID 和名称是必填项,描述和图像是选填字符串。<br />\\n@model 转换器指示我们需要创建数据库来存储数据。<br />\\n@auth 转换器添加了身份验证规则,以允许访问此数据。对于此项目,我们希望仅 NoteData 的拥有者有访问它们的权限。<br />\\n完成后,请不要忘记<strong>保存</strong>,然后返回您的终端以告知 Amplify CLI 您已完成。<br />\\n? Press enter to continue, press enter.<br />\\n几秒钟后,您应该会看到一条成功消息:<br />\\nGraphQL schema compiled successfully.</p>\n<h5><a id=\\"422_947\\"></a><strong>4.2.2生成客户端代码</strong></h5>\\n<p>根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。<br />\\n要生成代码,请在终端<strong>执行以下命令</strong>:<br />\\namplify codegen models<br />\\n这将在 java/com/amplifyframework.datastore.generated.model 目录中创建 Java 文件,如以下情况所示:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">➜ Android Getting Started git:(master) ✗ ls -al app/src/main/java/com/amplifyframework/datastore/generated/model \\ntotal 24\\ndrwxr-xr-x 4 stormacq admin 128 Oct 7 15:27 .\\ndrwxr-xr-x 3 stormacq admin 96 Oct 7 15:27 ..\\n-rw-r--r-- 1 stormacq admin 1412 Oct 7 15:27 AmplifyModelProvider.java\\n-rw-r--r-- 1 stormacq admin 7153 Oct 7 15:27 NoteData.java\\n</code></pre>\\n<p>这些文件会自动导入到您的项目中。</p>\n<h5><a id=\\"422_API__962\\"></a><strong>4.2.2部署 API 服务和数据库</strong></h5>\\n<p>要部署我们刚刚创建的后端 API 和数据库,请转至您的终端,然后<strong>执行命令</strong>:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">amplify push\\n# press Y when asked to continue\\n\\n? Are you sure you want to continue? accept the default Y and press enter\\n? Do you want to generate code for your newly created GraphQL API type N and press enter\\n</code></pre>\\n<p>几分钟后,您应该会看到一条成功消息:<br />\\n✔ All resources are updated in the cloud</p>\n<p>GraphQL endpoint: <a href=\\"https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql\\" target=\\"_blank\\">https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql</a></p>\\n<h5><a id=\\"424API_Android_Studio__976\\"></a><strong>4.2.4将API客户端库添加到 Android Studio 项目</strong></h5>\\n<p>在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他“amplifyframework”实现一起添加到您的单元的 build.gradle,然后在看到提示时单击 <strong>Sync Now</strong>。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">dependencies {\\n implementation 'com.amplifyframework:aws-api:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n}\\n</code></pre>\\n<h5><a id=\\"425_Amplify__985\\"></a><strong>4.2.5在运行时初始化 Amplify 库</strong></h5>\\n<p>打开 Backend.kt,然后在 initialize() 方法的 Amplify 初始化序列中添加一行。完整的尝试/捕获代码块应如下所示:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">try {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.addPlugin(AWSApiPlugin())\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, &quot;Initialized Amplify&quot;)\\n} catch (e: AmplifyException) {\\n Log.e(TAG, &quot;Could not initialize Amplify&quot;, e)\\n}\\n</code></pre>\\n<h5><a id=\\"426_GraphQL__999\\"></a><strong>4.2.6在 GraphQL 数据模型和应用程序模型之间添加桥接</strong></h5>\\n<p>我们的项目已经有一个数据模型来表示备注。在此教程中,我们将继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 UserData.kt 并添加两个组件:一个动态属性,从 UserData.Note 返回 NoteData 对象;一个相反静态方法,接收 API NoteData 并返回 Userdata.Note。<br />\\n在数据类 Note 中,添加以下内容:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// return an API NoteData from this Note object\\nval data : NoteData\\n get() = NoteData.builder()\\n .name(this.name)\\n .description(this.description)\\n .image(this.imageName)\\n .id(this.id)\\n .build()\\n\\n// static function to create a Note from a NoteData API object\\ncompanion object {\\n fun from(noteData : NoteData) : Note {\\n val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)\\n // some additional code will come here later\\n return result\\n }\\n} \\n</code></pre>\\n<p>确保通过生成的代码导入 NoteData 类。</p>\n<h5><a id=\\"427_API_CRUD__1023\\"></a><strong>4.2.7将 API CRUD 方法添加到后端类</strong></h5>\\n<p>我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型 (Note),以便通过用户界面轻松交互。这些方法可以透明地将 Note 转换为 GraphQL 的 NoteData 对象。<br />\\n<strong>打开</strong> Backend.kt 文件,然后在后端类末尾<strong>添加</strong>以下代码段:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">fun queryNotes() {\\n Log.i(TAG, &quot;Querying notes&quot;)\\n\\n Amplify.API.query(\\n ModelQuery.list(NoteData::class.java),\\n { response -&gt;\\n Log.i(TAG, &quot;Queried&quot;)\\n for (noteData in response.data) {\\n Log.i(TAG, noteData.name)\\n // TODO should add all the notes at once instead of one by one (each add triggers a UI refresh)\\n UserData.addNote(UserData.Note.from(noteData))\\n }\\n },\\n { error -&gt; Log.e(TAG, &quot;Query failure&quot;, error) }\\n )\\n}\\n\\nfun createNote(note : UserData.Note) {\\n Log.i(TAG, &quot;Creating notes&quot;)\\n\\n Amplify.API.mutate(\\n ModelMutation.create(note.data),\\n { response -&gt;\\n Log.i(TAG, &quot;Created&quot;)\\n if (response.hasErrors()) {\\n Log.e(TAG, response.errors.first().message)\\n } else {\\n Log.i(TAG, &quot;Created Note with id: &quot; + response.data.id)\\n }\\n },\\n { error -&gt; Log.e(TAG, &quot;Create failed&quot;, error) }\\n )\\n}\\n\\nfun deleteNote(note : UserData.Note?) {\\n\\n if (note == null) return\\n\\n Log.i(TAG, &quot;Deleting note \$note&quot;)\\n\\n Amplify.API.mutate(\\n ModelMutation.delete(note.data),\\n { response -&gt;\\n Log.i(TAG, &quot;Deleted&quot;)\\n if (response.hasErrors()) {\\n Log.e(TAG, response.errors.first().message)\\n } else {\\n Log.i(TAG, &quot;Deleted Note \$response&quot;)\\n }\\n },\\n { error -&gt; Log.e(TAG, &quot;Delete failed&quot;, error) }\\n )\\n}\\n确保通过生成的代码导入 ModelQuery、ModelMutation 和 NoteData 类。\\n最后,我们必须在应用程序启动时调用此 API,以查询当前登录用户的 Note 列表。\\n在 Backend.kt 文件中,更新 updateUserData(withSignInStatus: Boolean) 方法,使其看起来类似以下内容:\\n// change our internal state and query list of notes \\nprivate fun updateUserData(withSignedInStatus : Boolean) {\\n UserData.setSignedIn(withSignedInStatus)\\n\\n val notes = UserData.notes().value\\n val isEmpty = notes?.isEmpty() ?: false\\n\\n // query notes when signed in and we do not have Notes yet\\n if (withSignedInStatus &amp;&amp; isEmpty ) {\\n this.queryNotes()\\n } else {\\n UserData.resetNotes()\\n }\\n}\\n</code></pre>\\n<p>现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。</p>\n<h5><a id=\\"428Edit_1100\\"></a><strong>4.2.8添加“Edit&quot;按钮以添加备注</strong></h5>\\n<p>现在,后端和数据模型已到位,本节的最后一步是让用户创建新的 Note 然后将其删除。</p>\n<h6><a id=\\"a_Android_Studio__reslayout__1102\\"></a><strong>a.在 Android Studio 中的 res/layout 下,创建一个新布局:</strong></h6>\\n<p>右键单击 <strong>layout</strong>,选择“New”,然后选择 <strong>Layout Resource File</strong>。将此文件命名为 activity_add_note 并接受所有其他默认值。单击 <strong>OK</strong>。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/94ea6d84d209458eb7b08114cdd4dc2c_image.png\\" alt=\\"image.png\\" /></p>\n<p>打开刚刚创建的文件 activity_add_note,然后通过粘贴以下内容<strong>替换</strong>所生成的代码:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\\n&lt;ScrollView xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;\\n xmlns:app=&quot;http://schemas.android.com/apk/res-auto&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;match_parent&quot;\\n android:fitsSystemWindows=&quot;true&quot;\\n android:fillViewport=&quot;true&quot;&gt;\\n\\n &lt;LinearLayout\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:orientation=&quot;vertical&quot;\\n android:padding=&quot;8dp&quot;&gt;\\n\\n &lt;TextView\\n android:id=&quot;@+id/title&quot;\\n android:layout_width=&quot;wrap_content&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_marginTop=&quot;8dp&quot;\\n android:text=&quot;Create a New Note&quot;\\n android:textSize=&quot;10pt&quot; /&gt;\\n\\n &lt;EditText\\n android:id=&quot;@+id/name&quot;\\n android:layout_width=&quot;fill_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_marginTop=&quot;8dp&quot;\\n android:hint=&quot;name&quot;\\n android:inputType=&quot;text&quot;\\n android:lines=&quot;5&quot; /&gt;\\n\\n &lt;EditText\\n android:id=&quot;@+id/description&quot;\\n android:layout_width=&quot;fill_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_marginBottom=&quot;8dp&quot;\\n android:hint=&quot;description&quot;\\n android:inputType=&quot;textMultiLine&quot;\\n android:lines=&quot;3&quot; /&gt;\\n\\n &lt;Space\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;0dp&quot;\\n android:layout_weight=&quot;1&quot; /&gt;\\n\\n &lt;Button\\n android:id=&quot;@+id/addNote&quot;\\n style=&quot;?android:attr/buttonStyleSmall&quot;\\n android:layout_width=&quot;fill_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_gravity=&quot;center_horizontal&quot;\\n android:backgroundTint=&quot;#009688&quot;\\n android:text=&quot;Add Note&quot; /&gt;\\n\\n &lt;Button\\n android:id=&quot;@+id/cancel&quot;\\n style=&quot;?android:attr/buttonStyleSmall&quot;\\n android:layout_width=&quot;fill_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_gravity=&quot;center_horizontal&quot;\\n android:backgroundTint=&quot;#FFC107&quot;\\n android:text=&quot;Cancel&quot; /&gt;\\n\\n &lt;/LinearLayout&gt;\\n&lt;/ScrollView&gt;\\n</code></pre>\\n<p>这是一个非常简单的布局,可以只输入 Note 标题和描述。</p>\n<h6><a id=\\"b_AddNoteActivity__1177\\"></a><strong>b.添加 AddNoteActivity 类。</strong></h6>\\n<p>在 java/com.example.androidgettingstarted 下,创建一个新的 Kotlin 文件 AddActivityNote.kt,然后打开此文件并添加以下代码:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.os.Bundle\\nimport androidx.appcompat.app.AppCompatActivity\\nimport kotlinx.android.synthetic.main.activity_add_note.*\\nimport java.util.*\\n\\nclass AddNoteActivity : AppCompatActivity() {\\n\\n override fun onCreate(savedInstanceState: Bundle?) {\\n super.onCreate(savedInstanceState)\\n setContentView(R.layout.activity_add_note)\\n\\n cancel.setOnClickListener {\\n this.finish()\\n }\\n\\n addNote.setOnClickListener {\\n\\n // create a note object\\n val note = UserData.Note(\\n UUID.randomUUID().toString(),\\n name?.text.toString(),\\n description?.text.toString()\\n )\\n\\n // store it in the backend\\n Backend.createNote(note)\\n\\n // add it to UserData, this will trigger a UI refresh\\n UserData.addNote(note)\\n\\n // close activity\\n this.finish()\\n }\\n }\\n\\n companion object {\\n private const val TAG = &quot;AddNoteActivity&quot;\\n }\\n} \\n</code></pre>\\n<p>最后,在清单下打开 AndroidManifest.xml,并在应用程序节点的任何位置添加以下活动元素。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">&lt;activity\\n android:name=&quot;.AddNoteActivity&quot;\\n android:label=&quot;Add Note&quot;\\n android:theme=&quot;@style/Theme.GettingStartedAndroid.NoActionBar&quot;&gt;\\n &lt;meta-data\\n android:name=&quot;android.support.PARENT_ACTIVITY&quot;\\n android:value=&quot;com.example.androidgettingstarted.MainActivity&quot; /&gt;\\n&lt;/activity&gt;\\n</code></pre>\\n<h6><a id=\\"cMain_ActivityAdd_NoteFloatingActionButtonhttpsdeveloperandroidcomreferencecomgoogleandroidmaterialfloatingactionbuttonFloatingActionButton_1235\\"></a><strong>c.在“Main Activity”中添加一个“<a href=\\"https://developer.android.com/reference/com/google/android/material/floatingactionbutton/FloatingActionButton\\" target=\\"_blank\\">Add Note”FloatingActionButton</a>。</strong></h6>\n<p>在 res/layout 下,打开 activity_main.xml 并将以下内容添加到现有“Floating Action Button”上方。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">&lt;com.google.android.material.floatingactionbutton.FloatingActionButton\\n android:id=&quot;@+id/fabAdd&quot;\\n android:layout_width=&quot;wrap_content&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_alignParentRight=&quot;true&quot;\\n android:layout_gravity=&quot;bottom|end&quot;\\n android:layout_margin=&quot;@dimen/fab_margin&quot;\\n android:visibility=&quot;invisible&quot;\\n android:src=&quot;@drawable/ic_baseline_post_add&quot;\\n app:fabCustomSize=&quot;60dp&quot;\\n app:fabSize=&quot;auto&quot;/&gt;\\n</code></pre>\\n<p>在 res/drawable 中添加一个“Add Note”图标。右键单击“drawable”,选择 <strong>New</strong>,然后选择 <strong>Vector Asset</strong>。输入 <strong>ic_baseline_add</strong> 作为名称,并从“Clip Art”中选择添加图标。单击 <strong>Next</strong>,然后单击 <strong>Finish</strong>。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/50c96c12d9c749eaa89a2dedcb2adb36_image.png\\" alt=\\"image.png\\" /></p>\n<h6><a id=\\"dAdd_Note_1255\\"></a><strong>d.添加代码以处理“Add Note”按钮。</strong></h6>\\n<p>要拥有功能完全的“Add Button”,还需要完成的最后两项工作是让按钮根据 isSignedIn 值显示或消失,显然,需要添加代码来处理按钮点击操作。<br />\\n打开 mainActivity.kt,并将以下内容添加到 onCreate() 方法的末尾:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// register a click listener\\nfabAdd.setOnClickListener {\\n startActivity(Intent(this, AddNoteActivity::class.java))\\n}\\n然后,仍然在 onCreate() 方法中,将 UserData.isSignedIn.observe 替换为以下内容:\\nUserData.isSignedIn.observe(this, Observer&lt;Boolean&gt; { isSignedUp -&gt;\\n // update UI\\n Log.i(TAG, &quot;isSignedIn changed : \$isSignedUp&quot;)\\n\\n //animation inspired by https://www.11zon.com/zon/android/multiple-floating-action-button-android.php\\n if (isSignedUp) {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)\\n Log.d(TAG, &quot;Showing fabADD&quot;)\\n fabAdd.show()\\n fabAdd.animate().translationY(0.0F - 1.1F * fabAuth.customSize)\\n } else {\\n fabAuth.setImageResource(R.drawable.ic_baseline_lock)\\n Log.d(TAG, &quot;Hiding fabADD&quot;)\\n fabAdd.hide()\\n fabAdd.animate().translationY(0.0F)\\n }\\n}) \\n</code></pre>\\n<p>要验证一切是否都按预期运行,请构建项目。单击 <strong>Build</strong> 菜单,并选择 <strong>Make Project</strong>,或者,在 Mac 上按 <strong>⌘F9</strong>。应该不会出现错误。<br />\\n运行应用程序时,您将看到“Add Note”按钮会在用户登录时显示,在用户注销时消失。您现在可以添加备注了。</p>\n<h5><a id=\\"429_1285\\"></a><strong>4.2.9添加“滑动删除”行为</strong></h5>\\n<p>可以通过在 Note 列表中添加触摸处理程序来添加“滑动删除”行为。触摸处理程序负责绘制红色背景、删除图标,并在释放触摸时调用 Backend.delete() 方法。</p>\n<h6><a id=\\"a_SimpleTouchCallback_1287\\"></a><strong>a.创建一个新类 SimpleTouchCallback。</strong></h6>\\n<p>在 java/com 下,右键单击 example.androidgettingstarted,依次选择 New、Kotlin File,然后输入 SwipeCallback 作为名称。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/5d8a6018e08143b790f21ceb94e3cb8e_image.png\\" alt=\\"image.png\\" /></p>\n<p>将以下代码粘贴到新文件中:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">package com.example.androidgettingstarted\\n\\nimport android.graphics.Canvas\\nimport android.graphics.Color\\nimport android.graphics.drawable.ColorDrawable\\nimport android.graphics.drawable.Drawable\\nimport android.util.Log\\nimport android.widget.Toast\\nimport androidx.appcompat.app.AppCompatActivity\\nimport androidx.core.content.ContextCompat\\nimport androidx.recyclerview.widget.ItemTouchHelper\\nimport androidx.recyclerview.widget.RecyclerView\\n\\n\\n// https://stackoverflow.com/questions/33985719/android-swipe-to-delete-recyclerview\\nclass SwipeCallback(private val activity: AppCompatActivity): ItemTouchHelper.SimpleCallback(\\n 0,\\n ItemTouchHelper.LEFT\\n) {\\n\\n private val TAG: String = &quot;SimpleItemTouchCallback&quot;\\n private val icon: Drawable? = ContextCompat.getDrawable(\\n activity,\\n R.drawable.ic_baseline_delete_sweep\\n )\\n private val background: ColorDrawable = ColorDrawable(Color.RED)\\n\\n override fun onChildDraw(\\n c: Canvas,\\n recyclerView: RecyclerView,\\n viewHolder: RecyclerView.ViewHolder,\\n dX: Float,\\n dY: Float,\\n actionState: Int,\\n isCurrentlyActive: Boolean\\n ) {\\n super.onChildDraw(\\n c,\\n recyclerView,\\n viewHolder,\\n dX,\\n dY,\\n actionState,\\n isCurrentlyActive\\n )\\n val itemView = viewHolder.itemView\\n val backgroundCornerOffset = 20\\n val iconMargin = (itemView.height - icon!!.intrinsicHeight) / 2\\n val iconTop = itemView.top + (itemView.height - icon.intrinsicHeight) / 2\\n val iconBottom = iconTop + icon.intrinsicHeight\\n val iconRight: Int = itemView.right - iconMargin\\n if (dX &lt; 0) {\\n val iconLeft: Int = itemView.right - iconMargin - icon.intrinsicWidth\\n icon.setBounds(iconLeft, iconTop, iconRight, iconBottom)\\n background.setBounds(\\n itemView.right + dX.toInt() - backgroundCornerOffset,\\n itemView.top, itemView.right, itemView.bottom\\n )\\n background.draw(c)\\n icon.draw(c)\\n } else {\\n background.setBounds(0, 0, 0, 0)\\n background.draw(c)\\n }\\n }\\n\\n override fun onMove(\\n recyclerView: RecyclerView,\\n viewHolder: RecyclerView.ViewHolder,\\n target: RecyclerView.ViewHolder\\n ): Boolean {\\n Toast.makeText(activity, &quot;Moved&quot;, Toast.LENGTH_SHORT).show()\\n return false\\n }\\n\\n override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {\\n\\n Toast.makeText(activity, &quot;deleted&quot;, Toast.LENGTH_SHORT).show()\\n\\n //Remove swiped item from list and notify the RecyclerView\\n Log.d(TAG, &quot;Going to remove \${viewHolder.adapterPosition}&quot;)\\n\\n // get the position of the swiped item in the list\\n val position = viewHolder.adapterPosition\\n\\n // remove to note from the userdata will refresh the UI\\n val note = UserData.deleteNote(position)\\n\\n // async remove from backend\\n Backend.deleteNote(note)\\n }\\n}\\n</code></pre>\\n<p>重要的代码行位于 onSwiped() 方法中。当滑动手势完成时,将调用此方法。我们将收集列表中滑动项的位置,并会将相应备注从 UserData 结构(这将更新用户界面)和云后端中删除。</p>\n<h6><a id=\\"b_resdrawable__1389\\"></a><strong>b.现在,我们已经有了类,让我们在 res/drawable 中添加“删除”图标。</strong></h6>\\n<p>右键单击“drawable”,选择 <strong>New</strong>,然后选择 <strong>Vector Asset</strong>。输入 <strong>ic_baseline_delete_sweep</strong> 作为名称,并从“Clip Art”中选择“删除滑动”图标。单击 <strong>Next</strong>,然后单击 <strong>Finish</strong>。</p>\\n<p><img src=\\"https://dev-media.amazoncloud.cn/4faf844970ba4d76ab20066f2972963a_image.png\\" alt=\\"image.png\\" /></p>\n<h6><a id=\\"c_RecyclerView__1394\\"></a><strong>c.将以下代码粘贴到新文件中:为 RecyclerView 添加“滑动删除”手势处理程序。</strong></h6>\\n<p>在 java/com/example.androidgettingstarted 下,打开 MainActivity.kt,并将下面两行代码添加到 setupRecyclerView 中:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// add a touch gesture handler to manager the swipe to delete gesture\\nval itemTouchHelper = ItemTouchHelper(SwipeCallback(this))\\nitemTouchHelper.attachToRecyclerView(recyclerView)\\n</code></pre>\\n<h5><a id=\\"4210_1402\\"></a><strong>4.2.10构建和测试</strong></h5>\\n<p>要验证一切是否都按预期运行,请构建并<strong>运行</strong>项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。<br />\\n假设您仍处于登录状态,应用程序会在一个空列表上启动。它现在具有一个用于添加 Note 的“Add Note”按钮。<strong>单击“Add Note”符号、输入标题和描述、单击“Add Note”按钮</strong>,随后备注应显示在列表中。<br />\\n您可以通过向左滑动一行来删除 Note。</p>\n<h3><a id=\\"5_1407\\"></a><strong>5.添加图像存储功能</strong></h3>\\n<p>在本单元中,您将添加存储以及将图像与您的应用程序中的备注关联的功能。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/0064920b16b2417b83366906550bc701_image.png\\" alt=\\"image.png\\" /></p>\n<h4><a id=\\"51_1412\\"></a><strong>5.1简介</strong></h4>\\n<p>现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本单元中,您将使用 Amplify CLI 和库来创建利用 <a href=\\"https://aws.amazon.com/cn/s3/\\" target=\\"_blank\\">Amazon S3</a> 的存储服务。最后,您将更新 Android 应用程序以启用图像上传、获取和渲染。</p>\\n<h4><a id=\\"52_1415\\"></a><strong>5.2实施</strong></h4>\\n<h5><a id=\\"521_1416\\"></a><strong>5.2.1创建存储服务</strong></h5>\\n<p>要添加图像存储功能,我们将使用 Amplify 存储类别:<br />\\namplify add storage<br />\\n一段时间后,您将看到:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">Successfully added resource image locally\\n</code></pre>\\n<h5><a id=\\"522_1424\\"></a><strong>5.2.2部署存储服务</strong></h5>\\n<p>要部署我们刚刚创建的存储服务,请转至您的终端,然后执行以下命令:<br />\\namplify push<br />\\n按 <strong>Y</strong> 确认,一段时间后,您将看到:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">✔ Successfully pulled backend environment amplify from the cloud.\\n</code></pre>\\n<h5><a id=\\"523_Android_Studio__Amplify__1432\\"></a><strong>5.2.3向 Android Studio 项目添加 Amplify 存储库</strong></h5>\\n<p>在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他<br />\\n<code>amplifyframework</code> 实现一起添加到您的单元的<br />\\n<code>build.gradle</code>,然后在看到提示时单击 <strong>Sync Now</strong>:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">dependencies {\\n ...\\n // Amplify core dependency\\n implementation 'com.amplifyframework:core:1.4.0'\\n implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'\\n implementation 'com.amplifyframework:aws-api:1.4.0'\\n implementation 'com.amplifyframework:aws-storage-s3:1.4.0'\\n}\\n</code></pre>\\n<h5><a id=\\"524_Amplify__1447\\"></a><strong>5.2.4在运行时初始化 Amplify 存储插件</strong></h5>\\n<p>返回 Android Studio,在 java/example.androidgettingstarted 下,打开 Backend.kit,并在 initialize() 方法中的 Amplify 初始化序列中添加一行。完整代码块应如下所示:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">try {\\n Amplify.addPlugin(AWSCognitoAuthPlugin())\\n Amplify.addPlugin(AWSApiPlugin())\\n Amplify.addPlugin(AWSS3StoragePlugin())\\n\\n Amplify.configure(applicationContext)\\n\\n Log.i(TAG, &quot;Initialized Amplify&quot;)\\n} catch (e: AmplifyException) {\\n Log.e(TAG, &quot;Could not initialize Amplify&quot;, e)\\n}\\n</code></pre>\\n<h5><a id=\\"525_Image_CRUD__1463\\"></a><strong>5.2.5将 Image CRUD 方法添加到后端类</strong></h5>\\n<p>依然在 Backend.kt 中。在后端类的任何位置,添加以下三个方法,用于从存储中上传、下载和删除图像:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">fun storeImage(filePath: String, key: String) {\\n val file = File(filePath)\\n val options = StorageUploadFileOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n Amplify.Storage.uploadFile(\\n key,\\n file,\\n options,\\n { progress -&gt; Log.i(TAG, &quot;Fraction completed: \${progress.fractionCompleted}&quot;) },\\n { result -&gt; Log.i(TAG, &quot;Successfully uploaded: &quot; + result.key) },\\n { error -&gt; Log.e(TAG, &quot;Upload failed&quot;, error) }\\n )\\n}\\n\\nfun deleteImage(key : String) {\\n\\n val options = StorageRemoveOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n Amplify.Storage.remove(\\n key,\\n options,\\n { result -&gt; Log.i(TAG, &quot;Successfully removed: &quot; + result.key) },\\n { error -&gt; Log.e(TAG, &quot;Remove failure&quot;, error) }\\n )\\n}\\n\\nfun retrieveImage(key: String, completed : (image: Bitmap) -&gt; Unit) {\\n val options = StorageDownloadFileOptions.builder()\\n .accessLevel(StorageAccessLevel.PRIVATE)\\n .build()\\n\\n val file = File.createTempFile(&quot;image&quot;, &quot;.image&quot;)\\n\\n Amplify.Storage.downloadFile(\\n key,\\n file,\\n options,\\n { progress -&gt; Log.i(TAG, &quot;Fraction completed: \${progress.fractionCompleted}&quot;) },\\n { result -&gt;\\n Log.i(TAG, &quot;Successfully downloaded: \${result.file.name}&quot;)\\n val imageStream = FileInputStream(file)\\n val image = BitmapFactory.decodeStream(imageStream)\\n completed(image)\\n },\\n { error -&gt; Log.e(TAG, &quot;Download Failure&quot;, error) }\\n )\\n}\\n</code></pre>\\n<p>这三种方法仅调用其 Amplify 对应项。Amplify 存储有三个文件保护级别:</p>\n<ul>\\n<li><strong>公有</strong>:所有用户均可访问</li>\\n<li><strong>受保护</strong>:所有用户均可读取,但只有创建用户可写入</li>\\n<li><strong>私有</strong>:只有创建用户可读可写<br />\\n对于此应用程序,我们希望仅备注拥有者可使用图像,我们将使用 StorageAccessLevel.PRIVATE 属性。</li>\n</ul>\\n<h5><a id=\\"526_UI__1524\\"></a><strong>5.2.6添加 UI 代码以捕获图像</strong></h5>\\n<p>下一步是修改 UI,以允许用户在单击 AddNoteACtivity 上的“Add image”按钮时从手机库中选择图像。<br />\\n必须执行两个更改:更改“Add Note”活动布局以添加“Add image”按钮和图像视图,以及在活动类中添加处理程序代码。<br />\\n在 Android Studio 中的“res/layout”下,打开 activity_add_note.xml 文件,然后<strong>将以下 Button 元素添加到</strong> addNote 按钮的正上方:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">&lt;!-- after the description EditText --&gt;\\n&lt;com.google.android.material.imageview.ShapeableImageView\\n android:id=&quot;@+id/image&quot;\\n android:layout_width=&quot;match_parent&quot;\\n android:layout_height=&quot;280dp&quot;\\n android:layout_margin=&quot;16dp&quot;\\n android:scaleType=&quot;centerCrop&quot; /&gt;\\n\\n&lt;!-- after the Space --&gt;\\n&lt;Button\\n android:id=&quot;@+id/captureImage&quot;\\n style=&quot;?android:attr/buttonStyleSmall&quot;\\n android:layout_width=&quot;fill_parent&quot;\\n android:layout_height=&quot;wrap_content&quot;\\n android:layout_gravity=&quot;center_horizontal&quot;\\n android:backgroundTint=&quot;#009688&quot;\\n android:text=&quot;Add image&quot; /&gt;\\n</code></pre>\\n<p>在 Android Studio 中的 java/example.androidgettingstarted 下,打开 AddNoteACtivity.kt 文件,然后在 onCreate() 方法中<strong>添加以下代码</strong>:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// inside onCreate() \\n// Set up the listener for add Image button\\ncaptureImage.setOnClickListener {\\n val i = Intent(\\n Intent.ACTION_GET_CONTENT,\\n MediaStore.Images.Media.EXTERNAL_CONTENT_URI\\n )\\n startActivityForResult(i, SELECT_PHOTO)\\n}\\n\\n// create rounded corners for the image\\nimage.shapeAppearanceModel = image.shapeAppearanceModel\\n .toBuilder()\\n .setAllCorners(CornerFamily.ROUNDED, 150.0f)\\n .build()\\n</code></pre>\\n<p>在 Intent、MediaStore 和 CornerFamily 上添加所需导入。<br />\\n同时在伴生对象中添加以下常量值:<br />\\n// add this to the companion object<br />\\nprivate const val SELECT_PHOTO = 100<br />\\n最后,添加收到的代码并将所选图像存储到临时文件。<br />\\n将以下代码添加到 AddNoteACtivity 类的任何位置:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">//anywhere in the AddNoteActivity class\\n\\nprivate var noteImagePath : String? = null\\nprivate var noteImage : Bitmap? = null\\n\\noverride fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) {\\n super.onActivityResult(requestCode, resultCode, imageReturnedIntent)\\n Log.d(TAG, &quot;Select photo activity result : \$imageReturnedIntent&quot;)\\n when (requestCode) {\\n SELECT_PHOTO -&gt; if (resultCode == RESULT_OK) {\\n val selectedImageUri : Uri? = imageReturnedIntent!!.data\\n\\n // read the stream to fill in the preview\\n var imageStream: InputStream? = contentResolver.openInputStream(selectedImageUri!!)\\n val selectedImage = BitmapFactory.decodeStream(imageStream)\\n val ivPreview: ImageView = findViewById&lt;View&gt;(R.id.image) as ImageView\\n ivPreview.setImageBitmap(selectedImage)\\n\\n // store the image to not recreate the Bitmap every time\\n this.noteImage = selectedImage\\n\\n // read the stream to store to a file\\n imageStream = contentResolver.openInputStream(selectedImageUri)\\n val tempFile = File.createTempFile(&quot;image&quot;, &quot;.image&quot;)\\n copyStreamToFile(imageStream!!, tempFile)\\n\\n // store the path to create a note\\n this.noteImagePath = tempFile.absolutePath\\n\\n Log.d(TAG, &quot;Selected image : \${tempFile.absolutePath}&quot;)\\n }\\n }\\n}\\n\\nprivate fun copyStreamToFile(inputStream: InputStream, outputFile: File) {\\n inputStream.use { input -&gt;\\n val outputStream = FileOutputStream(outputFile)\\n outputStream.use { output -&gt;\\n val buffer = ByteArray(4 * 1024) // buffer size\\n while (true) {\\n val byteCount = input.read(buffer)\\n if (byteCount &lt; 0) break\\n output.write(buffer, 0, byteCount)\\n }\\n output.flush()\\n output.close()\\n }\\n }\\n}\\n</code></pre>\\n<p>以上代码将所选图像作为 InputStream 使用两次。第一次,InputStream 会创建一个位图图像以显示在用户界面中,第二次,InputStream 会保存一个临时文件以发送到后端。<br />\\n本单元将浏览一个临时文件,因为 Amplify API 使用 Fileobjects。尽管不是最高效的设计,但此代码十分简单。<br />\\n要验证一切是否都按预期运行,请构建项目。单击 **Build **菜单,并选择 <strong>Make Project</strong>,或者,在 Mac 上按 <strong>⌘F9</strong>。应该不会出现错误。</p>\\n<h6><a id=\\"527_1628\\"></a><strong>5.2.7创建备注时存储图像</strong></h6>\\n<p>创建备注时,我们从后端调用存储方法。打开 AddNoteActivity.kt 并修改 addNote.setOnClickListener() 方法,以便在创建备注对象后添加以下代码。<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// add this in AddNoteACtivity.kt, inside the addNote.setOnClickListener() method and after the Note() object is created.\\nif (this.noteImagePath != null) {\\n note.imageName = UUID.randomUUID().toString()\\n //note.setImage(this.noteImage)\\n note.image = this.noteImage\\n\\n // asynchronously store the image (and assume it will work)\\n Backend.storeImage(this.noteImagePath!!, note.imageName!!)\\n}\\n</code></pre>\\n<h6><a id=\\"528_1642\\"></a><strong>5.2.8加载备注时加载图像</strong></h6>\\n<p>要加载图像,我们需要修改备注数据类上的静态方法。这样,每当 API 返回的 NoteData 对象转换为 Note 对象时,将同时加载图像。加载图像时,我们会通知 LiveData 的 UserData,以便观察者知晓进行的更改。这将触发用户界面刷新。<br />\\n打开 UserData.kt,然后<strong>修改</strong>备注数据类的伴生对象,如下所示:<br />\\nJava</p>\n<pre><code class=\\"lang-\\">// static function to create a Note from a NoteData API object\\ncompanion object {\\n fun from(noteData : NoteData) : Note {\\n val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)\\n \\n if (noteData.image != null) {\\n Backend.retrieveImage(noteData.image!!) {\\n result.image = it\\n\\n // force a UI update\\n with(UserData) { notifyObserver() }\\n }\\n }\\n return result\\n }\\n}\\n</code></pre>\\n<h6><a id=\\"529_1664\\"></a><strong>5.2.9删除备注时删除图像</strong></h6>\\n<p>最后一步是自己清理,即,当用户删除备注时,从云存储中删除图像。如果清理不是为了节省存储空间,可以出于节省 AWS 费用的目的进行清理,因为 Amazon S3 对存储的数据按 Gb/月收费(前 5Gb 免费,运行本教程不收费)。<br />\\n打开 SwipeCallback.kt,然后在 onSwipe() 方法末尾添加以下代码:<br />\\nif (note?.imageName != null) {<br />\\n//asynchronously delete the image (and assume it will work)<br />\\nBackend.deleteImage(note.imageName!!)<br />\\n}</p>\n<h6><a id=\\"5210_1671\\"></a><strong>5.2.10构建和测试</strong></h6>\\n<p>要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的<strong>运行</strong>图标 ▶️,或按 ^ <strong>R</strong>。应该不会出现错误。<br />\\n假设您仍在登录中,应用程序将从未从上一部分删除的备注列表开始。再次使用“Add Note”按钮创建备注。这一次,添加从本地图像存储中选择的图片。<br />\\n退出应用程序并重新启动,以验证图像是否正确加载。</p>\n<h3><a id=\\"_1675\\"></a><strong>总结</strong></h3>\\n<p>到这里,你已经使用 Amazon Amplify 构建了 Android 应用程序! 并且在应用程序中添加了身份验证,使用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,使用户可以创建和删除备注。您还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。</p>\n<p><img src=\\"https://dev-media.amazoncloud.cn/b34291378370439bb9c9ff5a52855d32_image.png\\" alt=\\"image.png\\" /></p>\n<p>通过本文相信你对从零使用 Amazon Amplify 创建简单的 Android 应用程序有了一个完整的认识,并且完成了入门。</p>\n<h3><a id=\\"_1681\\"></a><strong>最后想说的是</strong></h3>\\n<p>最后想要说的是,一门新技术,新框架,新的解决方案来临的时候努力去学习它,并从中发现机会。本次使用该技术也算是一个新的体验,总体来说,优势很明显,开发速度很快,只要你具有一定的Android基础,那么你就可以很快的利用该产品制作属于自己的一个应用程序。总之,不断的把自己的舒适圈扩大,扩大,再扩大,主动学习和挑战新的东西 。<br />\\n最后,很感谢能够阅读到这里的读者。如果看完觉得好的话,还请轻轻点一下赞或者分享给更多的人,你们的鼓励就是作者继续行文的动力。<br />\\n亚马逊云科技专为开发者们打造了多种学习平台:</p>\n<ol>\\n<li>入门资源中心:从0到1 轻松上手云服务,内容涵盖:成本管理,上手训练,开发资源。<a href=\\"https://aws.amazon.com/cn/getting-started/?nc1=h_ls&amp;trk=32540c74-46f0-46dc-940d-621a1efeedd0&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/getting-started/?nc1=h_ls&amp;trk=32540c74-46f0-46dc-940d-621a1efeedd0&amp;sc_channel=el</a></li>\\n<li>架构中心:亚马逊云科技架构中心提供了云平台参考架构图表、经过审查的架构解决方案、Well-Architected 最佳实践、模式、图标等。<a href=\\"https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&amp;trk=3fa608de-d954-4355-a20a-324daa58bbeb&amp;sc_channel=e\\" target=\\"_blank\\">https://aws.amazon.com/cn/architecture/?intClick=dev-center-2021_main&amp;trk=3fa608de-d954-4355-a20a-324daa58bbeb&amp;sc_channel=el</a></li>\\n<li>构建者库:了解亚马逊云科技如何构建和运营软件。<a href=\\"https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&amp;cards-body.sort-order=desc&amp;awsf.filter-content-category=*all&amp;awsf.filter-content-type=*all&amp;awsf.filter-content-level=*all&amp;trk=835e6894-d909-4691-aee1-3831428c04bd&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/builders-library/?cards-body.sort-by=item.additionalFields.sortDate&amp;cards-body.sort-order=desc&amp;awsf.filter-content-category=*all&amp;awsf.filter-content-type=*all&amp;awsf.filter-content-level=*all&amp;trk=835e6894-d909-4691-aee1-3831428c04bd&amp;sc_channel=el</a></li>\\n<li>用于在亚马逊云科技平台上开发和管理应用程序的工具包:<a href=\\"https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&amp;trk=972c69e1-55ec-43af-a503-d458708bb645&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/tools/?intClick=dev-center-2021_main&amp;trk=972c69e1-55ec-43af-a503-d458708bb645&amp;sc_channel=el</a><br />\\n最后也给大家带来了专属福利</li>\n</ol>\\n<h3><a id=\\"_1690\\"></a><strong>【专属福利】</strong></h3>\\n<p>福利一:100余种产品免费套餐。其中,计算资源 Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。<br />\\n<a href=\\"https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el\\" target=\\"_blank\\">https://aws.amazon.com/cn/free/?nc2=h_ql_pr_ft&amp;all-free-tier.sort-by=item.additionalFields.SortRank&amp;all-free-tier.sort-order=asc&amp;awsf.Free%20Tier%20Types=*all&amp;awsf.Free%20Tier%20Categories=*all&amp;trk=e0213267-9c8c-4534-bf9b-ecb1c06e4ac6&amp;sc_channel=el</a></p>\n"}
0
目录
关闭