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

article/2025/8/5 1:52:23

背景:

亚马逊云科技提供了100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。
大家好,我是坚果,由于最近一直疫情居家,所以想到外边转转,但是确实出不去,那么作为程序员的我们肯定也不能闲着,于是想做一个很简单的旅行应用,具有基础的增删改查的功能。以及身份验证!这个时候考虑到Amazon 具有高响应,高可用等特点,这个时候你只要Amazon 结合一定的Android基础,就可以很方便快捷的拥有自己的应用程序。而且由于Amazon 具有全球优势的特点,以及Amazon Amplify 是一组位于云端的工具和无服务器服务都让他拥有了一定的优势,这样一来技术选型接确定了,那么说了这么多,如何结合Amazon 很快的创建属于自己的Android 应用程序呢?只需要简单的五个步骤就可以。接下来开始正文

开始之前看一下具体你要具备哪些条件:

先决条件

  • Android Studio 4.x 或更高版本

  • 一个至少具有这些权限的 Amazon 账户

  • Node.js 10 或更高版本

  • Amazon 命令行界面 Amazon CLI 2.0.x 或更高版本。

检查noidejs版本,node -v,发现版本是16.13.0,符合条件

android studio 版本查看方式

点击help-about,如图所示:版本符合要求

image-20220427121047101

image-20220427121109122

接下来构建您的首个 Android 应用程序。

概览

在本文中,您将使用 Amazon Amplify 创建一个简单的 Android 应用程序,Amazon Amplify 是一组位于云端的工具和无服务器服务。在第一个单元中,您将构建一个简单的 Android 应用程序。在其余单元中,您将使用 Amplify 命令行接口 (Amplify CLI) 初始化一个本地应用程序、添加用户身份验证、添加一个 GraphQL API 和一个数据库以存储您的数据,并更新您的应用程序以存储图像。

本文将引导您完成创建上面讨论的简单 Android 应用程序。

本文分为五个简短单元。您必须按顺序完成每个单元才能进入下一单元。

image-20220427121125518

使用 Amazon Amplify 创建简单的 Android 应用qq

​ 1.构建 Android 应用程序

​ 在 Android 模拟器中创建 Android 应用程序并测试

​ 2.初始化本地应用程序

​ 使用 Amazon Amplify 初始化本地应用程序

​ 3.添加身份验证

​ 添加身份验证到您的应用程序中

​ 4.添加API和数据库

​ 创建 GraphQL API

​ 5.添加存储

​ 添加存储到您的应用程序中

image-20220427121140893

您将使用终端和 Google 的 Android Studio IDE 来构建此 Android 应用程序。

1.创建和部署 Android 应用程序

在这,您将创建 Android 应用程序并使用 Amazon Amplify 的 Web 托管服务将其部署到云中。

image-20220427121202560

1.1简介

Amazon Amplify 提供基于 Git 的工作流,用于创建、管理、集成和部署 Web 和移动应用程序的无服务器后端。Amplify CLI 提供了一个简单的基于文本的用户界面,用于预置和管理后端服务,如用于您的应用程序的用户身份验证或 REST 或 GraphQL API。使用 Amplify 库可以轻松地将这些后端服务与应用程序中的几行代码集成。

1.2实施

1.2.1.创建 Android 项目

启动 Android Studio,然后选择New Project

image-20220427121217482

Phone and Tablet 下,选择 Basic Activity,然后单击 Next

image-20220427121237119

为您的项目键入名称,例如 Android Amazon Started。确保语言为 Kotlin,开发工具包最低为 API 26: Android 8.0 (oreo),然后单击 Finish

image-20220427121300485

现在,项目的框架已经存在,我们需要完成 4 个步骤来让我们的基本应用程序运行:

  1. 删除不需要的类和文件,并添加插件
  2. 创建类以保留数据结构
  3. 创建视图以在列表中保留单个备注
  4. 修改 MainActivity 以显示备注列表

1.2.2.删除不需要的类和文件,并添加插件

在“res/layout”和 java/com.example.androidgettingstarted 下,删除四个片段文件(选择这 4 个文件,右键单击,然后从上下文菜单中选择 Delete ):

image-20220427121323068

Gradle Scripts下,打开 build.gradle (Module:app),并添加 Kotlin 扩展插件。

plugins {id 'com.android.application'id 'kotlin-android'id 'kotlin-android-extensions' //  <== add this line}

image-20220427121337596

1.2.3.创建类以保留数据结构

UserData 类可保留用户状态:一个 isSignedIn 标记和一个备注值列表。

要创建新类,请右键单击 java/com.example/androidgettingstarted,然后选择 New -> Kotlin file/class。键入 UserData 作为名称。

image-20220427121349958

将以下代码粘贴到新创建的文件 (UserData.kt) 中

package com.example.androidgettingstartedimport android.graphics.Bitmapimport android.util.Logimport androidx.lifecycle.LiveDataimport androidx.lifecycle.MutableLiveData// a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)object UserData {private const val TAG = "UserData"//// observable properties//// signed in statusprivate val _isSignedIn = MutableLiveData<Boolean>(false)var isSignedIn: LiveData<Boolean> = _isSignedInfun setSignedIn(newValue : Boolean) {// use postvalue() to make the assignation on the main (UI) thread_isSignedIn.postValue(newValue)}// the notesprivate val _notes = MutableLiveData<MutableList<Note>>(mutableListOf())// please check https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedataprivate fun <T> MutableLiveData<T>.notifyObserver() {this.postValue(this.value)}fun notifyObserver() {this._notes.notifyObserver()}fun notes() : LiveData<MutableList<Note>>  = _notesfun addNote(n : Note) {val notes = _notes.valueif (notes != null) {notes.add(n)_notes.notifyObserver()} else {Log.e(TAG, "addNote : note collection is null !!")}}fun deleteNote(at: Int) : Note?  {val note = _notes.value?.removeAt(at)_notes.notifyObserver()return note}fun resetNotes() {this._notes.value?.clear()  //used when signing out_notes.notifyObserver()}data class Note(val id: String, val name: String, val description: String, var imageName: String? = null) {override fun toString(): String = name// bitmap imagevar image : Bitmap? = null}}

我们刚刚添加了哪些内容?

  • UserData 类负责保留用户数据,即一个 isSignedIn 标记用于跟踪当前的身份验证状态和备注对象列表。

  • 这两个属性是根据 LiveData 发布/订阅框架来实现的。它允许图形用户界面 (GUI) 订阅更改并做出相应反应。

  • 我们还添加了一个备注数据类,仅用于保留单个备注的数据。针对 ImageName 和 Image 使用了两个不同的属性。我们将在后续单元中介绍 Image。

  • 为 UserData 对象实施了单态设计模式,因为此模式允许只使用 UserData 即可从应用程序的任何位置引用此对象。

1.2.4.为列表中的但各单元格添加GUI

滚动列表中的单个单元格称为 RecyclerView,因为当用户上下滚动屏幕,屏幕上再也看不到视图时,可以回收视图。

与常规视图一样,我们也创建了布局 XML 文件和 Kotlin 类。单个单元格类似以下内容:

image-20220427121427177

要创建布局文件,请右键单击“res/layout”,然后选择 New -> Layout Resource File。键入 content_note 作为名称,并保留所有其他值,因为我们将直接编辑 XML 文件。

image-20220427121441620

打开新创建的文件的 Code 视图。

image-20220427121500252

在新创建的文件 (content_note.xml) 的“Code”视图中,通过粘贴以下代码来替代所生成的代码:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingVertical="16dp"><ImageViewandroid:id="@+id/image"android:layout_width="100dp"android:layout_height="match_parent"android:padding="8dp"android:scaleType="centerCrop"android:src="@drawable/ic_launcher_background" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginLeft="5dp"android:orientation="vertical"><TextViewandroid:id="@+id/name"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Title"android:textSize="20sp"android:textStyle="bold" /><TextViewandroid:id="@+id/description"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Title"android:textSize="15sp" /></LinearLayout></LinearLayout>

最后,创建 Kotlin 类:右键单击 java/com.example/androidgettingstarted,然后选择 New -> Kotlin file/class。键入 NoteRecyclerViewAdapter 作为名称。

image-20220427121517171

将以下代码粘贴到新创建的文件 (NoteRecyclerViewAdapter.kt) 中

package com.example.androidgettingstartedimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.ImageViewimport android.widget.TextViewimport androidx.recyclerview.widget.RecyclerView// this is a single cell (row) in the list of Notesclass NoteRecyclerViewAdapter(private val values: MutableList<UserData.Note>?) :RecyclerView.Adapter<NoteRecyclerViewAdapter.ViewHolder>() {override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.content_note, parent, false)return ViewHolder(view)}override fun onBindViewHolder(holder: ViewHolder, position: Int) {val item = values?.get(position)holder.nameView.text = item?.nameholder.descriptionView.text = item?.descriptionif (item?.image != null) {holder.imageView.setImageBitmap(item.image)}}override fun getItemCount() = values?.size ?: 0inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {val imageView: ImageView = view.findViewById(R.id.image)val nameView: TextView = view.findViewById(R.id.name)val descriptionView: TextView = view.findViewById(R.id.description)}}

我们刚刚添加了哪些内容?

上述代码包含

  • 一个布局 xml 文件,该文件说明表示备注的单元格上的组件布局。它显示图像、备注标题和备注说明。

  • 一个支持 Kotlin 类。它在创建时接收备注数据对象,并将单个值分配给其相对应的视图(图像、标题和说明)

1.2.5.更新主要活动

现在,我们已经有了数据类(UserData 和备注)和单个备注的视图 (NoteRecyclerViewAdapter),让我们在主要活动上创建备注列表。

从 Android Studio 左侧的文件列表,打开 res/layout/content_main.xml,并将代码替换为以下内容:

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/frameLayout"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/item_list"android:name="com.example.myapplication.ItemListFragment"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="60dp"android:paddingHorizontal="8dp"android:paddingVertical="8dp"app:layoutManager="LinearLayoutManager"tools:context=".MainActivity"tools:listitem="@layout/content_note" /></FrameLayout>

从 Android Studio 左侧的文件列表,打开 java/com.example/androidgettingstarted/MainActivity.kt,并将代码替换为以下内容:

package com.example.androidgettingstartedimport android.os.Bundleimport android.util.Logimport androidx.appcompat.app.AppCompatActivityimport androidx.lifecycle.Observerimport androidx.recyclerview.widget.ItemTouchHelperimport androidx.recyclerview.widget.RecyclerViewimport kotlinx.android.synthetic.main.activity_main.*import kotlinx.android.synthetic.main.content_main.*class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)setSupportActionBar(toolbar)// prepare our List view and RecyclerView (cells)setupRecyclerView(item_list)}// recycler view is the list of cellsprivate fun setupRecyclerView(recyclerView: RecyclerView) {// update individual cell when the Note data are modifiedUserData.notes().observe(this, Observer<MutableList<UserData.Note>> { notes ->Log.d(TAG, "Note observer received ${notes.size} notes")// let's create a RecyclerViewAdapter that manages the individual cellsrecyclerView.adapter = NoteRecyclerViewAdapter(notes)})}companion object {private const val TAG = "MainActivity"}}

我们刚刚添加了哪些内容?

  • 主要布局是一个 RecyclerView,用于管理我们之前创建的单个单元格列表

  • 主要活动类可观察备注列表的变化,并创建一个 NoteRecyclerViewAdapter 以创建单个单元格。

1.2.6.验证生成依赖项

  • Gradle Scripts下,打开 build.gradle (Module:app),并验证所生成的依赖关系是否正确。需要选中

**libraries versions**

```gradledependencies {implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.core:core-ktx:1.3.2'implementation 'androidx.appcompat:appcompat:1.2.0'implementation 'com.google.android.material:material:1.2.1'implementation 'androidx.constraintlayout:constraintlayout:2.0.2'implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1'implementation 'androidx.navigation:navigation-ui-ktx:2.3.1'testImplementation 'junit:junit:4.+'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'}

1.2.7.构建和测试

  • 现在,请在模拟器中构建并启动应用程序。单击工具栏中的运行图标 ▶️ 或按 ^ R

image-20220427121633484

片刻之后,应用程序会在 Android 模拟器中启动,初始屏幕为空。

image-20220427121648965

在此阶段,没有要在运行时呈现的数据。到此您已成功创建 Android 应用程序。接下来开始使用 Amplify 进行构建!

2.初始化 Amplify

在此单元中,您将安装并配置 Amplify CLI。

image-20220427121703725

2.1简介

现在我们已创建一个 Android 应用程序,我们想要继续开发并添加新功能。

要开始在应用程序中使用 Amazon Amplify,必须安装 Amplify 命令行,初始化 Amplify 项目目录,将项目配置为使用 Amplify 库,并在运行时初始化 Amplify 库。

2.2实施

2.2.1安装Amplify CLI

Amazon Amplify CLI 取决于 Node.js,没有安装的化,请安装。

要安装 Amazon Amplify CLI,请打开一个终端,然后输入以下命令

## Install Amplify CLInpm install -g @aws-amplify/cli

image-20220427121719797

image-20220427121742813

查看版本:

image-20220427121756711

## Verify installation and versionamplify --version7.6.26

2.2.2初始化Amplify后端

要创建后端基本结构,首先需要初始化 Amplify 项目目录并创建云后端。

打开项目所在目录,输入cmd,即可打开终端

image-20220427121812495

image-20220427121832012

验证您是否在正确的目录中,它应该如下所示:

F:\workspace\AndroidASW>tree卷 开发环境 的文件夹 PATH 列表卷序列号为 D4D7-D5B3F:.├─.gradle│  ├─7.0.2│  │  ├─dependencies-accessors│  │  ├─fileChanges│  │  ├─fileHashes│  │  └─vcsMetadata-1│  ├─buildOutputCleanup│  ├─checksums│  └─vcs-1├─.idea│  ├─libraries│  └─modules│      └─app├─app│  ├─libs│  └─src│      ├─androidTest│      │  └─java│      │      └─com│      │          └─example│      │              └─androidasw│      ├─main│      │  ├─java│      │  │  └─com│      │  │      └─example│      │  │          └─androidasw│      │  └─res│      │      ├─drawable│      │      ├─drawable-v24│      │      ├─layout│      │      ├─menu│      │      ├─mipmap-anydpi-v26│      │      ├─mipmap-hdpi│      │      ├─mipmap-mdpi│      │      ├─mipmap-xhdpi│      │      ├─mipmap-xxhdpi│      │      ├─mipmap-xxxhdpi│      │      ├─navigation│      │      ├─values│      │      ├─values-land│      │      ├─values-night│      │      ├─values-w1240dp│      │      └─values-w600dp│      └─test│          └─java│              └─com│                  └─example│                      └─androidasw└─gradle└─wrapperF:\workspace\AndroidASW>

接下来,在亚马逊服务管理后台创建对应的用户,并添加权限类型,具体如下图所示:

2.2.3控制台相关操作

第一步注册

image-20220427121855426

[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=](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 Tier Types=)*all&awsf.Free%20Tier%20Categories=*all

登录控制台

image-20220427121912813

添加权限

image-20220427121929119

点击“下一步”,选择“直接附加现有策略”,一直“下一步",后会提示创建用户成功。

添加用户

image-20220427121944684

到此,我们也就创建完成。接下来继续下面的步骤。

初始化 Amplify 项目结构和配置文件。运行以下命令

amplify init

? Enter a name for your project (androidgettingstarted): accept the default, press enter? Enter a name for the environment (dev): accept the default, press enter? Choose your default editor: use the arrow key to select your favorite text editor an press enter? Choose the type of app that you're building: android is already selected, press enter? Where is your Res directory: accept the default, press enter? Do you want to use an AWS profile?, Y, press enter? Please choose the profile you want to use: use the arrow keys to select your profile and press enter.

如果没有配置文件,可使用 Amazon CLI 键入命令 aws configure --profile 创建一个。

Amplify 在云中初始化您的项目,可能需要几分钟。几分钟后,您应该会看到如下消息:

image-20220427122000627

2.2.4将Amplify库添加到项目中

Amplify for Android 是作为 Apache Maven 软件包分发的。在本部分中,您会将软件包和其他必需的指令添加到构建配置中。

返回 Android Studio,展开 Gradle Scripts 并打开 build.gradle (Project: Android_Getting_Started)。在 buildscript 和 allprojects 数据块的 repositories 数据块内添加行 mavenCentral()。

buildscript {ext.kotlin_version = "1.4.10"repositories {google()jcenter()// Add this line into `repositories` in `buildscript`mavenCentral()}dependencies {classpath "com.android.tools.build:gradle:4.0.1"classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"// NOTE: Do not place your application dependencies here; they belong// in the individual module build.gradle files}}allprojects {repositories {google()jcenter()// Add this line into `repositories` in `buildscript`mavenCentral()}}

Gradle Scripts 下,打开 build.gradle (Module:app),并在 implementations 数据块中添加 Amplify 框架核心依赖项。

dependencies {implementation fileTree(dir: "libs", include: ["*.jar"])implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.core:core-ktx:1.3.2'implementation 'androidx.appcompat:appcompat:1.2.0'implementation 'com.google.android.material:material:1.2.1'implementation 'androidx.constraintlayout:constraintlayout:2.0.1'implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'testImplementation 'junit:junit:4.13'androidTestImplementation 'androidx.test.ext:junit:1.1.2'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'// Amplify core dependencyimplementation 'com.amplifyframework:core:1.4.0'}

如果您使用 Java 或目标 Android SDK 21 或更早版本进行开发,请查看文档了解其他配置更改。

现在,运行 Gradle Sync

image-20220427122014118

过一会儿您将看到

BUILD SUCCESSFUL in 1s

image-20220427122027591

2.2.5在运行时初始化Amplify

我们来创建一个后端类对代码进行分组,以便与后端交互。我使用单例设计模式,使其通过应用程序轻松可用,并确保 Amplify 库仅初始化一次。

类初始化程序负责初始化 Amplify 库。

在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Backend.kt,打开它并添加以下代码:

package com.example.androidgettingstartedimport android.content.Contextimport android.util.Logimport com.amplifyframework.AmplifyExceptionimport com.amplifyframework.core.Amplifyobject Backend {private const val TAG = "Backend"fun initialize(applicationContext: Context) : Backend {try {Amplify.configure(applicationContext)Log.i(TAG, "Initialized Amplify")} catch (e: AmplifyException) {Log.e(TAG, "Could not initialize Amplify", e)}return this}}

应用程序启动时,初始化单例 Backend 对象。

在 java/com.example.androidgettingstarted 下创建一个新的 Kotlin 文件 Application.kt,打开它并添加以下代码:

package com.example.androidgettingstartedimport android.app.Applicationclass AndroidGettingStartedApplication : Application() {override fun onCreate() {super.onCreate()// initialize Amplify when application is startingBackend.initialize(applicationContext)}}

在“manifests”下,打开 AndroidManifest.xml,并将应用程序类的名称添加到 元素。

    <!-- add the android:name attribute to the application node  --><applicationandroid:name="AndroidGettingStartedApplication"android:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="@string/app_name"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/Theme.GettingStartedAndroid">...

打开此文件后,请添加一些在本教程的后续步骤中应用程序需要的权限:

    <!-- add these nodes between manifest and application  --><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

2.2.6验证您的设置

要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R

要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的 运行 图标 ▶️ 或按 ^ R。

image-20220427122041766

应该不会出现错误。

BUILD SUCCESSFUL in 6s

23 actionable tasks: 8 executed, 15 up-to-date

3.添加身份验证

在此单元中,您将使用 Amplify CLI 和库配置和添加身份验证到您的应用程序中。

image-20220427122053082

3.1简介

您将添加的下一个功能是用户身份验证。在此单元中,您将了解如何使用 Amplify CLI 和库对用户进行身份验证,以利用托管用户身份提供商 Amazon Cognito。

您还将了解如何使用 Cognito 托管用户界面展示整个用户身份验证流,从而使用户只需几行代码即可注册、登录和重置密码。

使用“托管用户界面”意味着应用程序利用 Cognito 网页登录和注册用户界面流。应用程序的用户将重定向到 Cognito 托管的网页,并在登录后重定向回应用程序。当然,Cognito 和 Amplify 也支持本机 UI。

3.2实施

3.2.1创建身份验证服务

要创建身份验证服务,请打开一个终端,然后执行以下命令:

amplify add auth

当您看到此消息时,即表示配置成功(资源的确切名称将有所不同):

Successfully added resource androidgettingstartedfc5a4717 locally

3.2.2部署身份验证服务

现在,已在本地配置身份验证服务,我们可以将它部署到云:在终端中,请在项目目录中执行以下命令

amplify push

# press Y when asked to continue

片刻之后,您应看到以下消息:

✔ All resources are updated in the cloudHosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/Test Your Hosted UI Endpoint: https://androidgettingstarted-dev.auth.eu-central-1.amazoncognito.com/login?respons

3.2.3向项目添加Amplify身份验证库

在转到代码之前,请先返回 Android Studio,将以下依赖项连同您之前添加的其他

amplifyframework 实现一起,添加到您的单元的

build.gradle,然后在看到提示时,单击 Sync Now

dependencies {...// Amplify core dependencyimplementation 'com.amplifyframework:core:1.4.0'implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'}

3.2.4在运行时配置Amplify身份验证库

返回 Android Studio,打开 Backend.kt 文件。在后端类中,向我们在上一部分(在 initialize() 方法中)添加的 Amplify 初始化代码添加一行

完整代码块应如下所示:

// inside Backend classfun initialize(applicationContext: Context) : Backend {try {Amplify.addPlugin(AWSCognitoAuthPlugin())Amplify.configure(applicationContext)Log.i(TAG, "Initialized Amplify")} catch (e: AmplifyException) {Log.e(TAG, "Could not initialize Amplify", e)}return this}

请不要忘记添加导入语句,Android Studio 会自动为您完成此操作(在 Mac 上,在代码编辑器检测到的每个错误上,同时按 Alt 和 Enter 键)。

像在上一步骤中那样,为每个缺少的类定义添加所需的导入语句(在红色单词上同时按 Alt 和 Enter 键)。

要验证一切是否都按预期运行,请构建项目。单击 Build 菜单,并选择 Make Project,或者,在 Mac 上按 ⌘F9。应该不会出现错误。

3.2.5在运行时触发身份验证

其余代码更改会跟踪用户的状态(他们是否已登录?)并在用户单击锁定图标时触发“SignIn/SignUp”用户界面。

a.添加 signIn 和 signOut 方法

在后端类中的任意位置,添加以下四种方法:

private fun updateUserData(withSignedInStatus : Boolean) {UserData.setSignedIn(withSignedInStatus)}fun signOut() {Log.i(TAG, "Initiate Signout Sequence")Amplify.Auth.signOut({ Log.i(TAG, "Signed out!") },{ error -> Log.e(TAG, error.toString()) })}fun signIn(callingActivity: Activity) {Log.i(TAG, "Initiate Signin Sequence")Amplify.Auth.signInWithWebUI(callingActivity,{ result: AuthSignInResult ->  Log.i(TAG, result.toString()) },{ error: AuthException -> Log.e(TAG, error.toString()) })}

然后为每个缺少的类定义添加所需的导入语句(在红色单词上按 Alt 和 Enter 键)。当您可以在多个类之间进行选择时,请务必从 Amplify 包中选择一个,如下面的屏幕截图所示。

image-20220427122109355

请注意,我们没有在这些方法中更新 UserData.isSignedIn 标记,该操作将在下一节中完成。

b.添加身份验证中心侦听器

为了跟踪身份验证状态的变化,我们添加了代码以订阅由 Amplify 发送的身份验证事件。我们在 Backend.initialize() 方法中初始化该中心。

在收到身份验证事件时,我们将调用 updateUserData() 方法。此方法可使 UserData 对象保持同步。UserData.isSignedIn 属性是 LiveData,这意味着当值更改时,订阅此属性的观察者将收到通知。我们使用此机制来自动刷新用户界面。

我们还添加了代码以在应用程序启动时检查以前的身份验证状态。当应用程序启动时,它会检查 Cognito 会话是否已存在,并相应地更新 UserData。

在 Backend.initialize() 中,在 try/catch 块之后和 return 语句之前添加以下代码

// in Backend.initialize() function, after the try/catch block but before the return statement Log.i(TAG, "registering hub event")// listen to auth eventAmplify.Hub.subscribe(HubChannel.AUTH) { hubEvent: HubEvent<*> ->when (hubEvent.name) {InitializationStatus.SUCCEEDED.toString() -> {Log.i(TAG, "Amplify successfully initialized")}InitializationStatus.FAILED.toString() -> {Log.i(TAG, "Amplify initialization failed")}else -> {when (AuthChannelEventName.valueOf(hubEvent.name)) {AuthChannelEventName.SIGNED_IN -> {updateUserData(true)Log.i(TAG, "HUB : SIGNED_IN")}AuthChannelEventName.SIGNED_OUT -> {updateUserData(false)Log.i(TAG, "HUB : SIGNED_OUT")}else -> Log.i(TAG, """HUB EVENT:${hubEvent.name}""")}}}}Log.i(TAG, "retrieving session status")// is user already authenticated (from a previous execution) ?Amplify.Auth.fetchAuthSession({ result ->Log.i(TAG, result.toString())val cognitoAuthSession = result as AWSCognitoAuthSession// update UIthis.updateUserData(cognitoAuthSession.isSignedIn)when (cognitoAuthSession.identityId.type) {AuthSessionResult.Type.SUCCESS ->  Log.i(TAG, "IdentityId: " + cognitoAuthSession.identityId.value)AuthSessionResult.Type.FAILURE -> Log.i(TAG, "IdentityId not present because: " + cognitoAuthSession.identityId.error.toString())}},{ error -> Log.i(TAG, error.toString()) })

要验证一切是否都按预期运行,请构建项目。单击 Build菜单,并选择 Make Project,或者,在 Mac 上按 ⌘F9。应该不会出现错误。

c.更新用户界面代码

代码中的最后一个更改与用户界面相关,我们将 FloatingActionButton 添加到主要活动中。

在“res/layout”下,打开 activity_main.xml,并将现有的 FloatingActionButton 替换为以下内容:

<com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabAuth"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_gravity="bottom|end"android:layout_margin="@dimen/fab_margin"android:src="@drawable/ic_baseline_lock"app:fabCustomSize="60dp"app:fabSize="auto"/>

在“res/drawable”下添加一个锁状图标。右键单击“drawable”,选择 New,然后选择 Vector Asset。从 Clilp Art 中选择锁状图标,然后输入 ic_baseline_lock(不含 _24)作为名称,并从 Clip Art 中选择闭合的锁状图标。单击 Next,然后单击 Finish

对打开的锁状图标重复相同的操作。

image-20220427122126402

执行完以上操作后,您的“drawable”目录下应具有以下文件:

image-20220427122140763

现在,在代码中链接新创建的按钮。在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并添加以下代码。

// anywhere in the MainActivity classprivate fun setupAuthButton(userData: UserData) {// register a click listenerfabAuth.setOnClickListener { view ->val authButton = view as FloatingActionButtonif (userData.isSignedIn.value!!) {authButton.setImageResource(R.drawable.ic_baseline_lock_open)Backend.signOut()} else {authButton.setImageResource(R.drawable.ic_baseline_lock_open)Backend.signIn(this)}}}

还是在 MainActivity 中,在 onCreate() 方法的末尾添加以下代码

setupAuthButton(UserData)UserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->// update UILog.i(TAG, "isSignedIn changed : $isSignedUp")if (isSignedUp) {fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)} else {fabAuth.setImageResource(R.drawable.ic_baseline_lock)}})

以上代码会针对 Userdata.isSignedIn 值注册观察者。当 isSignedIn 值更改时调用闭包。现在,我们只是更改锁状图标:当用户通过身份验证时为打开的锁,当用户没有会话时为闭合的锁。

要验证一切是否都按预期运行,请构建项目。单击 Build 菜单,并选择 Make Project,或者,在 Mac 上按 ⌘F9。应该不会出现错误。

d.更新 AndroidManifest.xml 和 MainActivity

最后,我们必须确保在 Cognito 托管用户界面提供的 Web 身份验证序列结束时启动我们的应用程序。我们在清单文件中添加一个新的活动。当接收到 gettingstarted URI 方案时,将调用该活动。

在 Android Studio 中的“manifests”下,打开 AndroidManifest.xml,并在应用程序元素中添加以下活动。

    <activityandroid:name="com.amazonaws.mobileconnectors.cognitoauth.activities.CustomTabsRedirectActivity"><intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="gettingstarted" /></intent-filter></activity>

在 java/com.example.androidgettingstarted/ 下,打开 MainActivity.kt 并在类中的任意位置添加以下代码。

// MainActivity.kt// receive the web redirect after authenticationoverride fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)Backend.handleWebUISignInResponse(requestCode, resultCode, data)}在 java/com.example.androidgettingstarted/ 下,打开 Backend.kt 并在类中的任意位置添加以下代码。// Backend.kt// pass the data from web redirect to Amplify libs fun handleWebUISignInResponse(requestCode: Int, resultCode: Int, data: Intent?) {Log.d(TAG, "received requestCode : $requestCode and resultCode : $resultCode")if (requestCode == AWSCognitoAuthPlugin.WEB_UI_SIGN_IN_ACTIVITY_CODE) {Amplify.Auth.handleWebUISignInResponse(data)}}

e.构建和测试

要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。应用程序将启动,且屏幕右下角会显示一个闭合的锁状浮动按钮。

以下是完整的注册流程。

image-20220427122201468

image-20220427122216727

image-20220427122233830

image-20220427122249246

image-20220427122306838

4.添加 GraphQL API 和数据库

在此单元中,您将使用 Amplify CLI 和库配置和添加 GraphQL API 到您的应用程序中。

image-20220427122320475

4.1简介

现在,我们已经创建并配置了带用户身份验证功能的应用程序。接下来,我们要在数据库中添加 API 以及“创建”、“读取”、“更新”、“删除”(CRUD) 操作。

在此单元中,您将使用 Amplify CLI 和库将 API 添加到您的应用程序中。您将创建的 API 是 GraphQL API,它利用 Amazon DynamoDB(NoSQL 数据库)支持的 Amazon AppSync(托管 GraphQL 服务)。有关 GraphQL 的介绍,请访问此页面。

您将构建的应用程序是备注记录应用程序,用户可使用它创建、删除和列出备注。本示例将让您了解如何构建很多常见类型的 CRUD+L(创建、读取、更新、删除和列出)应用程序。

4.2实施

4.2.1创建GraphQL API服务和数据库

要创建 GraphQL API 及其后备数据库,请打开一个终端,然后从项目目录中执行此命令

amplify add api

初始化项目时选择的默认文本编辑器 (amplify init) 将使用预构建数据 schema 打开。

删除此 schema,并使用我们的应用程序 GraphQL schema 替换

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

数据模型由一个类 NoteData 和 4 个属性组成:ID 和名称是必填项,描述和图像是选填字符串。

@model 转换器指示我们需要创建数据库来存储数据。

@auth 转换器添加了身份验证规则,以允许访问此数据。对于此项目,我们希望仅 NoteData 的拥有者有访问它们的权限。

完成后,请不要忘记保存,然后返回您的终端以告知 Amplify CLI 您已完成。

? Press enter to continue, press enter.

几秒钟后,您应该会看到一条成功消息:

GraphQL schema compiled successfully.

4.2.2生成客户端代码

根据我们刚刚创建的 GraphQL 数据模型定义,Amplify 会生成客户端代码(即 Swift 代码)以代表我们应用程序中的数据。

要生成代码,请在终端执行以下命令:

amplify codegen models

这将在 java/com/amplifyframework.datastore.generated.model 目录中创建 Java 文件,如以下情况所示:

➜  Android Getting Started git:(master) ✗ ls -al app/src/main/java/com/amplifyframework/datastore/generated/model total 24drwxr-xr-x  4 stormacq  admin   128 Oct  7 15:27 .drwxr-xr-x  3 stormacq  admin    96 Oct  7 15:27 ..-rw-r--r--  1 stormacq  admin  1412 Oct  7 15:27 AmplifyModelProvider.java-rw-r--r--  1 stormacq  admin  7153 Oct  7 15:27 NoteData.java

这些文件会自动导入到您的项目中。

4.2.2部署API服务和数据库

要部署我们刚刚创建的后端 API 和数据库,请转至您的终端,然后执行命令

amplify push# press Y when asked to continue? Are you sure you want to continue? accept the default Y and press enter? Do you want to generate code for your newly created GraphQL API type N and press enter

几分钟后,您应该会看到一条成功消息:

✔ All resources are updated in the cloud

GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql

4.2.4将API客户端库添加到Android Studio项目

在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他“amplifyframework”实现一起添加到您的单元的 build.gradle,然后在看到提示时单击 Sync Now

dependencies {implementation 'com.amplifyframework:aws-api:1.4.0'implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'}

4.2.5在运行时初始化Amplify库

打开 Backend.kt,然后在 initialize() 方法的 Amplify 初始化序列中添加一行。完整的尝试/捕获代码块应如下所示:

try {Amplify.addPlugin(AWSCognitoAuthPlugin())Amplify.addPlugin(AWSApiPlugin())Amplify.configure(applicationContext)Log.i(TAG, "Initialized Amplify")} catch (e: AmplifyException) {Log.e(TAG, "Could not initialize Amplify", e)}

4.2.6在GraphQL数据模型和应用程序模型之间添加桥接

我们的项目已经有一个数据模型来表示备注。在此教程中,我们将继续使用该模型,并提供一种将 NoteData 转换为备注的简单方法。打开 UserData.kt 并添加两个组件:一个动态属性,从 UserData.Note 返回 NoteData 对象;一个相反静态方法,接收 API NoteData 并返回 Userdata.Note。

在数据类 Note 中,添加以下内容:

// return an API NoteData from this Note objectval data : NoteDataget() = NoteData.builder().name(this.name).description(this.description).image(this.imageName).id(this.id).build()// static function to create a Note from a NoteData API objectcompanion object {fun from(noteData : NoteData) : Note {val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)// some additional code will come here laterreturn result}}     

确保通过生成的代码导入 NoteData 类。

4.2.7将API CRUD方法添加到后端类

我们添加 3 种方法来调用 API:一种查询 Note 的方法,一种创建新 Note 的方法,以及一种删除 Note 的方法。请注意,这些方法适用于应用程序数据模型 (Note),以便通过用户界面轻松交互。这些方法可以透明地将 Note 转换为 GraphQL 的 NoteData 对象。

打开 Backend.kt 文件,然后在后端类末尾添加以下代码段:

fun queryNotes() {Log.i(TAG, "Querying notes")Amplify.API.query(ModelQuery.list(NoteData::class.java),{ response ->Log.i(TAG, "Queried")for (noteData in response.data) {Log.i(TAG, noteData.name)// TODO should add all the notes at once instead of one by one (each add triggers a UI refresh)UserData.addNote(UserData.Note.from(noteData))}},{ error -> Log.e(TAG, "Query failure", error) })}fun createNote(note : UserData.Note) {Log.i(TAG, "Creating notes")Amplify.API.mutate(ModelMutation.create(note.data),{ response ->Log.i(TAG, "Created")if (response.hasErrors()) {Log.e(TAG, response.errors.first().message)} else {Log.i(TAG, "Created Note with id: " + response.data.id)}},{ error -> Log.e(TAG, "Create failed", error) })}fun deleteNote(note : UserData.Note?) {if (note == null) returnLog.i(TAG, "Deleting note $note")Amplify.API.mutate(ModelMutation.delete(note.data),{ response ->Log.i(TAG, "Deleted")if (response.hasErrors()) {Log.e(TAG, response.errors.first().message)} else {Log.i(TAG, "Deleted Note $response")}},{ error -> Log.e(TAG, "Delete failed", error) })}确保通过生成的代码导入 ModelQuery、ModelMutation 和 NoteData 类。最后,我们必须在应用程序启动时调用此 API,以查询当前登录用户的 Note 列表。在 Backend.kt 文件中,更新 updateUserData(withSignInStatus: Boolean) 方法,使其看起来类似以下内容:// change our internal state and query list of notes private fun updateUserData(withSignedInStatus : Boolean) {UserData.setSignedIn(withSignedInStatus)val notes = UserData.notes().valueval isEmpty = notes?.isEmpty() ?: false// query notes when signed in and we do not have Notes yetif (withSignedInStatus && isEmpty ) {this.queryNotes()} else {UserData.resetNotes()}}

现在,只需创建一个用户界面即可创建新 Note 和从列表中删除 Note。

4.2.8添加“Edit"按钮以添加备注

现在,后端和数据模型已到位,本节的最后一步是让用户创建新的 Note 然后将其删除。

a.在 Android Studio 中的 res/layout 下,创建一个新布局:

右键单击 layout,选择“New”,然后选择 Layout Resource File。将此文件命名为 activity_add_note 并接受所有其他默认值。单击 OK

image-20220427122340132

打开刚刚创建的文件 activity_add_note,然后通过粘贴以下内容替换所生成的代码:

<?xml version="1.0" encoding="utf-8"?><ScrollView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:fitsSystemWindows="true"android:fillViewport="true"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="8dp"><TextViewandroid:id="@+id/title"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:text="Create a New Note"android:textSize="10pt" /><EditTextandroid:id="@+id/name"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginTop="8dp"android:hint="name"android:inputType="text"android:lines="5" /><EditTextandroid:id="@+id/description"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_marginBottom="8dp"android:hint="description"android:inputType="textMultiLine"android:lines="3" /><Spaceandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1" /><Buttonandroid:id="@+id/addNote"style="?android:attr/buttonStyleSmall"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:backgroundTint="#009688"android:text="Add Note" /><Buttonandroid:id="@+id/cancel"style="?android:attr/buttonStyleSmall"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:backgroundTint="#FFC107"android:text="Cancel" /></LinearLayout></ScrollView>

这是一个非常简单的布局,可以只输入 Note 标题和描述。

b.添加 AddNoteActivity 类。

在 java/com.example.androidgettingstarted 下,创建一个新的 Kotlin 文件 AddActivityNote.kt,然后打开此文件并添加以下代码:

package com.example.androidgettingstartedimport android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport kotlinx.android.synthetic.main.activity_add_note.*import java.util.*class AddNoteActivity : AppCompatActivity()  {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_add_note)cancel.setOnClickListener {this.finish()}addNote.setOnClickListener {// create a note objectval note = UserData.Note(UUID.randomUUID().toString(),name?.text.toString(),description?.text.toString())// store it in the backendBackend.createNote(note)// add it to UserData, this will trigger a UI refreshUserData.addNote(note)// close activitythis.finish()}}companion object {private const val TAG = "AddNoteActivity"}}    

最后,在清单下打开 AndroidManifest.xml,并在应用程序节点的任何位置添加以下活动元素。

<activityandroid:name=".AddNoteActivity"android:label="Add Note"android:theme="@style/Theme.GettingStartedAndroid.NoActionBar"><meta-dataandroid:name="android.support.PARENT_ACTIVITY"android:value="com.example.androidgettingstarted.MainActivity" /></activity>

c.在“Main Activity”中添加一个“Add Note”FloatingActionButton

在 res/layout 下,打开 activity_main.xml 并将以下内容添加到现有“Floating Action Button”上方。

<com.google.android.material.floatingactionbutton.FloatingActionButtonandroid:id="@+id/fabAdd"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_gravity="bottom|end"android:layout_margin="@dimen/fab_margin"android:visibility="invisible"android:src="@drawable/ic_baseline_post_add"app:fabCustomSize="60dp"app:fabSize="auto"/>

在 res/drawable 中添加一个“Add Note”图标。右键单击“drawable”,选择 New,然后选择 Vector Asset。输入 ic_baseline_add 作为名称,并从“Clip Art”中选择添加图标。单击 Next,然后单击 Finish

image-20220427122358242

d.添加代码以处理“Add Note”按钮。

要拥有功能完全的“Add Button”,还需要完成的最后两项工作是让按钮根据 isSignedIn 值显示或消失,显然,需要添加代码来处理按钮点击操作。

打开 mainActivity.kt,并将以下内容添加到 onCreate() 方法的末尾:

// register a click listenerfabAdd.setOnClickListener {startActivity(Intent(this, AddNoteActivity::class.java))}然后,仍然在 onCreate() 方法中,将 UserData.isSignedIn.observe 替换为以下内容:UserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->// update UILog.i(TAG, "isSignedIn changed : $isSignedUp")//animation inspired by https://www.11zon.com/zon/android/multiple-floating-action-button-android.phpif (isSignedUp) {fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)Log.d(TAG, "Showing fabADD")fabAdd.show()fabAdd.animate().translationY(0.0F - 1.1F * fabAuth.customSize)} else {fabAuth.setImageResource(R.drawable.ic_baseline_lock)Log.d(TAG, "Hiding fabADD")fabAdd.hide()fabAdd.animate().translationY(0.0F)}})    

要验证一切是否都按预期运行,请构建项目。单击 Build 菜单,并选择 Make Project,或者,在 Mac 上按 ⌘F9。应该不会出现错误。

运行应用程序时,您将看到“Add Note”按钮会在用户登录时显示,在用户注销时消失。您现在可以添加备注了。

4.2.9添加“滑动删除”行为

可以通过在 Note 列表中添加触摸处理程序来添加“滑动删除”行为。触摸处理程序负责绘制红色背景、删除图标,并在释放触摸时调用 Backend.delete() 方法。

a.创建一个新类 SimpleTouchCallback。

在 java/com 下,右键单击 example.androidgettingstarted,依次选择 NewKotlin File,然后输入 SwipeCallback 作为名称。

image-20220427122413871

将以下代码粘贴到新文件中:

package com.example.androidgettingstartedimport android.graphics.Canvasimport android.graphics.Colorimport android.graphics.drawable.ColorDrawableimport android.graphics.drawable.Drawableimport android.util.Logimport android.widget.Toastimport androidx.appcompat.app.AppCompatActivityimport androidx.core.content.ContextCompatimport androidx.recyclerview.widget.ItemTouchHelperimport androidx.recyclerview.widget.RecyclerView// https://stackoverflow.com/questions/33985719/android-swipe-to-delete-recyclerviewclass SwipeCallback(private val activity: AppCompatActivity): ItemTouchHelper.SimpleCallback(0,ItemTouchHelper.LEFT) {private val TAG: String = "SimpleItemTouchCallback"private val icon: Drawable? = ContextCompat.getDrawable(activity,R.drawable.ic_baseline_delete_sweep)private val background: ColorDrawable = ColorDrawable(Color.RED)override fun onChildDraw(c: Canvas,recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,dX: Float,dY: Float,actionState: Int,isCurrentlyActive: Boolean) {super.onChildDraw(c,recyclerView,viewHolder,dX,dY,actionState,isCurrentlyActive)val itemView = viewHolder.itemViewval backgroundCornerOffset = 20val iconMargin = (itemView.height - icon!!.intrinsicHeight) / 2val iconTop = itemView.top + (itemView.height - icon.intrinsicHeight) / 2val iconBottom = iconTop + icon.intrinsicHeightval iconRight: Int = itemView.right - iconMarginif (dX < 0) {val iconLeft: Int = itemView.right - iconMargin - icon.intrinsicWidthicon.setBounds(iconLeft, iconTop, iconRight, iconBottom)background.setBounds(itemView.right + dX.toInt() - backgroundCornerOffset,itemView.top, itemView.right, itemView.bottom)background.draw(c)icon.draw(c)} else {background.setBounds(0, 0, 0, 0)background.draw(c)}}override fun onMove(recyclerView: RecyclerView,viewHolder: RecyclerView.ViewHolder,target: RecyclerView.ViewHolder): Boolean {Toast.makeText(activity, "Moved", Toast.LENGTH_SHORT).show()return false}override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {Toast.makeText(activity, "deleted", Toast.LENGTH_SHORT).show()//Remove swiped item from list and notify the RecyclerViewLog.d(TAG, "Going to remove ${viewHolder.adapterPosition}")// get the position of the swiped item in the listval position = viewHolder.adapterPosition// remove to note from the userdata will refresh the UIval note = UserData.deleteNote(position)// async remove from backendBackend.deleteNote(note)}}

重要的代码行位于 onSwiped() 方法中。当滑动手势完成时,将调用此方法。我们将收集列表中滑动项的位置,并会将相应备注从 UserData 结构(这将更新用户界面)和云后端中删除。

b.现在,我们已经有了类,让我们在 res/drawable 中添加“删除”图标。

右键单击“drawable”,选择 New,然后选择 Vector Asset。输入 ic_baseline_delete_sweep 作为名称,并从“Clip Art”中选择“删除滑动”图标。单击 Next,然后单击 Finish

image-20220427122434214

c.将以下代码粘贴到新文件中:为 RecyclerView 添加“滑动删除”手势处理程序。

在 java/com/example.androidgettingstarted 下,打开 MainActivity.kt,并将下面两行代码添加到 setupRecyclerView 中:

// add a touch gesture handler to manager the swipe to delete gestureval itemTouchHelper = ItemTouchHelper(SwipeCallback(this))itemTouchHelper.attachToRecyclerView(recyclerView)

4.2.10构建和测试

要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。

假设您仍处于登录状态,应用程序会在一个空列表上启动。它现在具有一个用于添加 Note 的“Add Note”按钮。**单击“Add Note”符号、输入标题和描述、单击“Add Note”**按钮,随后备注应显示在列表中。

您可以通过向左滑动一行来删除 Note。

5.添加图像存储功能

在本单元中,您将添加存储以及将图像与您的应用程序中的备注关联的功能。

image-20220427122446750

5.1简介

现在,我们的备注应用程序已在运行,接下来添加将图像与每个备注关联的功能。在本单元中,您将使用 Amplify CLI 和库来创建利用 Amazon S3 的存储服务。最后,您将更新 Android 应用程序以启用图像上传、获取和渲染。

5.2实施

5.2.1创建存储服务

要添加图像存储功能,我们将使用 Amplify 存储类别:

amplify add storage

一段时间后,您将看到:

Successfully added resource image locally

5.2.2部署存储服务

要部署我们刚刚创建的存储服务,请转至您的终端,然后执行以下命令

amplify push

Y 确认,一段时间后,您将看到:

✔ Successfully pulled backend environment amplify from the cloud.

5.2.2向Android Studio项目添加Amplify存储库

在转到此代码之前,请先返回 Android Studio,将以下依赖关系连同您之前添加的其他

amplifyframework 实现一起添加到您的单元的

build.gradle,然后在看到提示时单击 Sync Now

dependencies {...// Amplify core dependencyimplementation 'com.amplifyframework:core:1.4.0'implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'implementation 'com.amplifyframework:aws-api:1.4.0'implementation 'com.amplifyframework:aws-storage-s3:1.4.0'}

5.2.4在运行时初始化Amplify存储插件

返回 Android Studio,在 java/example.androidgettingstarted 下,打开 Backend.kit,并在 initialize() 方法中的 Amplify 初始化序列中添加一行。完整代码块应如下所示:

try {Amplify.addPlugin(AWSCognitoAuthPlugin())Amplify.addPlugin(AWSApiPlugin())Amplify.addPlugin(AWSS3StoragePlugin())Amplify.configure(applicationContext)Log.i(TAG, "Initialized Amplify")} catch (e: AmplifyException) {Log.e(TAG, "Could not initialize Amplify", e)}

5.2.5将Image CRUD方法添加到后端类

依然在 Backend.kt 中。在后端类的任何位置,添加以下三个方法,用于从存储中上传、下载和删除图像:

fun storeImage(filePath: String, key: String) {val file = File(filePath)val options = StorageUploadFileOptions.builder().accessLevel(StorageAccessLevel.PRIVATE).build()Amplify.Storage.uploadFile(key,file,options,{ progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") },{ result -> Log.i(TAG, "Successfully uploaded: " + result.key) },{ error -> Log.e(TAG, "Upload failed", error) })}fun deleteImage(key : String) {val options = StorageRemoveOptions.builder().accessLevel(StorageAccessLevel.PRIVATE).build()Amplify.Storage.remove(key,options,{ result -> Log.i(TAG, "Successfully removed: " + result.key) },{ error -> Log.e(TAG, "Remove failure", error) })}fun retrieveImage(key: String, completed : (image: Bitmap) -> Unit) {val options = StorageDownloadFileOptions.builder().accessLevel(StorageAccessLevel.PRIVATE).build()val file = File.createTempFile("image", ".image")Amplify.Storage.downloadFile(key,file,options,{ progress -> Log.i(TAG, "Fraction completed: ${progress.fractionCompleted}") },{ result ->Log.i(TAG, "Successfully downloaded: ${result.file.name}")val imageStream = FileInputStream(file)val image = BitmapFactory.decodeStream(imageStream)completed(image)},{ error -> Log.e(TAG, "Download Failure", error) })}

这三种方法仅调用其 Amplify 对应项。Amplify 存储有三个文件保护级别:

  • 公有:所有用户均可访问

  • 受保护:所有用户均可读取,但只有创建用户可写入

  • 私有:只有创建用户可读可写

对于此应用程序,我们希望仅备注拥有者可使用图像,我们将使用 StorageAccessLevel.PRIVATE 属性。

5.2.6添加UI代码以捕获图像

下一步是修改 UI,以允许用户在单击 AddNoteACtivity 上的“Add image”按钮时从手机库中选择图像。

必须执行两个更改:更改“Add Note”活动布局以添加“Add image”按钮和图像视图,以及在活动类中添加处理程序代码。

在 Android Studio 中的“res/layout”下,打开 activity_add_note.xml 文件,然后将以下 Button 元素添加到 addNote 按钮的正上方:

<!-- after the description EditText --><com.google.android.material.imageview.ShapeableImageViewandroid:id="@+id/image"android:layout_width="match_parent"android:layout_height="280dp"android:layout_margin="16dp"android:scaleType="centerCrop" /><!-- after the Space --><Buttonandroid:id="@+id/captureImage"style="?android:attr/buttonStyleSmall"android:layout_width="fill_parent"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:backgroundTint="#009688"android:text="Add image" />

在 Android Studio 中的 java/example.androidgettingstarted 下,打开 AddNoteACtivity.kt 文件,然后在 onCreate() 方法中添加以下代码

// inside onCreate() // Set up the listener for add Image buttoncaptureImage.setOnClickListener {val i = Intent(Intent.ACTION_GET_CONTENT,MediaStore.Images.Media.EXTERNAL_CONTENT_URI)startActivityForResult(i, SELECT_PHOTO)}// create rounded corners for the imageimage.shapeAppearanceModel = image.shapeAppearanceModel.toBuilder().setAllCorners(CornerFamily.ROUNDED, 150.0f).build()

在 Intent、MediaStore 和 CornerFamily 上添加所需导入。

同时在伴生对象中添加以下常量值:

// add this to the companion object

private const val SELECT_PHOTO = 100

最后,添加收到的代码并将所选图像存储到临时文件。

将以下代码添加到 AddNoteACtivity 类的任何位置:

//anywhere in the AddNoteActivity classprivate var noteImagePath : String? = nullprivate var noteImage : Bitmap? = nulloverride fun onActivityResult(requestCode: Int, resultCode: Int, imageReturnedIntent: Intent?) {super.onActivityResult(requestCode, resultCode, imageReturnedIntent)Log.d(TAG, "Select photo activity result : $imageReturnedIntent")when (requestCode) {SELECT_PHOTO -> if (resultCode == RESULT_OK) {val selectedImageUri : Uri? = imageReturnedIntent!!.data// read the stream to fill in the previewvar imageStream: InputStream? = contentResolver.openInputStream(selectedImageUri!!)val selectedImage = BitmapFactory.decodeStream(imageStream)val ivPreview: ImageView = findViewById<View>(R.id.image) as ImageViewivPreview.setImageBitmap(selectedImage)// store the image to not recreate the Bitmap every timethis.noteImage = selectedImage// read the stream to store to a fileimageStream = contentResolver.openInputStream(selectedImageUri)val tempFile = File.createTempFile("image", ".image")copyStreamToFile(imageStream!!, tempFile)// store the path to create a notethis.noteImagePath = tempFile.absolutePathLog.d(TAG, "Selected image : ${tempFile.absolutePath}")}}}private fun copyStreamToFile(inputStream: InputStream, outputFile: File) {inputStream.use { input ->val outputStream = FileOutputStream(outputFile)outputStream.use { output ->val buffer = ByteArray(4 * 1024) // buffer sizewhile (true) {val byteCount = input.read(buffer)if (byteCount < 0) breakoutput.write(buffer, 0, byteCount)}output.flush()output.close()}}}

以上代码将所选图像作为 InputStream 使用两次。第一次,InputStream 会创建一个位图图像以显示在用户界面中,第二次,InputStream 会保存一个临时文件以发送到后端。

本单元将浏览一个临时文件,因为 Amplify API 使用 Fileobjects。尽管不是最高效的设计,但此代码十分简单。

要验证一切是否都按预期运行,请构建项目。单击 Build 菜单,并选择 Make Project,或者,在 Mac 上按 ⌘F9。应该不会出现错误。

5.2.7创建备注时存储图像

创建备注时,我们从后端调用存储方法。打开 AddNoteActivity.kt 并修改 addNote.setOnClickListener() 方法,以便在创建备注对象后添加以下代码。

// add this in AddNoteACtivity.kt, inside the addNote.setOnClickListener() method and after the Note() object is created.if (this.noteImagePath != null) {note.imageName = UUID.randomUUID().toString()//note.setImage(this.noteImage)note.image = this.noteImage// asynchronously store the image (and assume it will work)Backend.storeImage(this.noteImagePath!!, note.imageName!!)}

5.2.8加载备注时加载图像

要加载图像,我们需要修改备注数据类上的静态方法。这样,每当 API 返回的 NoteData 对象转换为 Note 对象时,将同时加载图像。加载图像时,我们会通知 LiveData 的 UserData,以便观察者知晓进行的更改。这将触发用户界面刷新。

打开 UserData.kt,然后修改备注数据类的伴生对象,如下所示:

// static function to create a Note from a NoteData API objectcompanion object {fun from(noteData : NoteData) : Note {val result = Note(noteData.id, noteData.name, noteData.description, noteData.image)if (noteData.image != null) {Backend.retrieveImage(noteData.image!!) {result.image = it// force a UI updatewith(UserData) { notifyObserver() }}}return result}}

5.2.9删除备注时删除图像

最后一步是自己清理,即,当用户删除备注时,从云存储中删除图像。如果清理不是为了节省存储空间,可以出于节省 AWS 费用的目的进行清理,因为 Amazon S3 对存储的数据按 Gb/月收费(前 5Gb 免费,运行本教程不收费)。

打开 SwipeCallback.kt,然后在 onSwipe() 方法末尾添加以下代码:

if (note?.imageName != null) {

​ //asynchronously delete the image (and assume it will work)

​ Backend.deleteImage(note.imageName!!)

}

5.2.10构建和测试

要验证一切是否都按预期运行,请构建并运行项目。单击工具栏中的运行图标 ▶️,或按 ^ R。应该不会出现错误。

假设您仍在登录中,应用程序将从未从上一部分删除的备注列表开始。再次使用“Add Note”按钮创建备注。这一次,添加从本地图像存储中选择的图片。

退出应用程序并重新启动,以验证图像是否正确加载。

总结

到这里,你已经使用 Amazon Amplify 构建了 Android 应用程序! 并且在应用程序中添加了身份验证,使用户可以注册、登录和管理其账户。该应用程序还使用 Amazon DynamoDB 数据库配置了一个可扩展的 GraphQL API,使用户可以创建和删除备注。您还使用 Amazon S3 添加了文件存储,从而使用户可以上传图像并在其应用程序中查看它们。

download_image

通过本文相信你对从零使用 Amazon Amplify 创建简单的 Android 应用程序有了一个完整的认识,并且完成了入门。

最后想说的是

最后想要说的是,一门新技术,新框架,新的解决方案来临的时候努力去学习它,并从中发现机会。本次使用该技术也算是一个新的体验,总体来说,优势很明显,开发速度很快,只要你具有一定的Android基础,那么你就可以很快的利用该产品制作属于自己的一个应用程序。总之,不断的把自己的舒适圈扩大,扩大,再扩大,主动学习和挑战新的东西 。

最后,很感谢能够阅读到这里的读者。如果看完觉得好的话,还请轻轻点一下赞或者分享给更多的人,你们的鼓励就是作者继续行文的动力。

最后也给大家带来了专属福利
【专属福利】
福利一:100余种产品免费套餐。其中,计算资源Amazon EC2首年12个月免费,750小时/月;存储资源 Amazon S3 首年12个月免费,5GB标准存储容量。

福利二:最新优惠大礼包,200 数 据 与 分 析 抵 扣 券 , 200 数据与分析抵扣券,200 200机器学习抵扣券,200$微服务与应用开发抵扣券。

福利三:解决方案CloudFormation一键部署模版库

❤️❤️❤️如果你喜欢的话,就不要吝惜你的一键三连了。


http://chatgpt.dhexx.cn/article/IefXyg8J.shtml

相关文章

基于Android的个人时间管理设计与开发

选题依据&#xff08;包括目的、意义、国内外现状和发展趋势&#xff0c;主要参考文献&#xff09;&#xff1a; 课题的意义随着科学技术高速发展&#xff0c;手机普遍率越来越高&#xff0c;大学生甚至小学生几乎人人拥有一台智能手机&#xff0c;移动端应用发展也越来越快&am…

基于Android平台的酒店预订管理系统软件设计的论文

基于Android平台的酒店预订管理系统软件设计 摘要 随着智能手机的快速普及&#xff0c;智能手机操作系统市场风生水起。为了让智能手机用户能够随时随地查询互联网所提供的服务&#xff0c;一种高效的办法就是将应用系统的功能拓展到手机终端上&#xff0c;让手机能够通过移动网…

Android Studio项目结构详解

Android Studio 是谷歌推出的一个Android集成开发工具&#xff0c;基于IntelliJ IDEA. 类似 Eclipse ADT&#xff0c;Android Studio 提供了集成的 Android 开发工具用于开发和调试。 在IDEA的基础上&#xff0c;Android Studio 提供&#xff1a; 1.基于Gradle的构建支持。2.An…

基于Android公交查询系统的设计与实现(论文+程序设计源码+数据库文件)

【摘 要】随着互联网的技术的不断更新发展&#xff0c;人们生活节奏也在不断的加快&#xff0c;对于网络的依赖也越来越紧密&#xff0c;尤其是在等公交&#xff0c;经常会错过班次&#xff0c;但又不知道&#xff0c;下次班次几点发车&#xff0c;这样会导致乘客花掉大把时间在…

Android应用程序开发习题答案

第一章、Android 简介 1.简述各种手机操作系统的特点。 目前&#xff0c;手机上的操作系统主要包括以下几种&#xff0c;分别是Android、iOS、Windows Mobile、Windows Phone 7、Symbian、黑莓、PalmOS和Linux。 &#xff08;1&#xff09;Android是谷歌发布的基于Linux的开源手…

基于安卓的个人理财记账app(android毕业设计)

个人理财管理系统是基于Android系统开发的一款手机应用程序。它主要是为了满足人们在快节奏的生活中可以随时记下自己的收支情况的需求。个人理财管理系统与传统的记账方式相比&#xff0c;体现了它的便捷性、安全性及可扩展性。系统采用Eclipse Android Developer Tools作为开…

Android Studio实现外卖订餐系统

项目目录 一、项目概述二、使用技术三、开发环境四、详细设计4.1 工程结构4.2 数据库设计4.3 首页4.4 购物车4.5 我的4.6 滑动菜单 五、运行演示六、项目总结七、源码获取 一、项目概述 随着人们生活节奏的加快&#xff0c;生活质量的普遍提高&#xff0c;足不出户享受美食的需…

Android程序的目录结构

Android程序的目录结构 Android的项目的目录结构&#xff0c;在开始设计程序时&#xff0c;可能感觉不到有多大的用处&#xff0c; 但是随着开发的深入&#xff0c;会涉及到各种类型的文件&#xff0c;项目的目录结构就非常重要了&#xff0c; 你不能把文件乱放&#xff0c…

Android App 架构设计

简介 本文是对谷歌原生文档的翻译&#xff0c;仅供学习参照。 原文链接 此文档写给希望学习最优编程实践和架构以开发健壮、高质量APP的开发者。 开发者常遇到的问题 传统的桌面程序大多数使用场景是有一个启动入口&#xff0c;作为一个独立进程运行。Android app结构要复…

Android 程序框架设计

这篇文章主要内容来自于之前我讲的一个PPT文档&#xff0c;现在将其整理如下。欢迎指正。以下的内容都是来自于我自身的经验&#xff0c;欢迎大家多提自己的建议。 1、一些概念 模式的定义&#xff1a; 每个模式都描述了一个在我们的环境中不断出现的问题&#xff0c;然后描述…

3、Android项目结构分析

Android项目结构分析 文章目录 Android项目结构分析一、文件系统结构1、项目配置清单文件夹 manifests2、源程序文件夹Java3、资源程序文件夹res①布局文件夹res/layout②值文件夹res/values③软件设计的国际化④图像文件夹res/drawable⑤声音文件夹res/raw 二、创建多模块三、…

Android | 序列化Serializable/Parcelable 使用总结

本文已同步发表于我的微信公众号&#xff0c;搜索 代码说 即可关注&#xff0c;欢迎与我沟通交流。 文章目录 一、什么是序列化&#xff1f;为什么要序列化&#xff1f;怎么进行序列化&#xff1f;二、Serializable2.1 序列化举例2.2 重写readObject、writeObject、readResolve…

Android应用程序设计——入门配置

第一章:Android简介与项目部署 版权声明: 本文为博主学习整理原创文章,如有不正之处请多多指教。 未经博主允许不得转载。https://mp.csdn.net/postedit/81837814 Android由来Android又称“机器人”,版权归google公司所有。是Google于2007年11月5日宣布的基于Linux平台的…

Android程序设计基础 • 【第1章 Android程序入门】

全部章节 >>>> 本章目录 1.1 Android 平台介绍 1.1.1 Android 简介 1.1.2 Android 平台的发展 1.1.3 Android 平台架构 1.2 搭建 Android 开发环境 1.2.1 下载和配置 Android Studio 1.2.2 初始化配置 Android Studio 1.2.3 配置和使用 Genymotion 模拟器…

Android自动化的一般方法

1 背景 https://mp.weixin.qq.com/s/B7o3JSkYI_9c4RivatsZKQ Android自动化包含自动化测试和第三方App的自动化运行&#xff0c;这里的自动化测试主要指的是纯粹的黑盒测试&#xff0c;即在完全不了解代码逻辑的情况下编写的测试用例&#xff0c;可以代替人工完成重复性的工…

基于Android的点餐系统设计与实现

目 录 摘 要 2 ABSTRACT 3 目 录 1 1 绪 论 1 1.1课题研究背景和意义 1 1.2课题可行性分析 2 1.3论文框架安排 3 2 开发语言说明 4 2.1 开发工具及环境简介 4 2.2 Android系统 5 2.2.1 Android Features特性 5 2.2.2 Android系统的四大组件 6 3 点餐系统分析 8 3.1概述 8 3.1.1…

基于Android的记账本的设计与实现

1.基于Android平台的记账系统概述 记账就是按照生活中的发生的经济活动&#xff0c;及时进行记录&#xff0c;为了便于分析个人消费和各种财务状况等等&#xff0c;通过您的随身会计——记账本APP更易看出资金的流动方向。 本论文采用的是一种可移动的记账方法&#xff0c;由移…

Android设计模式之——模板方法模式

一、介绍 在面向对象开发过程中&#xff0c;通常会遇到这样的一个问题&#xff0c;我们知道一个算法所需的关键步骤&#xff0c;并确定了这些步骤的执行顺序&#xff0c;但是&#xff0c;某些步骤的具体实现是未知的&#xff0c;或者说某些步骤的实现是会随着环境的变化而改变…

Android程序设计基础

#Android应用程序架构 Android程序的入口被称为Main Activity&#xff0c;带有界面的Android应用程序一般需要从Main Activity启动。 目录结构 Adnroid工程主要包括两个区域&#xff1a;源码区和资源区。 Mainifest.xml 下面是一个标准的AndroidManifest.xml文件样例。 <…

qt如何编写android程序,如何利用Qt开发Android应用程序

Qt 工具箱是一个使用广泛的跨平台GUI工具箱,可用于 Windows、Linux、Mac OSX 和许多手持平台。QT 具有良好结构化(但灵活)的面向对象的结构、清晰的文档以及直观的 API。 Qt最大的优势就是跨平台,一次性开发的Qt应用程序,可以跑Windows、跑linux、跑MAC……如今移动平台Andr…