プロジェクト: Lunch Tray アプリ

1. 始める前に

この Codelab では、Lunch Tray というアプリをご自身でビルドします。また、Android Studio 内でのプロジェクトのセットアップやテストなど、Lunch Tray アプリ プロジェクトを完了するための手順についても説明します。

この Codelab は、このコースの他の Codelab とは異なります。アプリの作成手順を説明する、これまでの Codelab のようなチュートリアルとは異なり、この Codelab ではユーザーが単独で進められるプロジェクトをセットアップし、完成したアプリを自分で確認する方法を紹介します。

そのため、解答コードではなく、ダウンロードするアプリの一部としてテストスイートを提供します。これらのテストを Android Studio で実行して(その方法については、後ほどこの Codelab で説明します)、コードが合格するかを確認します。1 回のテストで完了するとは限りません。プロのデベロッパーでも、すべてのテストを 1 回で合格することはほとんどないと言っていいでしょう。このプロジェクトは、作成したコードがすべてのテストに合格した時点で完了です。

これについては、解答を確認するだけでよいのではないかと考える方がいるかもしれません。解答コードを提供しないのは、プロフェッショナルなデベロッパーの作業を体験していただく意図からです。そのため次のような、まだ習熟していないスキルが求められる場合があります。

  • 初めて目にする用語、エラー メッセージ、アプリ内のコードをオンライン検索
  • コードのテスト、エラーの読み取り、コードの変更と再テスト
  • Android の基本にあるコンテンツに戻り、学習した内容を復習
  • 動作することがわかっているコード(プロジェクトで提供されたコード、またはユニット 3 で他のアプリについて使用した解答コード)と、作成したコードを比較

最初は大変に思えるかもしれませんが、ユニット 3 を完了できていれば、このプロジェクトにも十分に対応できると確信しています。じっくりと時間をかけ、あきらめずに取り組んでください。必ずできるはずです。

前提条件

  • このプロジェクトは、Kotlin コースの「Android の基本」のユニット 3 を完了しているユーザーを対象としています。

作成するアプリの概要

  • Lunch Tray という料理注文アプリを作成し、データ バインディングによって ViewModel を実装して、フラグメント間のナビゲーションを追加します。

必要なもの

  • Android Studio がインストールされているパソコン

2. 完成したアプリの概要

プロジェクト: Lunch Tray へようこそ。

ご存じのとおり、ナビゲーションは Android 開発に欠かせない要素です。アプリを使用してレシピをブラウジングする、お気に入りのレストランへの行き方を調べる、または最も重要な機能として料理を注文する場合などに、複数のコンテンツの画面をナビゲートすると考えられます。このプロジェクトでは、ユニット 3 で学習したスキルを活用して、ビューモデル、データ バインディング、画面間のナビゲーションを実装し、Lunch Tray というランチ注文アプリを作成します。

最終的なアプリのスクリーンショットは以下のとおりです。Lunch Tray アプリを最初に起動すると、画面に「START ORDER」というボタンが 1 つ表示されます。

20fa769d4ba93ef3.png

[START ORDER] をクリックすると、注文可能な主菜が表示され、いずれか 1 つを選択できます。選択内容を変更した場合は、下部に表示される [Subtotal] が更新されます。

438b61180d690b3a.png

次の画面で、副菜を 1 つ追加できます。

768352680759d3e2.png

その後の画面で、注文する料理の付け合わせを 1 つ選択できます。

8ee2bf41e9844614.png

最後に、注文した料理ごとの金額、小計、売上税、合計金額が表示されます。注文を送信またはキャンセルすることもできます。

61c883c34d94b7f7.png

どちらを選択しても、ユーザーは最初の画面に戻ります。注文を送信した場合は、画面下部にトーストが表示され、注文が送信されたことを確認できます。

acb7d7a5d9843bac.png

3. 始める

プロジェクト コードをダウンロードする

フォルダ名は android-basics-kotlin-lunch-tray-app です。Android Studio でプロジェクトを開くときは、このフォルダを選択してください。

  1. プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
  2. ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。

1e4c0d2c081a8fd2.png

  1. プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。

1debcf330fd04c7b.png

  1. ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
  2. パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
  3. ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。

Android Studio でプロジェクトを開く

  1. Android Studio を起動します。
  2. [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。

d8e9dbdeafe9038a.png

注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。

8d1fda7396afe8e5.png

  1. ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
  2. そのプロジェクト フォルダをダブルクリックします。
  3. Android Studio でプロジェクトが開かれるまで待ちます。
  4. 実行ボタン 8de56cba7583251f.png をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。

ViewModel とナビゲーションの実装を開始する前に、プロジェクトを正常にビルドし、プロジェクトについて習熟するための時間を設けてください。初めてアプリを実行した際に、空の画面が表示されます。ナビゲーション グラフをまだ設定していないため、MainActivity ではフラグメントが提示されていません。

プロジェクトの構造は、これまでに取り組んだ他のプロジェクトと類似しているはずです。データ、モデル、UI の各パッケージと、リソース用の個別ディレクトリがあります。

a19fd8a4bc92f2fc.png

ユーザーが注文できるランチ オプション(主菜、副菜、付け合わせ)はすべて、model パッケージの MenuItem クラスで表されます。MenuItem オブジェクトには、名前、説明、価格、タイプがあります。

data class MenuItem(
    val name: String,
    val description: String,
    val price: Double,
    val type: Int
) {
    fun getFormattedPrice(): String = NumberFormat.getCurrencyInstance().format(price)
}

型は、constants パッケージの ItemType オブジェクトから得られる整数で表されます。

object ItemType {
    val ENTREE = 1
    val SIDE_DISH = 2
    val ACCOMPANIMENT = 3
}

個別の MenuItem オブジェクトは、データ パッケージの DataSource.kt にあります。

object DataSource {
    val menuItems = mapOf(
        "cauliflower" to
        MenuItem(
            name = "Cauliflower",
            description = "Whole cauliflower, brined, roasted, and deep fried",
            price = 7.00,
            type = ItemType.ENTREE
        ),
    ...
}

このオブジェクトには、キーとそれに対応する MenuItem で構成されるマップだけが含まれます。DataSource には、最初に実装する ObjectViewModel からアクセスします。

ViewModel を定義する

前のページのスクリーンショットに示したとおり、ユーザーは、主菜、副菜、付け合わせの 3 つを選ぶよう求められます。続いて、注文概要の画面に小計が表示され、選択した料理に基づいて売上税が計算されます。この価格は注文の合計の計算に使用されます。

model パッケージで OrderViewModel.kt を開くと、いくつかの変数がすでに定義されていることがわかります。menuItems プロパティを使用すると、簡単に ViewModel から DataSource にアクセスできます。

val menuItems = DataSource.menuItems

最初の段階で、previousEntreePricepreviousSidePricepreviousAccompanimentPrice の変数も用意されています。ユーザーが選択を行うと小計が更新される(最後に合計されるのではなく)ため、これらの変数はユーザーが次の画面に移動する前に選択内容を変更する場合に過去の選択内容を記録する目的で使用されます。これらを使用して、前に選択された料理と現在選択されている料理の価格の差が小計の金額に確実に反映されるようにします。

private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0

現在の選択内容を保存するための非公開変数 _entree_side_accompaniment もあります。これらは MutableLiveData<MenuItem?> 型です。それぞれに公開バッキング プロパティ entreesideaccompaniment が付属しています。これらは不変の型 LiveData<MenuItem?> です。これらはフラグメントのレイアウトによってアクセスされ、選択された料理が画面に表示されます。LiveData オブジェクトに含まれる MenuItem は、ユーザーが主菜、副菜、または付け合わせを選択しない可能性があるため、null 値許容にすることもできます。

// Entree for the order
private val _entree = MutableLiveData<MenuItem?>()
val entree: LiveData<MenuItem?> = _entree

// Side for the order
private val _side = MutableLiveData<MenuItem?>()
val side: LiveData<MenuItem?> = _side

// Accompaniment for the order.
private val _accompaniment = MutableLiveData<MenuItem?>()
val accompaniment: LiveData<MenuItem?> = _accompaniment

小計、合計、税金の LiveData 変数もあり、これらは数値形式を使用して通貨として表示されます。

// Subtotal for the order
private val _subtotal = MutableLiveData(0.0)
val subtotal: LiveData<String> = Transformations.map(_subtotal) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Total cost of the order
private val _total = MutableLiveData(0.0)
val total: LiveData<String> = Transformations.map(_total) {
    NumberFormat.getCurrencyInstance().format(it)
}

// Tax for the order
private val _tax = MutableLiveData(0.0)
val tax: LiveData<String> = Transformations.map(_tax) {
    NumberFormat.getCurrencyInstance().format(it)
}

最後に、税率はハードコードされた値である 0.08(8%)です。

private val taxRate = 0.08

OrderViewModel には、実装する必要のある 6 つのメソッドがあります。

setEntree()、setSide()、setAccompaniment()

すべてのメソッドは、主菜、副菜、付け合わせのそれぞれについて、同じように機能します。たとえば、setEntree() は次のことを行います。

  1. _entreenull でない場合(ユーザーが、選択済みの主菜を別のものに変更した場合)は、previousEntreePricecurrent _entree の価格に設定します。
  2. _subtotalnull でない場合は、小計から previousEntreePrice を引きます。
  3. _entree の値を、関数に渡された主菜に更新します(menuItems を使用して MenuItem にアクセスします)。
  4. updateSubtotal() を呼び出して、新しく選択した主菜の価格を渡します。

setSide()setAccompaniment() のロジックは、setEntree() の実装と同じです。

updateSubtotal()

updateSubtotal() は、小計に追加する新しい価格の引数で呼び出されます。このメソッドでは 3 つの処理を行う必要があります。

  1. _subtotalnull でない場合は、itemPrice_subtotal に追加します。
  2. または、_subtotalnull の場合は、_subtotalitemPrice に設定します。
  3. _subtotal を設定(または更新)したら、calculateTaxAndTotal() を呼び出して、新しい小計を反映するようにこれらの値を更新します。

calculateTaxAndTotal()

calculateTaxAndTotal() は、小計に基づいて税金と合計の変数を更新する必要があります。メソッドを次のように実装します。

  1. _tax には、税率に小計を乗じた値を設定します。
  2. _total には、小計に税金を加算した値を設定します。

resetOrder()

resetOrder() は、ユーザーが注文を送信またはキャンセルしたときに呼び出されます。ユーザーが新規の注文を開始したときに、アプリにデータが残っていないようにする必要があります。

OrderViewModel で変更したすべての変数を元の値(0.0 または null)に戻して resetOrder() を実装します。

データ バインディング変数を作成する

レイアウト ファイルにデータ バインディングを実装します。レイアウト ファイルを開いて、タイプ OrderViewModel または対応するフラグメント クラスのデータ バインディング変数、あるいはそれらの両方を追加します。

4 つのレイアウト ファイルにテキストとクリック リスナーを設定するには、すべての TODO コメントを実装する必要があります。

  1. fragment_entree_menu.xml
  2. fragment_side_menu.xml
  3. fragment_accompaniment_menu.xml
  4. fragment_checkout.xml

各タスクはレイアウト ファイルの TODO コメントに記載されていますが、大まかな手順は次のとおりです。

  1. fragment_entree_menu.xml<data> タグ内に、EntreeMenuFragment のバインディング変数を追加します。各ラジオボタンがオンにされたときに、ViewModel で主菜を設定する必要があります。小計のテキストビューのテキストは、それに応じて更新する必要があります。また、注文をキャンセルする、あるいは次の画面に進むには、それぞれ cancel_buttonnext_buttononClick 属性を設定する必要があります。
  2. fragment_side_menu.xml でも同じ操作を行い、SideMenuFragment のバインディング変数を追加します。ただし、ラジオボタンがオンにされるたびにビューモデルの副菜を設定する点が異なります。小計テキストも更新する必要があります。また、キャンセル ボタンと「次へ」ボタンに onClick 属性を設定する必要があります。
  3. fragment_accompaniment_menu.xml でも同じ操作を行いますが、今回は AccompanimentMenuFragment のバインディング変数を設定し、ラジオボタンがオンにされるたびに付け合わせを設定します。再度、小計テキスト、キャンセル ボタン、「次へ」ボタンについても属性を設定する必要があります。
  4. fragment_checkout.xml では、バインディング変数を定義するために、<data> タグを追加する必要があります。<data> タグ内に、2 つのバインディング変数(OrderViewModel 用と CheckoutFragment 用)を追加します。テキストビューでは、選択した主菜、副菜、付け合わせの名前と価格を、OrderViewModel から設定する必要があります。また、OrderViewModel の小計、税金、合計を設定する必要があります。次に、CheckoutFragment の適切な関数を使用して、注文を送信する場合とキャンセルする場合の onClickAttributes を設定します。

.

フラグメントでデータ バインディング変数を初期化する

メソッド onViewCreated() 内の対応するフラグメント ファイルで、データ バインディング変数を初期化します。

  1. EntreeMenuFragment
  2. SideMenuFragment
  3. AccompanimentMenuFragment
  4. CheckoutFragment

ナビゲーション グラフを作成する

ユニット 3 で学んだように、ナビゲーション グラフはアクティビティに含まれる FragmentContainerView でホストされています。activity_main.xml を開き、TODO を次のコードに置き換えて FragmentContainerView を宣言します。

<androidx.fragment.app.FragmentContainerView
   android:id="@+id/nav_host_fragment"
   android:name="androidx.navigation.fragment.NavHostFragment"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   app:defaultNavHost="true"
   app:navGraph="@navigation/mobile_navigation"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintLeft_toLeftOf="parent"
   app:layout_constraintRight_toRightOf="parent"
   app:layout_constraintTop_toTopOf="parent" />

ナビゲーション グラフ mobile_navigation.xml は、res.navigation パッケージ内にあります。

e3381215c35c1726.png

これは、このアプリのナビゲーション グラフです。ただし、ファイルは現在空です。ここでは、ナビゲーション グラフにデスティネーションを追加し、画面間の次のナビゲーションをモデル化します。

  1. StartOrderFragment から EntreeMenuFragment へのナビゲーション
  2. EntreeMenuFragment から SideMenuFragment へのナビゲーション
  3. SideMenuFragment から AccompanimentMenuFragment へのナビゲーション
  4. AccompanimentMenuFragment から CheckoutFragment へのナビゲーション
  5. CheckoutFragment から StartOrderFragment へのナビゲーション
  6. EntreeMenuFragment から StartOrderFragment へのナビゲーション
  7. SideMenuFragment から StartOrderFragment へのナビゲーション
  8. AccompanimentMenuFragment から StartOrderFragment へのナビゲーション
  9. 開始デスティネーションStartOrderFragment です。

ナビゲーション グラフを設定したら、フラグメント クラスでナビゲーションを行う必要があります。フラグメントの残りの TODO コメントと MainActivity.kt を実装します。

  1. EntreeMenuFragmentSideMenuFragmentAccompanimentMenuFragmentgoToNextScreen() メソッドについて、アプリの次の画面に移動します。
  2. EntreeMenuFragmentSideMenuFragmentAccompanimentMenuFragmentCheckoutFragmentcancelOrder() メソッドについては、最初に sharedViewModelresetOrder() を呼び出し、StartOrderFragment に移動してください。
  3. StartOrderFragment で、setOnClickListener() を実装して EntreeMenuFragment に移動します。
  4. CheckoutFragmentsubmitOrder() メソッドを実装します。sharedViewModelresetOrder() を呼び出してから、StartOrderFragment に移動します。
  5. 最後に、MainActivity.kt で、navControllerNavHostFragmentnavController に設定します。

4. アプリをテストする

Lunch Tray プロジェクトには、MenuContentTestsNavigationTestsOrderFunctionalityTests という複数のテストケース付きの「androidTest」ターゲットが含まれています。

テストを実行する

テストを実行するには、次のいずれかを行います。

テストケースが 1 つの場合は、テストケース クラスを開いて、クラス宣言の左側にある緑色の矢印をクリックします。メニューから [Run] オプションを選択します。これにより、テストケース内のすべてのテストが実行されます。

8ddcbafb8ec14f9b.png

多くの場合、1 つのテストを実行するだけで済みます。たとえば、失敗したテストが 1 つしかなく、他のテストには合格している場合などです。テストケース全体を実行するときと同じように、1 つのテストを実行できます。緑色の矢印をクリックして [Run] オプションを選択します。

335664b7fc8b4fb5.png

複数のテストケースがある場合は、テストスイート全体を実行することもできます。アプリを実行する場合と同様に、このオプションは [Run] メニューにあります。

80312efedf6e4dd3.png

Android Studio は、デフォルトでは最後に実行したターゲット(アプリやテスト ターゲットなど)を実行するため、メニューに [Run] > [Run 'app'] と表示されている場合は、[Run] > [Run] を選択してテスト ターゲットを実行できます。

95aacc8f749dee8e.png

ポップアップ メニューからテスト ターゲットを選択します。

8b702efbd4d21d3d.png

5. 省略可: フィードバックをお寄せください

このプロジェクトに関するご意見をお寄せいただけければ幸いです。こちらの簡単なアンケートにお答えください。お寄せいただいたフィードバックは、このコースに関する今後のプロジェクトの参考にさせていただきます。