AWS の開始方法

Android アプリケーションをビルドする

AWS Amplify を使用してシンプルな Android アプリケーションを作成する

モジュール 4: GraphQL API とデータベースを追加する

このモジュールでは、Amplify CLI とライブラリを使用して、アプリに GraphQL API を設定して追加します。

はじめに

ユーザー認証搭載のアプリケーションを作成、設定できました。API および CRUD オペレーション (作成、読み取り、更新、削除) をデータベースに追加してみます。

このモジュールでは、Amplify CLI とライブラリを使用してアプリケーションに API を追加します。作成する API は GraphQL API で、Amazon DynamoDB (NoSQL データベース) によってサポートされる AWS AppSync (マネージド GraphQL サービス) を利用します。GraphQL の概要については、このページにアクセスしてください。

作成するアプリケーションは、ユーザーがメモを作成、削除、一覧表示できるようにするメモアプリです。この例から、人気のあるさまざまな CRUD + L (作成、読み取り、更新、削除、一覧表示) アプリケーションをビルドする方法がよくわかります。

学習内容

  • GraphQL API を作成してデプロイする
  • API と対話するためのフロントエンドコードを記述する

主要な概念

API – 複数のソフトウェア仲介者間の通信および相互作用を可能にするプログラミングインターフェイスを提供します。

GraphQL – アプリケーションの型付き表現に基づくクエリ言語とサーバー側 API の実装。この API 表現は、GraphQL 型システムに基づくスキーマを使用して宣言されます。(GraphQL の詳細については、このページにアクセスしてください)。

 所要時間

20 分

 使用するサービス

実装

  • GraphQL API サービスおよびデータベースを作成する

    GraphQL API および GraphQL API がサポートするデータベースを作成するには、ターミナルを開き、プロジェクトディレクトリからこのコマンドを実行します。

    amplify add api
    
    ? Please select from one of the below mentioned services: select GraphQL and press enter
    ? Provide API name: select the default, press enter
    ? Choose the default authorization type for the API: use the arrow key to select Amazon Cognito User Pool and press enter
    ? Do you want to configure advanced settings for the GraphQL API: select the default No, I am done and press enter
    ? Do you have an annotated GraphQL schema? keep the default N and press enter
    ? What best describes your project: choose any model, we are going to replace it with our own anyway. Press enter
    ? Do you want to edit the schema now? type Y and press enter

    プロジェクト (amplify init) を初期化した際に選択したデフォルトのテキストエディタによって、事前ビルド済みのデータスキーマが開かれます。

    そのスキーマを削除して、アプリケーションの GraphQL スキーマと置換します。

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

    データモデルは 1 つの NoteData クラスおよび 4 つのプロパティ (文字列の ID と名前は必須。説明と画像は任意) で構成されます。

    データベースを作成して、データを保存する目的であることを @model トランスフォーマーが示しています。

    @auth トランスフォーマーによって、このデータへのアクセスを許可する認証ルールが追加されます。このプロジェクトでは、NoteData の所有者だけがアクセスできるようにします。

    完了したら必ず保存し、ターミナルに戻って、完了したことを Amplify CLI に伝達します。

    ? Press enter to continue, press enter.

    数秒経過したら、成功メッセージが表示されます。

    GraphQL schema compiled successfully.
  • クライアントサイドコードを生成する

    作成した GraphQL データモデル定義に基づき、Amplify によってクライアントサイドコード (すなわち Swift コード) が生成され、アプリケーション内のデータが記述されます。

    スクリプトを実行するには、ターミナルで次のコマンドを実行します。

    amplify codegen models

    この結果、ご覧のとおり、java/com/amplifyframework.datastore.generated.model ディレクトリに Java ファイルが作成されます。

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

    このファイルが自動的にプロジェクトにインポートされます。

  • API サービスおよびデータベースをデプロイする

    作成したバックエンド API およびデータベースをデプロイするには、ターミナルでコマンドを実行します。

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

    数分後、成功メッセージが表示されます。

    ✔ All resources are updated in the cloud
    
    GraphQL endpoint: https://yourid.appsync-api.eu-central-1.amazonaws.com/graphql
  • API クライアントライブラリを Android Studio プロジェクトを追加する

    コードに進む前に、Android Studio に戻って、下記の依存関係をモジュールの build.gradle および以前に追加したさまざまな amplifyframework 運用に追加して、[Sync Now] が表示されたらクリックします。

    dependencies {
        implementation 'com.amplifyframework:aws-api:1.4.0'
        implementation 'com.amplifyframework:aws-auth-cognito:1.4.0'
    }
  • 実行時に Amplify ライブラリを初期化する

    Backend.kt を開き、initialize() メソッドの Amplify 初期化シーケンスに 1 行を追加します。try/catch ブロックが完成すると、下記のようになります。

    try {
        Amplify.addPlugin(AWSCognitoAuthPlugin())
        Amplify.addPlugin(AWSApiPlugin())
        Amplify.configure(applicationContext)
    
        Log.i(TAG, "Initialized Amplify")
    } catch (e: AmplifyException) {
        Log.e(TAG, "Could not initialize Amplify", e)
    }
  • GraphQL データモデルとアプリケーションモデル間のブリッジングを追加する

    プロジェクトには、メモを表すデータモデルがすでにあります。そのため、引き続きこのモデルを使うことにして、NoteData をメモに簡単に変換します。UserData.kt を開き、2 つのコンポーネント、つまり UserData.Note から NoteData オブジェクトを返す動的プロパティと、その反対に API NoteData を受け取り Userdata.Note を返す静的メソッドを追加します。

    データクラスの Note 内に、下記を追加します。

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

    必ず生成されたコードから NoteData クラスをインポートするようにします。

  • API CRUD メソッドをバックエンドクラスに追加する

    API を呼び出す 3 つのメソッド、つまり Note をクエリするメソッド、新しい Note を作成するメソッド、Note を削除するメソッドを追加します。ユーザーインターフェイスから 3 つのメソッドと簡単にインタラクションできるように、アプリケーションデータモデル (メモ) 上で動作するようになっています。これらのメソッドは、透過的にメモを GraphQL の NoteData オブジェクトに変換します。

    Backend.kt ファイルを開き、次のスニペットをバックエンドクラスの最後に追加します

    fun queryNotes() {
        Log.i(TAG, "Querying notes")
    
        Amplify.API.query(
            ModelQuery.list(NoteData::class.java),
            { response ->
                Log.i(TAG, "Queried")
                for (noteData in response.data) {
                    Log.i(TAG, noteData.name)
                    // TODO should add all the notes at once instead of one by one (each add triggers a UI refresh)
                    UserData.addNote(UserData.Note.from(noteData))
                }
            },
            { error -> Log.e(TAG, "Query failure", error) }
        )
    }
    
    fun createNote(note : UserData.Note) {
        Log.i(TAG, "Creating notes")
    
        Amplify.API.mutate(
            ModelMutation.create(note.data),
            { response ->
                Log.i(TAG, "Created")
                if (response.hasErrors()) {
                    Log.e(TAG, response.errors.first().message)
                } else {
                    Log.i(TAG, "Created Note with id: " + response.data.id)
                }
            },
            { error -> Log.e(TAG, "Create failed", error) }
        )
    }
    
    fun deleteNote(note : UserData.Note?) {
    
        if (note == null) return
    
        Log.i(TAG, "Deleting note $note")
    
        Amplify.API.mutate(
            ModelMutation.delete(note.data),
            { response ->
                Log.i(TAG, "Deleted")
                if (response.hasErrors()) {
                    Log.e(TAG, response.errors.first().message)
                } else {
                    Log.i(TAG, "Deleted Note $response")
                }
            },
            { error -> Log.e(TAG, "Delete failed", error) }
        )
    }

    必ず生成されたコードから ModelQuery、ModelMutation、NoteData クラスをインポートするようにします。

    最後に、このアプリケーションの起動時に、この API を呼び出して、サインインしているユーザーのメモのリストをクエリする必要があります。

    Backend.ktfile で、updateUserData(withSignInStatus: Boolean) を下記のように更新します。

    // change our internal state and query list of notes 
    private fun updateUserData(withSignedInStatus : Boolean) {
        UserData.setSignedIn(withSignedInStatus)
    
        val notes = UserData.notes().value
        val isEmpty = notes?.isEmpty() ?: false
    
        // query notes when signed in and we do not have Notes yet
        if (withSignedInStatus && isEmpty ) {
            this.queryNotes()
        } else {
            UserData.resetNotes()
        }
    }

    後は新しい Note の作成とリストから Note の削除を行うためのユーザーインターフェイス部分を作るだけです。

  • 編集ボタンを追加して、メモを追加する

    バックエンドおよびデータモデル部分が所定の位置に収まりました。このセクションの最後のステップとしてはユーザーが新しい Note を作成、削除できるようにします。

    a.Android Studio で、res/layout 下に新しいレイアウトを作成します。[レイアウト] を右クリックして [New (新規)] を選択し、次に [Layout Resource File (レイアウトリソースファイル)] を選択します。名前を「activity_add_note」にしてその他はすべてデフォルトの値にします。[OK] をクリックします。

    AndroidAppTutorial_Modiule4_Image1

    今作成した「activity_add_note」ファイルを開き、以下のコードを貼り付けて、生成されたコードを置換します。

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

    メモの見出しと説明が入力できる非常にシンプルなレイアウトです。

    b.AddNoteActivity クラスを追加します。

    java/com.example.androidgettingstarted に新しい kotlin ファイル AddActivityNote.kt を作成してそれを開き、次のコードを追加します。 

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

    最後に、manifests で AndroidManifest.xml を開き、アプリケーションノード内の任意の場所にこのアクティビティ要素を追加します。

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

    c.メインアクティビティで「メモを追加」の FloatingActionButton を追加します。res/layout で、activity_main.xml を開いて既存の Floating Action Button の上にこれを追加します。

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

    res/drawable に「メモを追加」アイコンを追加します。[drawable] を右クリックし、[New (新規)] を選択し、次に [Vector Asset (ベクターアセット)] を選択します。名前として「 ic_baseline_add」と入力し、Clip Art から追加アイコンを選択します。[Next (次へ)]、[Finish (終了)] の順にクリックします。

    AndroidAppTutorial_Modiule4_Image2

    d.「メモを追加」ボタンをハンドリングするためのコードを追加します。

    完全な機能を持つ「[追加] ボタン」にするための最後の 2 つの部分は、isSignedIn 値に従ってボタンを表示、非表示させることと、言うまでもありませんがボタンへのタップをハンドリングするコードを追加することです。

    mainActivity.kt を開き onCreate() メソッドの末尾に次のコードを追加します。

    // register a click listener
    fabAdd.setOnClickListener {
        startActivity(Intent(this, AddNoteActivity::class.java))
    }

    その後、同じ onCreate() メソッド内で、UserData.isSignedIn.observe をこのコードで置換します。

    UserData.isSignedIn.observe(this, Observer<Boolean> { isSignedUp ->
        // update UI
        Log.i(TAG, "isSignedIn changed : $isSignedUp")
    
        //animation inspired by https://www.11zon.com/zon/android/multiple-floating-action-button-android.php
        if (isSignedUp) {
            fabAuth.setImageResource(R.drawable.ic_baseline_lock_open)
            Log.d(TAG, "Showing fabADD")
            fabAdd.show()
            fabAdd.animate().translationY(0.0F - 1.1F * fabAuth.customSize)
        } else {
            fabAuth.setImageResource(R.drawable.ic_baseline_lock)
            Log.d(TAG, "Hiding fabADD")
            fabAdd.hide()
            fabAdd.animate().translationY(0.0F)
        }
    })    

    すべてが想定どおりに動作することを検証するには、プロジェクトをビルドします。[Build (構築)] メニューをクリックして、[Make Project (プロジェクトの作成)] を選択します。Mac の場合は、⌘F9 を押します。操作エラーがないようにします。

    アプリケーションを実行すると、「メモを追加」ボタンはユーザーがサインインすると表示され、ユーザーがサインアウトすると消えます。これで、メモを追加できるようになりました。

  • 「スワイプして削除」の動作を追加する

    「スワイプして削除」の動作はメモのリストにタッチハンドラーを追加することで実装できます。タッチハンドラーは赤の背景、削除アイコンを描画する部分を管理しており、タッチが発生すると Backend.delete() メソッドを呼び出します。

    a.新しいクラスの SimpleTouchCallback を作成します。java/com の直下で、[example.androidgettingstarted] を右クリックし、[New (新規)]、[Kotlin File (Kotlin ファイル)] の順に選択して、名前として「SwipeCallback」と入力します。

    AndroidAppTutorial_Modiule4_Image3

    新しいファイルに下記のコードをペーストします。

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

    コードの重要な行は、onSwiped() メソッド内にあります。このメソッドはスワイプのジェスチャが終わると呼び出されます。スワイプされたアイテムのリストで配置を収集し、UserData の構造 (これが UI を更新) とクラウドのバックエンドから対応するメモを排除します。

    b.クラスができたので res/drawable に [Delete (削除)] アイコンを追加しましょう。[drawable] を右クリックし、[New (新規)] を選択し、次に [Vector Asset (ベクターアセット)] を選択します。名前として「ic_baseline_delete_sweep」と入力し、Clip Art から「delete sweep」アイコンを選択します。[Next (次へ)]、[Finish (終了)] の順にクリックします。

    AndroidAppTutorial_Modiule4_Image4

    c.この新しいファイルに下記のコードをペーストします。「スワイプして削除」のジェスチャハンドラーを RecyclerView に追加します。

    java/com/example.androidgettingstarted の直下で、MainActivity.kt を開いて、2 行のコードを setupRecyclerView に追加します。

    // add a touch gesture handler to manager the swipe to delete gesture
    val itemTouchHelper = ItemTouchHelper(SwipeCallback(this))
    itemTouchHelper.attachToRecyclerView(recyclerView)
  • ビルドとテスト

    すべてが想定どおりに動作することを検証するには、プロジェクトをビルドして実行します。ツールバーの [Run (実行)] アイコン ▶️ をクリックするか、または「^ R」と入力します。操作エラーがないようにします。

    まだサインインしていると想定して、アプリケーションは空のリストから起動します。現在はメモを追加するための [Add Note (メモを追加)] ボタンがあります。[Add Note (メモを追加)] シンボルをタップし、タイトルと説明を入力し、[Add Note (メモを追加)] ボタンをタップすると、メモがリストに表示されます。

    メモは行を左にスワイプすることで削除できます。

    完全なフローは下記のとおりです。

    AndroidAppTutorial_Modiule4_Image5
    AndroidAppTutorial_Modiule4_Image6
    AndroidAppTutorial_Modiule4_Image7
    AndroidAppTutorial_Modiule4_Image8

まとめ

これで、Android アプリができました。 AWS Amplify を使用して、GraphQL API を追加し、作成、読み取り、削除機能をアプリケーションに設定しました。

次のモジュールでは、UI および動作を追加して、写真を管理します。

このモジュールは役に立ちましたか?

ありがとうございます
このチュートリアルで良かった点をお聞かせください。
閉じる
ご期待に添えず申し訳ありません
古い説明、わかりにくい説明、間違った説明はございませんでしたか? このチュートリアルの改善のために、ぜひフィードバックをお寄せください。
閉じる

ストレージを追加する