開発者コンソール

AWS Amplifyを活用して短時間でFire TV対応アプリを作成

Share:
How to Fire TV
Blog_Header_Post_Img

Fire TV対応アプリの開発には、既に検討すべきことが山ほどあります。Fire TVリモコンによるナビゲーション、部屋の離れた場所からでも見やすいUIレイアウト、モバイルアプリとは基本的に異なるUX動作に対応する必要があります。

 

認証の実装やバックエンドデータの管理、メディアアセットの保存や配信の方法の検討などに時間を費やしている余裕はありません。このような作業の負担を、開発者に優しく、簡単に統合できるサービスにすべて任せることができれば、開発効率は大きく向上します。

 

AWS Amplifyはまさにそのためのソリューションです。AWS Amplifyは、開発者がAWSのサービスを最小限のバックエンド設定で利用できる開発プラットフォームです。Webアプリやモバイルアプリを素早く構築、デプロイ、スケールすることができます。Amplifyを使用することで、認証、クラウドストレージ、データ永続化など、バックエンドのあらゆる要素の処理が容易になります。これにより、開発者はFire TVで違和感なく機能するエクスペリエンスの構築に専念することができます。

前提条件

このFire TV対応アプリのチュートリアルに沿って作業を進めるにあたって、以下の準備が必要です。

 

  • Android Studioをダウンロードしてインストールしていること
  • Fire TVデバイスの実機またはAndroid Emulator(セットアップ方法は後述)
  • リソースをプロビジョニングする権限を持つAWSアカウント
  • Amplify CLI(Gen 2)(英語のみ)をインストールしていること
  • AWS Amplify Androidアプリの開発において、レイアウトの編集や画面の関連付けを円滑に行うことができる、基本的なKotlinやAndroidの使用経験

 

このデモでは、Fire OSアプリ開発におけるAmplifyの活用方法を紹介することが目的です。本番環境用のアプリではないため、セキュリティのベストプラクティスは含まれていません。

Fire TV開発者にとってAmplifyが革新的である理由

Fire TVアプリの開発には独自の課題があります。D-PadのナビゲーションTV向けに最適化されたレイアウトオーディオフォーカスの制御などです。これらの課題にバックエンドのインフラ構築が加わると、UIが完成する前に開発の勢いは容易に失速してしまいます。

Amplifyは問題解決の糸口になります。Amplifyにより、定型処理のコード作成を省略して、アプリの開発に集中できます。(英語のみ)

  • 認証 Amazon AWS Cognitoを使用して安全なログインフローを追加します。
  • ストレージ Amazon S3を使用してアセットを保存および取得します。
  • データとAPI Amplify Dataは、AppSyncとGraphQLを使用してアプリケーションデータを永続化します。

このガイドで作成するアプリでは、上記3つの機能を中心に説明します。これ以外にも、Amplifyはサーバーレス関数を提供しており、インフラを管理することなく、バックグラウンド処理用のLambda関数を追加できます。

つまり、クラウドサービスの構築に時間をかけずに、サーバーレスアプリの開発についても学習するため、Fire TV体験の構築に注力できるようになります。

手順1: Amplifyのバックエンドをセットアップする

まずはじめに、アプリの認証、ストレージ、データ処理を担うAmplifyのバックエンドのセットアップをします。

十分な権限を持つIAMユーザーの作成

Amplify CLIを使用してリソースをプロビジョニングするには、十分な権限を持つIAM Identity Centerの認証情報が必要です。まずAWSコンソールにログインし、IAM Identity Centerで新しいユーザーを作成します。

Amazon Appstore Android developer's guide to React Native blog image

ユーザーを作成すると、新規ユーザーのアクセスポータルのURL、ユーザー名、ワンタイムパスワードが表示されます。

Amazon Appstore Android developer's guide to React Native blog image

こちらのページの残りの手順に従って、ユーザーの設定を完了します。また、コマンドラインからAmplify関連の呼び出しを実行する際に使用するローカルAWSプロファイルも設定します。本ガイドのこれ以降の説明では、amplify-app-developerという名前のローカルAWSプロファイルが存在し、このプロファイルにarn:aws:iam::aws:policy/service-role/AmplifyBackendDeployFullAccessパーミッションが付与されていることを前提としています。

Amplifyプロジェクトの初期化

projectフォルダから、次のコマンドを実行します。

Copied to clipboard
~/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.

これにより、amplifyという名前のサブフォルダが作成されます。

 

Amplifyサンドボックス環境の設定

作成済みのAWSプロファイルを使用してAmplifyサンドボックスを作成します。これにより、開発に使用できるリソース(ストレージ用のS3、データ管理用のマネージド型GraphQL、認証用のCognitoなど)がプロビジョニングされます。プロセスが進行すると、amplifyサブフォルダ内のファイルの変更が検出され、それに応じてサンドボックスが更新されます。

Copied to clipboard
$ 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の環境設定の手順が表示されます。表示される手順をクリックしながら進めて、設定を完了します。

 

Amazon Appstore Android developer's guide to React Native blog image
Amazon Appstore Android developer's guide to React Native blog image

最終的に、このブートストラッププロセスでは、AWS CloudFormationを使用して、AWS Cloud Development Kitで使用されるリソースをプロビジョニングします。CloudFormationスタックは、S3、ECR、IAMの各リソースを操作します。

 

Amazon Appstore Android developer's guide to React Native blog image

ブートストラップが完了したら、もう一度sandboxコマンドを実行します。

Copied to clipboard
$ 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サンドボックスプロセスは、このターミナルで実行したままにしておくことができます。

手順2: Fire TV用のAndroidプロジェクトをセットアップする

Amplifyの設定が完了したので、Androidプロジェクトを作成しましょう。

 

Android Studioでの新規プロジェクトの作成

Android Studioを開きます(インストールの手順)。[File] > [New] > [New Project] の順にクリックします。

 

[Television] テンプレートで [Empty Activity] を選択します。

Amazon Appstore Android developer's guide to React Native blog image

[Package name] を指定します(com.example.amplifyfiretvなど)。[Save Location] をプロジェクトのルートフォルダに設定します。使用する最小バージョンのSDKを選択します。この例では、API 29を使用します。

Amazon Appstore Android developer's guide to React Native blog image

これにより、新規プロジェクトが初期化され、多くの新規ファイルが作成されます。Android Studioでプロジェクトを初めて開くと、依存関係のダウンロードに数分かかります。

Amazon Appstore Android developer's guide to React Native blog image

エミュレーターで使用するAndroid TV仮想デバイスの作成

アプリをテストするために、Androidエミュレーターを使用します。Android TVエミュレーターとFire TV対応アプリについて詳しくは、こちらのページを参照してください。

 

Android Studioで、[Device Manager] に移動します。[Create Virtual Device] をクリックします。[New Hardware Profile] をクリックします。以下の設定でプロファイルを作成します。

 

  • デバイスタイプ: Android TV
  • 画面サイズ: 40インチ
  • 解像度: 1280 x 720 px
  • ナビゲーションスタイル: D-Pad
  • サポート対象デバイスの状態: 横向きのみ
  • カメラとセンサー: すべてのチェックボックスをオフ

 

仮想デバイスの名前を指定します。この例では、「Fire TV Emulator」という名前を指定していますが、任意の名前を付けることができます。

Amazon Appstore Android developer's guide to React Native blog image

[Finish] をクリックします。次に、新規作成したプロファイルを選択して [Next] をクリックします。APIレベルが23以上のシステムイメージを選択します。このガイドでは、Q(29)を使用します。イメージをダウンロードし、この仮想デバイス用に選択します。

 

Amazon Appstore Android developer's guide to React Native blog image

仮想デバイスの作成を終了します。次に、エミュレーターを起動します。

Amazon Appstore Android developer's guide to React Native blog image

セットアップした最初のプロジェクトを使用して、エミュレーターでテストすることができます。

Amazon Appstore Android developer's guide to React Native blog image

Leanbackライブラリの使用

Android StudioのAndroid TVアプリ用の初期設定では、Android TV向けJetpack Composeが使用されます。Jetpack Composeは、Android StudioでUIを作成するために使用されるデフォルトのツールキットです。ただし、Fire TV対応アプリでは、AndroidのLeanbackライブラリを使用します。これは、Fire TVがLeanbackのUIコンポーネントや機能を強力にサポートしているためです。そこで、app/build.gradle.ktsファイルを次のように変更します。

Copied to clipboard
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」というシンプルなテキストを表示するように変更します。

Copied to clipboard
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のカードデータは、CardData.ktというファイルで定義されている、CardDataProviderによってリアルタイムで読み込まれます。

Copied to clipboard
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に記述されています。

エミュレーターでテストすると、現在、アプリは次のように表示されます。

AWS Amplify Screenshot
AWS Amplify Screenshot

手順3: プロジェクトをAmplifyに接続する

Amplifyのセットアップは完了していますが、Amplifyをプロジェクトに追加する必要があります。

 

まず、Amplifyの依存関係をapp/build.gradle.ktsに追加します。

Copied to clipboard
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という新しいファイルを作成します。内容は次のとおりです。

Copied to clipboard
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, "Amplifyを初期化する際のエラー", error)
        } catch (error: Exception) {
            Log.e(TAG, "初期化中の予期しないエラー", error)
        }
    }
} 

次に、app/src/main/AndroidManifest.xmlを修正し、AndroidでApplicationクラスとしてAmplifyAppクラスを使用するように設定します。

Copied to clipboard
    <application
        android:name=".AmplifyApp"

手順4: Amplifyを使用してS3のストレージを追加する

Amplifyを使用する利点の1つは、AWS S3にアセットを保存して取得できることです。これを確認するために、cards.jsonファイルをS3バケットに移動してみます。これにより、アプリはコンパイル時にローカルファイルを参照する代わりに、S3からリアルタイムでカードデータを取得するようになります。

 

Amplifyを使用してAndroidプロジェクトでストレージをセットアップする方法については、こちらを参照してください。

Amplifyのストレージの定義

amplify/storage/resource.ts:を作成します。

Copied to clipboard
import { defineStorage } from '@aws-amplify/backend';

export const storage = defineStorage({
  name: 'my-bucket',
  access: (allow) => ({
    'data/*': [
      allow.guest.to(['get']),
      allow.authenticated.to(['get'])
    ]
  })
});

cards.jsonファイルをS3バケットのdataサブフォルダにアップロードします。このファイルへのアクセスは、認証されていないゲストユーザーと認証済みユーザーの両方に許可されます。

Amplifyの認証と認可の定義

このチュートリアルでは、Amplifyの認証(Cognitoを使用)も追加します。この認証はストレージを使用するのに必要です。

 

amplify/auth/resource.tsの内容は次のようになります。

Copied to clipboard
import { defineAuth } from '@aws-amplify/backend';

export const auth = defineAuth({
  loginWith: {
    email: true,
  },
});

Amplifyのバックエンドの定義

次に、amplify/backend.ts:を作成し、独自の認証とストレージの定義を使用することを指定します。

Copied to clipboard
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { storage } from './storage/resource';

defineBackend({
  auth,
  storage,
});

これらのファイルを作成すると、実行中のAmplifyサンドボックスプロセスにより、AWSリソースが更新され、認証とストレージのニーズが処理されます。

アプリのamplify_outputs.jsonファイルのパス指定

Amplifyサンドボックスプロセスはクライアント構成ファイル(amplify_outputs.json)を生成します。このファイルは、Amplifyが作成するさまざまなリソースにアプリが接続する際に使用されます。このファイルは、実行中のプロセスが、これまでの手順で行ったようなAmplifyの構成の変更を検出したときに自動的に更新されます。

 

デフォルトでは、amplify_outputs.jsonはAmplifyサンドボックスプロセスが実行されているディレクトリに直接書き込まれます。ただし、Fire TV対応アプリでは、このファイルがapp/src/main/res/raw/amplify_outputs.jsonに保存されている必要があります。このファイルを今後保存する場所のパスを明示的に指定して、Amplifyサンドボックスプロセスを再起動します。

Copied to clipboard
$ 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 S3バケットへのcards.jsonのアップロード

前の手順で生成されたamplify_outputs.jsonファイルを見ると、Amplifyプロジェクト用に作成されたS3バケットの名前を確認できます。

Copied to clipboard
 "storage": {
    "aws_region": "us-east-1",
    "bucket_name": "amplify-project-firedev-sandb-mybucket-ovpsejk2htya",
    ...

AWSコンソールで、このS3バケットに移動して、dataというサブフォルダを作成し、このフォルダにcards.jsonをアップロードします。これにより、このファイルのS3 URIは次のようになります。

Copied to clipboard
s3://amplify-project-firedev-sandb-mybucket-ovpsejk2htya/data/cards.json

CardDataProviderを修正してS3からcards.jsonを取得

次に、CardData.kt内のコードを修正します。このコードは、ローカルにあるcards.jsonから読み込むのではなく、amplify_outputs.jsonに記載されているS3バケット名を参照し、Amplifyリソースを呼び出してリモートファイルをダウンロードした後、ローカルキャッシュに保存します。initialize関数の主要なコード行は次のとおりです。

Copied to clipboard
// ダウンロード用のローカルファイルを設定します
val localFile = File(cacheDir, FILE_NAME)

// 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
                
// S3パスを設定 - パーミッションに合わせてdata/プレフィックスを使用します
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()を呼び出すように修正します。

Copied to clipboard
CardDataProvider.setOnCardsLoadedListener {
  updateContent()
}

最後に、AmplifyApp.ktのコードを、CardDataProvider、ストレージ用のS3、認証用のCognitoを初期化するように更新します。

Copied to clipboard
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へのアップロードが完了したので、再びアプリをビルドして実行し、エミュレーターでテストします。

AWS Amplify Screenshot

カードの画像表示とビデオの再生は期待どおりに機能しています。Amplifyを使用したS3によるストレージの統合が正常に完了しました。

 

コードリポジトリを使用して作業を進めている場合、現在、 Retrieves card data via remote file with Amplify and S3というタイトルのコミットの状態です。

手順5: ログインフローを実装する

AmplifyとS3を利用してストレージを使用するために、既に認証サービスを作成してデプロイしました。ここでは、ユーザーのサインインを処理し、セッション情報を保存する認証フローを作成します。

 

Amplifyを使用してAndroidプロジェクトで認証をセットアップする方法について詳しくは、こちらを参照してください。

Cognitoユーザープールでのテストユーザーの作成

テストするための準備として、Cognitoユーザープールでユーザーを作成します。amplify_outputs.jsonを見ると、user_pool_idを確認できます。たとえば次のようになります。

Copied to clipboard
"auth": {
    "user_pool_id": "us-east-1_8Riey238F",
    "aws_region": "us-east-1",
    …

AWSコンソールで、Cognitoに移動し、このIDのユーザープールを検索します。該当するユーザープール内で、[User management] -> [Users] の順に選択します。数人の新規ユーザーを作成します。このガイドでは、2人のユーザーを作成しています。

Eメールアドレス パスワード
joe@example.com My password is 1 number.
jen@example.com My password has 0 numbers.

 

これらのユーザーを作成すると、その確認ステータスが「Force change password」になっていることがわかります。 テストユーザーとして使用するだけであるため、上記のパスワードでユーザーアカウントを確認します。この作業はAWS CLIで実行できます。たとえば次のようになります。

Copied to clipboard
$ aws cognito-idp admin-set-user-password \
    --profile amplify-app-developer \
    --user-pool-id us-east-1_8Riey238F \
    --username jen@example.com \
    --password "パスワードに数字が含まれていません。" \
    --permanent

これで、2人のテストユーザーが確認されました。

AWS Amplify Screenshot

メイン画面へのサインインステータスの追加

ユーザー向けに、メイン画面にシンプルサインインのステータス表示を追加します。Amplify.Auth.fetchAuthSessionを呼び出してユーザーがサインインしているかどうかを確認するcheckAuthSession()関数を作成します。ユーザーがサインインしていない場合は、[Sign In] が表示されます。ユーザーがサインインしている場合は、サインインしているユーザーのEメールアドレスと [Sign Out] が表示されます。

Copied to clipboard
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)
      }
    }
  )
}

エミュレーターでは、ステータスに応じて、次のように異なるページが表示されます。

AWS Amplify Screenshot
AWS Amplify Screenshot

サインイン画面の追加

次に、SignInActivity.ktを作成します。これは、サインインのアクティビティ画面を定義します。app/src/main/res/layout/activity_sign_in.xmlで、Eメールアドレスとパスワードのフィールドを含むレイアウトを定義します。この画面は、フォームの送信時にAmplify.Auth.SignInを呼び出します。これに対応するためのAndroid組み込みの接続コンポーネントについては、こちらを参照してください。

AWS Amplify Screenshot

このアクティビティ画面は、必ずAndroidManifest.xml:に追加してください。

Copied to clipboard
<activity
  android:name=".SignInActivity"
  android:exported="false"
  android:theme="@style/Theme.AppCompat.NoActionBar" />

アプリのテスト

アプリをビルドして実行し、エミュレーターでテストします。認証機能の準備は完了しているため、Cognitoのユーザープールに登録した2人のテストユーザーの認証情報を使用してサインインとサインアウトを実行できます。

手順6: サインイン済みユーザーのお気に入りのビデオを保存する

ここでは、Amplifyを使用してサインイン済みユーザーのデータを保存・取得する方法を紹介します。そのために、サインイン済みユーザーに特定のビデオをお気に入りとしてマークしたり、マークを解除したりするよう指示します。 ユーザーがサインインすると、アプリはユーザーが選択したビデオを表示します。

 

ユーザー設定のデータはGraphQLデータベースに保存され、アプリはAmplify Data(英語のみ)を介してこのデータにアクセスします(内部ではAppSyncが使用されます)。

Amplifyのデータの定義

amplify/data/resource.tsを作成してAmplify Dataの使用を初期化し、データスキーマを定義(英語のみ)します。

 

Copied to clipboard
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のバックエンドの定義に追加します。

Copied to clipboard
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を追加(英語のみ)します。

Copied to clipboard
...
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 Dataが利用できるようになります。AWSコンソールでAWS AppSyncサービスを確認すると、アプリ用にセットアップされたAPIがスキーマと共に表示されます。

AWS Amplify Screenshot

アプリのamplify_outputs.jsonファイルの確認

Amplifyアプリの構成を更新したため、構成ファイル(amplify_outputs.json)は自動的に更新されます。更新済みのファイルには、Amplify Dataを利用するための詳細情報が記述されていることがわかります。

Copied to clipboard
…
"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クライアントコードのセットアップ

次に、GraphQLクライアントコードを生成します。アプリはこのコードを使用することで、Amplifyを通じてデータバックエンドに送信するリクエストを簡単に作成できます。

Copied to clipboard
~project/$ npx ampx \
           --profile amplify-app-developer generate graphql-client-code \
           --format modelgen \
           --model-target java \
           --out app/src/main/java/

上記のコマンドは、アプリがビルドして利用する3つのJavaファイルを生成します。

Copied to clipboard
~/project/app/src/main/java/com$ tree amplifyframework

amplifyframework/
└── datastore
    └── generated
        └── model
            ├── AmplifyModelProvider.java
            ├── Favorite.java
            └── FavoritePath.java

画面の更新とお気に入りのステータスの表示

DetailsSupportFragment(英語のみ)を使用するように画面を修正します。これにより、一覧でビデオカードを選択すると、VideoDetailsFragment.ktで定義されている詳細ビューが表示されます。ビデオを再生する代わりに、選択可能な2つのアクションとして、ビデオの再生(三角形)とビデオのお気に入り登録/登録解除(ハート)のアイコンが表示されます。

AWS Amplify Screenshot

次に、MainActivity.ktでカードの一覧ビューを更新して、お気に入りのステータスが表示されるように設定します。

AWS Amplify Screenshot

また、ユーザーがサインインしている場合にのみ、各カードのお気に入りのステータスが表示されるようにチェックを追加できます。

AWS Amplify Screenshot

Amplify.API.queryの実装によるお気に入りのステータスの取得

ビデオがサインイン済みユーザーのお気に入りに登録されているかどうかを確認するには、Amplify.API.queryを使用します。リストアイテム(英語のみ)の呼び出しは簡単で、生成済みのFavoriteモデル用のGraphQLクライアントコードを利用できます。VideoCardPresenter.ktのコードは次のようになります。

Copied to clipboard
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)
    }
  )
}

Amplify.API.mutateの実装によるお気に入りのステータスの切り替え

次に、VideoDetailsFragment.ktで、ハート形アイコンのクリック時にお気に入りのステータスを切り替える機能を実装します。ビデオをお気に入りとして設定するには、バックエンドデータにお気に入りを作成(英語のみ)した後、アイコンの表示を更新します。

Copied to clipboard
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.message}", error)
  }
)

ビデオのお気に入り設定を解除するには、レコードのid を取得し、deleteミューテーション(英語のみ)を実行します。

Copied to clipboard
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
           )
  }
)

アプリのテスト

すべてのコードの準備ができたので、アプリをビルドして、エミュレーターでテストしてみましょう。個々のビデオをお気に入りとして登録したり、登録解除したりすることができます。

AWS Amplify Screenshot

サインアウトして再びサインインすると、ユーザーのお気に入りが表示され、バックエンドデータが永続化されていることがわかります。

AWS Amplify Screenshot

これでアプリは完成です。

まとめ

1時間足らずで、Amplifyを利用して以下の処理を行うFire TV対応アプリを作成できました。

 

  • Cognitoを使用してユーザーを認証する
  • S3から動的にコンテンツを表示する
  • AppSyncとGraphQLを使用してユーザー設定を保存する

 

Amplifyに複雑な処理を任せることで、開発者はFire TVで快適に使用できるアプリの作成に集中できます。

 

Fire TV対応アプリの開発が初めての場合でも、短期間で試作品を作成する場合でも、Amplifyには適切なツールが用意されており、基礎を押さえながら迅速に開発を進めることができます。ここからさらにAmplifyを活用して、パーソナライズされたおすすめ機能、分析機能、プッシュ通知などを追加し、アプリの機能を充実させることができます。

関連記事

ニュースレターを購読してみませんか?

最新のAmazon開発者向けニュース、業界の動向、ブログの記事をお届けします。