构建Fire TV应用已经需要考虑诸多事项。您需要处理Fire TV远程控制导航、需要适应远距离观看的用户界面布局,以及与移动应用有着根本性不同的用户体验行为。
如果还要花费时间去处理其他一些事项,例如连接身份验证、管理后端数据或明确如何存储和提供媒体资产等,可能会令您无法承受。若能将上述工作都转移到对开发者友好且易于集成的服务上,那么您将节省大量时间,避免不必要的麻烦。
这正是AWS Amplify的用武之地。AWS Amplify帮助开发者使用AWS服务以很少的后端配置快速构建、部署和扩展全栈网页和移动应用。Amplify可以助您处理所有后端工作,包括身份验证、云存储和数据持久化。这样一来,您就可以专注于打造Fire TV上的流畅体验。
要遵照本Fire TV应用教程,您需要准备好以下项目:
请一定记住,这是个演示,重点介绍Amplify以及相关的Fire OS应用开发 - 这并非生产级应用,因此没有纳入安全最佳实践。
Fire TV应用开发存在着其特有的一些用户体验挑战,包括:方向键导航、专门针对电视优化的布局以及音频焦点处理。如果在应对上述挑战之外还要处理后端基础架构,可能在用户界面工作开始之前您就已耗费大量心力,倍感沮丧。
Amplify可以带给您便捷。它可让您跳过下列繁琐的后端工作,直接专注于应用的打造:
利用本指南创建应用,主要可在上述三个特性上获益,但Amplify还为您提供无服务器功能,可让您在不用管理基础架构的情况下为后台处理添加Lambda函数。
这意味着您还将学习无服务器应用开发,这样您就可以专注于打造自己的Fire TV体验,而不用从头开始连接云服务。
第一步是设置Amplify后端,为我们的应用提供身份验证、存储和数据方面的支持。
在使用Amplify CLI配置资源之前,您需要具有足够权限的IAM Identity Center凭证。首先登录AWS控制台并在IAM Identity Center创建新用户。
 
                        创建用户后,您将看到新用户的访问门户URL、用户名和一次性密码。
 
                        按照此处(仅提供英文版)的其余说明完成用户设置,并从命令行进行Amplify相关调用时将使用的本地AWS配置文件设置。在本指南的其余部分,我们将假设存在一个名为amplify-app-developer的本地AWS配置文件,并且该配置文件具有arn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccess权限。
在您的项目文件夹中,运行以下命令:
~/project$ $ npm create amplify@latest
Need to install the following packages:
create-amplify@1.2.0
Ok to proceed? (y) y
> npx
> create-amplify
✔ Where should we create your project? .
Installing devDependencies:
- @aws-amplify/backend
- @aws-amplify/backend-cli
- aws-cdk-lib@2.189.1
...
Welcome to AWS Amplify!
- Get started by running npx ampx sandbox.
- Run npx ampx help for a list of available commands.
$ npx ampx sandbox --profile amplify-app-developer
The region us-east-1 has not been bootstrapped. Sign in to the AWS console as a Root user or Admin to complete the bootstrap process, then restart the sandbox.
If this is not the region you are expecting to bootstrap, check for any AWS environment variables that may be set in your shell or use --profile <profile-name> to specify a profile with the correct region.
如果这是您首次使用Amplify,那么您的浏览器将打开,引导您完成Amplify环境的启动过程。进行各个步骤的单击操作以完成此设置。
 
                         
                        最终,此启动过程使用AWS CloudFormation来配置AWS云开发工具包使用的资源。CloudFormation堆栈将触及S3、ECR和IAM中的资源。
 
                        启动完成后,再次运行sandbox命令。
$ npx ampx sandbox --profile amplify-app-developer
  Amplify Sandbox
  
  Identifier: 	firedev
  Stack: 	amplify-project-firedev-sandbox-c212f661f8
  Region: 	us-east-1
  
  To specify a different sandbox identifier, use --identifier
WARNING: Schema is using an @auth directive with deprecated provider 'iam'. Replace 'iam' provider with 'identityPool' provider.
✔ Backend synthesized in 1.47 seconds
✔ Type checks completed in 3.38 seconds
✔ Built and published assets
✔ Deployment completed in 210.98 seconds
AppSync API endpoint = https://hw734z3v7bhknhxjpvxf4xrnvu.appsync-api.us-east-1.amazonaws.com/graphql
[Sandbox] Watching for file changes...
您可以让Amplify沙盒进程在此终端中运行。
现在Amplify已经启动并运行,让我们开始构建Android项目。
打开Android Studio(安装说明)。前往File(文件)> New(新建)> New Project(新项目)。
选择带有Empty Activity(空活动)的Television(电视)模板。
 
                        指定Package name(程序包名称)(例如com.example.amplifyfiretv)。将Save Location(保存位置)设置为项目根文件夹。选择最低SDK。对于本例,我们将使用API 29。
 
                        这样将初始化您的新项目,创建许多新文件。一旦Android Studio首次打开您的项目,它将花费几分钟时间下载依赖项。
 
                        为了测试我们的应用,我们将使用Android模拟器。您可以在此页面上找到有关Android TV模拟器和Fire TV应用的更多详细信息。
在Android Studio中,转到Device Manager(设备管理器)。单击Create Virtual Device(创建虚拟设备)。创建New Hardware Profile(新的硬件配置文件)。使用以下设置创建配置文件:
为您的虚拟设备指定名称。在这个示例中,设备将被命名为“Fire TV Emulator”,您可以自行为其指定任何名称。
 
                        单击Finish(完成)。然后,选择新创建的配置文件并单击Next(下一步)。对于系统映像,将API级别选择为23或更高级别。在本指南中,我们将使用Q (29)。下载映像,并为该虚拟设备选择映像。
 
                        结束虚拟设备的创建。然后启动模拟器。
 
                        在设置好我们的初始项目后,我们可以在模拟器中对其进行测试。
 
                        适用于Android TV应用的Android Studio初始设置使用适用于Android TV的Jetpack Compose。Jetpack Compose是Android Studio中用于构建用户界面的默认工具包。然而,对于我们的Fire TV应用,我们将使用Android的Leanback库,因为Fire TV对Leanback的用户界面组件和功能有着很好的支持。因此,您可以如下所示修改app/build.gradle.kts文件:
plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}
android {
    namespace = "com.example.amplifyfiretv"
    compileSdk = 35
    defaultConfig {
        applicationId = "com.example.amplifyfiretv"
        minSdk = 29
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions {
        jvmTarget = "11"
    }
    lint {
        abortOnError = false
        checkReleaseBuilds = false
    }
}
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
    implementation("androidx.leanback:leanback:1.0.0")
    implementation("com.github.bumptech.glide:glide:4.12.0")
    implementation(libs.androidx.lifecycle.runtime.ktx)
    
    // 测试依赖项
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
然后,修改app/src/main/java/com/example/amplifyfiretv/MainActivity.kt以使用Leanback,并以显示非常简单的文本“Hello Android”开始。
package com.example.amplifyfiretv
import android.os.Bundle
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.FragmentActivity
import androidx.leanback.app.BrowseSupportFragment
import androidx.leanback.widget.*
class MainActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                .replace(R.id.main_browse_fragment, MainFragment())
                .commit()
        }
    }
}
class MainFragment : BrowseSupportFragment() {
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        setupUIElements()
        loadContent()
    }
    private fun setupUIElements() {
        title = "Amplify Fire TV"
        headersState = HEADERS_DISABLED
    }
    private fun loadContent() {
        val textView = TextView(requireContext()).apply {
            layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            text = "Hello Android"
            textSize = 24f
        }
        val container = requireActivity().findViewById<ViewGroup>(R.id.main_browse_fragment)
        container.addView(textView)
    }
}
接下来,我们将在主活动页面中添加卡片,每张卡片代表一个视频。现在,我们将在名为cards.json的本地JSON文件中,将所有对视频和缩略图的引用硬编码。这样,我们就可以确保视频播放和导航正常工作。
我们的JSON卡数据由CardDataProvider实时读入,在名为CardData.kt
的文件中定义:
package com.example.amplifyfiretv.data
import android.content.Context
import android.util.Log
import com.google.gson.Gson
data class CardData(
  val title: String,
  val subtitle: String,
  val imageUrl: String,
  val videoUrl: String
)
data class CardDataResponse(
  val cards: List<CardData>
)
object CardDataProvider {
  private const val TAG = "CardDataProvider"
  private var cards: List<CardData> = emptyList()
  fun initialize(context: Context) {
    try {
      val jsonString = context
                         .assets
                         .open("cards.json")
                         .bufferedReader()
                         .use { it.readText() }
      val response = Gson()
                     .fromJson(jsonString, CardDataResponse::class.java)
      cards = response.cards
    } catch (e: Exception) {
      cards = emptyList()
    }
  }
  fun getCards(): List<CardData> {
    return cards
  }
} 
我们将对视频播放使用Media3 ExoPlayer。创建视频播放器和加载视频的所有功能都位于VideoPlayerActivity.kt中。
在模拟器中测试,我们应用的外观如下所示:
 
                         
                        我们已经设置了Amplify,但需要将其添加到我们的项目中。
首先,将Amplify依赖项添加到app/build.gradle.kts中:
implementation("com.amplifyframework:aws-api:2.24.0")
implementation("com.amplifyframework:aws-datastore:2.24.0")
implementation("com.amplifyframework:aws-storage-s3:2.24.0")
implementation("com.amplifyframework:aws-auth-cognito:2.24.0")
然后创建名为AmplifyApp.kt的新文件。其内容如下所示:
package com.example.amplifyfiretv
import android.app.Application
import android.util.Log
import com.amplifyframework.AmplifyException
import com.amplifyframework.api.aws.AWSApiPlugin
import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin
import com.amplifyframework.core.Amplify
import com.amplifyframework.datastore.AWSDataStorePlugin
import com.amplifyframework.storage.s3.AWSS3StoragePlugin
import com.example.amplifyfiretv.data.CardDataProvider
class AmplifyApp : Application() {
    companion object {
        private const val TAG = "AmplifyApp"
    }
    override fun onCreate() {
        super.onCreate()
        
        CardDataProvider.initialize(applicationContext)
        
        try {
            Amplify.addPlugin(AWSApiPlugin())
            Amplify.configure(applicationContext)
        } catch (error: AmplifyException) {
            Log.e(TAG, "Error initializing Amplify", error)
        } catch (error: Exception) {
            Log.e(TAG, "Unexpected error during initialization", error)
        }
    }
} 
然后,修改app/src/main/AndroidManifest.xml,要求Android使用AmplifyApp类作为Application类。
    <application
        android:name=".AmplifyApp"
使用Amplify的好处之一在于,您可以在AWS S3中存储和检索资产。将您的cards.json文件移动到S3存储桶中,以此作为一个简单的测试。应用在编译时不会在本地引用此文件,而是会实时从S3检索卡片的数据。
有关如何使用Amplify在Android项目中设置存储,请参阅此处的文档(仅提供英文版)。
继续操作并创建amplify/storage/resource.ts:
import { defineStorage } from '@aws-amplify/backend';
export const storage = defineStorage({
  name: 'my-bucket',
  access: (allow) => ({
    'data/*': [
      allow.guest.to(['get']),
      allow.authenticated.to(['get'])
    ]
  })
});
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
  loginWith: {
    email: true,
  },
});
然后,创建amplify/backend.ts,规定您将使用自己的身份验证和存储定义。
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { storage } from './storage/resource';
defineBackend({
  auth,
  storage,
});
创建这些文件后,正在运行的Amplify沙盒进程将更新AWS资源,以处理您的身份验证和存储需求。
Amplify沙盒进程生成一个客户端配置文件 (amplify_outputs.json),您的应用在连接到Amplify为您启动的各种资源时将使用该文件。每当运行中的进程检测到您的Amplify配置发生更改时(例如我们在上面所做的更改),这个文件都会自动更新。
默认情况下,amplify_outputs.json直接写入运行Amplify沙盒进程的目录中。但是,您的Fire TV应用需要app/src/main/res/raw/amplify_outputs.json中提供的文件。重新启动Amplify沙盒进程,明确指定从现在开始写入文件应当使用的路径:
$ npx ampx sandbox \
    --profile amplify-app-developer \
    --outputs-out-dir app/src/main/res/raw/
  Amplify Sandbox
  
  Identifier: 	firedev
  Stack: 	      amplify-project-firedev-sandbox-c212f661f8
  Region: 	      us-east-1
  
  To specify a different sandbox identifier, use --identifier
WARNING: Schema is using an @auth directive with deprecated provider 'iam'. Replace 'iam' provider with 'identityPool' provider.
✔ Backend synthesized in 1.47 seconds
✔ Type checks completed in 3.38 seconds
✔ Built and published assets
✔ Deployment completed in 210.98 seconds
AppSync API endpoint = https://hw734z3v7bhknhxjpvxf4xrnvu.appsync-api.us-east-1.amazonaws.com/graphql
[Sandbox] Watching for file changes…
File written: app/src/main/res/raw/amplify_outputs.json
在上面生成的amplify_outputs.json文件中,您可以看到为Amplify项目创建的S3存储桶的名称。
 "storage": {
    "aws_region": "us-east-1",
    "bucket_name": "amplify-project-firedev-sandb-mybucket-ovpsejk2htya",
    ...
在AWS控制台中,导航至S3存储桶,创建一个名为data的子文件夹,然后将cards.json上传到该文件夹。得到的该文件的S3 URI为:
s3://amplify-project-firedev-sandb-mybucket-ovpsejk2htya/data/cards.json接下来,在CardData.kt中修改您的代码。它不会在本地从cards.json读取,而是在amplify_outputs.json中查找S3存储桶名称,然后调用Amplify资源来下载远程文件并将其保存到本地缓存。initialize函数的关键代码行如下所示:
// Set up local file for downloading
val localFile = File(cacheDir, FILE_NAME)
// Get bucket name from AmplifyOutputs
val outputs = AmplifyOutputs.fromResource(R.raw.amplify_outputs)
val jsonString = context
      .resources
      .openRawResource(R.raw.amplify_outputs)
      .bufferedReader()
      .use { it.readText() }
val jsonObject = Gson().fromJson(jsonString, JsonObject::class.java)
val storageObject = jsonObject.getAsJsonObject("storage")
val bucketName = storageObject.get("bucket_name").asString
                
// Set up S3 path - using data/ prefix to match permissions
val s3Path = StoragePath.fromString("$DATA_SUBFOLDER/$FILE_NAME")
val options = StorageDownloadFileOptions.builder().build()
Amplify.Storage.downloadFile(
  s3Path,
  localFile,
  options,
  { result ->
      try {
        val jsonString = localFile.readText()
        val response = Gson()
          .fromJson(jsonString, CardDataResponse::class.java)
        cards = response.cards
        onCardsLoaded?.invoke()
      } catch (e: Exception) {
        e.printStackTrace()
        throw e
      }
  },
  { error ->
      error.printStackTrace()
      throw error
  }
)
然后,更新MainActivity.kt以侦听卡片加载的时间,然后相应地调用updateContent()。
CardDataProvider.setOnCardsLoadedListener {
  updateContent()
}
最后,更新AmplifyApp.kt代码以初始化CardDataProvider、用于存储的S3、用于身份验证的Cognito。
override fun onCreate() {
  super.onCreate()
        
  try {
    Amplify.addPlugin(AWSS3StoragePlugin())
    Amplify.addPlugin(AWSCognitoAuthPlugin())
    val outputs = AmplifyOutputs.fromResource(R.raw.amplify_outputs)
    Amplify.configure(outputs, applicationContext)
            
    applicationScope.launch {
      try {
        CardDataProvider.initialize(applicationContext)
      } catch (e: Exception) {
        e.printStackTrace()
      }
    }
  ...
现在我们已经设置了Amplify,修改了我们的应用程序,并将cards.json文件上传至S3,我们可以构建应用程序并再次运行,在模拟器中进行测试。
 
                        卡片图像和视频播放功能符合预期。您已成功使用Amplify集成了采用S3的存储。
如果您需要代码存储库,敬请期待我们正在编写的题为敬请期待我们正在编写的题为Retrieves card data via remote file with Amplify and S3(使用Amplify和S3通过远程文件检索卡数据)的文章。
为了通过Amplify和S3使用存储,您已经创建并部署了身份验证服务。现在,应该创建一个身份验证流程来处理用户登录和存储会话信息。
有关如何使用Amplify在Android项目中设置身份验证,请参阅此处的文档(仅提供英文版)。
要准备好进行测试,请在Cognito用户池中创建一些用户。在amplify_outputs.json中,可以看到user_pool_id。例如:
"auth": {
    "user_pool_id": "us-east-1_8Riey238F",
    "aws_region": "us-east-1",
    …
在AWS控制台中,导航到Cognito并找到具有此ID的用户池。在用户池中,导航至User management(用户管理)-> Users(用户)。然后创建一些新用户。在本指南中,我们创建两个用户:
| 电子邮件地址 | 密码 | 
| joe@example.com | My password is 1 number. | 
| jen@example.com | My password has 0 numbers. | 
创建这些用户后,您会注意到该用户的确认状态为“Force change password”(强制更改密码)。 由于您使用的只是测试用户,因此您应该使用这些密码确认这些用户账户。这可以使用AWS CLI来完成。例如:
$ aws cognito-idp admin-set-user-password \
    --profile amplify-app-developer \
    --user-pool-id us-east-1_8Riey238F \
    --username jen@example.com \
    --password "My password has 0 numbers." \
    --permanent
现在,您的两个测试用户已得到确认。
 
                        为用户在主页上添加一个简单登录状态。创建一个调用checkAuthSession()函数,以确定用户是否已登录。如果用户未登录,则会显示“Sign In”(登录)。如果用户已登录,则会显示已登录用户的电子邮件地址以及“Sign Out”(登出)。
private fun checkAuthSession() {
  Amplify.Auth.fetchAuthSession(
    { session ->
      val cognitoSession = session as AWSCognitoAuthSession
      if (cognitoSession.isSignedIn) {
        handleSignedInState()
      } else {
        Handler(Looper.getMainLooper()).post {
          updateAuthUI(false)
        }
      }
    },
    { error ->
      Handler(Looper.getMainLooper()).post {
        updateAuthUI(false)
      }
    }
  )
}
在模拟器中,具有不同状态的页面如下所示:
 
                         
                        接下来,创建SignInActivity.kt,它定义了登录的活动屏幕。在app/src/main/res/layout/activity_sign_in.xml, 中定义布局,其中应包含电子邮件地址和密码的字段。该屏幕在进行表单提交时会调用Amplify.Auth.SignIn。您可以在此处(仅提供英文版)查看内置的连接Android的组件。
 
                        请务必将此活动屏幕添加至AndroidManifest.xml:
<activity
  android:name=".SignInActivity"
  android:exported="false"
  android:theme="@style/Theme.AppCompat.NoActionBar" />
构建并运行应用程序,在模拟器中进行测试。身份验证已启动并运行,您可以使用Cognito用户池中两个测试用户的凭证进行登录和登出。
import { type ClientSchema, a, defineData } from '@aws-amplify/backend';
const schema = a.schema({
  Favorite: a
    .model({
      userId: a.string().required(),
      videoId: a.string().required()
    })
    .authorization(allow => [
      allow.authenticated()
    ]),
});
export type Schema = ClientSchema<typeof schema>;
export const data = defineData({
  schema,
  authorizationModes: {
    defaultAuthorizationMode: 'userPool'
  }
});
然后,将下面内容添加到amplify/backend.ts中的Amplify后端定义:
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { storage } from './storage/resource';
import { data } from './data/resource';
defineBackend({
  auth,
  storage,
  data,
});
最后,请务必在AmplifyApp.kt中,向应用程序添加AWSApiPlugin。
...
import com.amplifyframework.api.aws.AWSApiPlugin
...
class AmplifyApp : Application() {
    private val TAG = "AmplifyApp"
    private val applicationScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
    override fun onCreate() {
        super.onCreate()
        try {
            Amplify.addPlugin(AWSS3StoragePlugin())
            Amplify.addPlugin(AWSCognitoAuthPlugin())
            Amplify.addPlugin(AWSApiPlugin())
...
当您进行这些更新时,正在运行的Amplify沙盒进程将更新您的后端资源,以支持对Amplify数据的使用。在AWS控制台中快速检查AWS AppSync服务,可以看到为应用程序设置的API以及架构。
 
                        因为您已经更新了Amplify应用程序配置,您的配置文件 (amplify_outputs.json) 将自动更新。您现在可以看到更新的文件中包含有关您使用Amplify数据的详细信息。
…
"data": {
    "url": "https://kvvm75giyvdk5kjdttewbdfvry.appsync-api.us-east-1.amazonaws.com/graphql",
    "aws_region": "us-east-1",
    "default_authorization_type": "AMAZON_COGNITO_USER_POOLS",
    "authorization_types": [
      "AWS_IAM"
    ],
    "model_introspection": {
      "version": 1,
      "models": {
        "Favorite": {
          "name": "Favorite",
          "fields": {
            "id": {
              "name": "id",
              "isArray": false,
              "type": "ID",
              "isRequired": true,
              "attributes": []
            },
            "userId": {
              "name": "userId",
              "isArray": false,
              "type": "String",
              "isRequired": true,
              "attributes": []
            },
            "videoId": {
              "name": "videoId",
              "isArray": false,
              "type": "String",
              "isRequired": true,
              "attributes": []
            },
            …
然后,需要生成GraphQL客户端代码,应用可以使用该代码方便地编写您将通过Amplify发送到数据后端的请求。
~project/$ npx ampx \
           --profile amplify-app-developer generate graphql-client-code \
           --format modelgen \
           --model-target java \
           --out app/src/main/java/
上面的命令会生成三个Java文件,您的应用程序将构建和使用这些文件
~/project/app/src/main/java/com$ tree amplifyframework
amplifyframework/
└── datastore
    └── generated
        └── model
            ├── AmplifyModelProvider.java
            ├── Favorite.java
            └── FavoritePath.java
修改屏幕以使用DetailsSupportFragment。现在,当您选择行中的视频卡片时,会打开您在VideoDetailsFragment.kt中定义的详情视图。不是播放视频,而是显示两种可能操作的图标:播放视频(三角形)或收藏/取消收藏视频(心形)。
 
                        接下来,更新MainActivity.kt中卡片行的视图,以显示收藏状态。
 
                        您还可以添加一个复选框,以确保每张卡的收藏状态仅在用户登录时显示。
 
                        使用Amplify.API.query查看视频是否为已登录用户的收藏项。列表项目调用很简单,您可以利用为您生成的Favorite模型的GraphQL客户端代码。在VideoCardPresenter.kt代码中的内容如下:
private fun checkFavoriteStatus(
  cardView: ImageCardView,
  cardData: CardData) {
  val userId = AuthStateManager.getInstance().getCurrentUserId()
  val query = ModelQuery.list(Favorite::class.java,
                              Favorite
                                .VIDEO_ID.eq(cardData.videoId)
                                .and(Favorite.USER_ID.eq(userId))
              )
  Amplify.API.query(
    query,
    { response ->
      if (response.hasData()) {
        val isFavorite = response.data.items.count() > 0
        cardView.post {
          updateFavoriteIcon(cardView, isFavorite)
        }
      }
    },
    { error -> 
      Log.e(TAG, "检查收藏状态时出错:${error.message}", error)
    }
  )
}
val favorite = Favorite.builder()
                 .userId(userId)
                 .videoId(videoData.videoId)
                 .build()
val createFavorite = ModelMutation.create(favorite)
Amplify.API.mutate(createFavorite,
  { response -> 
      checkFavoriteStatus()
  },
  { error -> 
      Log.e(TAG, "Error creating favorite: ${error.message}", error)
  }
)
要取消将视频设置为收藏项,请检索记录的id,然后运行delete mutation。
val query = ModelQuery.list(Favorite::class.java,
                            Favorite
                              .VIDEO_ID.eq(videoData.videoId)
                              .and(Favorite.USER_ID.eq(userId))
            )
            
Amplify.API.query(
  query,
  { response ->
    if (response.hasData() && response.data.items.any()) {
      val favorite = response.data.items.first()
      val deleteMutation = ModelMutation.delete(favorite)
                        
      Amplify.API.mutate(
        deleteMutation,
        { 
          isFavorited = false
          updateFavoriteIcon()
        },
        { error -> 
            Log.e(TAG, "删除收藏项时出错:${error.message}", error)
        }
      )
    }
  },
  { error -> 
      Log.e(TAG, 
            "查询收藏项进行删除时出错:${error.message}",
            Error
           )
  }
)
所有代码就绪后,构建应用并在模拟器中进行测试。您可以将单个视频标记为收藏项或取消标记为收藏项。
 
                        注销并重新登录后,将显示用户的收藏夹,表明后端数据仍然存在。
 
                        应用程序即告完成!
在不到一个小时的时间,我们构建了一个Fire TV应用,该应用使用Amplify来处理以下方面:
Amplify替我们处理繁重的工作,让我们专注于最重要的事情:构建在Fire TV上使用体验出色的应用。
如果您是Fire TV开发的新手,或者只想更快地制作原型,Amplify可为您提供一套正确的工具,可以在不跳过基础工作的情况下快速完成工作。以此为基础,您可以通过个性化推荐、分析或推送通知等功能扩展应用,所有这些功能都可获得Amplify的支持。