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 つ表示されます。
[START ORDER] をクリックすると、注文可能な主菜が表示され、いずれか 1 つを選択できます。選択内容を変更した場合は、下部に表示される [Subtotal] が更新されます。
次の画面で、副菜を 1 つ追加できます。
その後の画面で、注文する料理の付け合わせを 1 つ選択できます。
最後に、注文した料理ごとの金額、小計、売上税、合計金額が表示されます。注文を送信またはキャンセルすることもできます。
どちらを選択しても、ユーザーは最初の画面に戻ります。注文を送信した場合は、画面下部にトーストが表示され、注文が送信されたことを確認できます。
3. 始める
プロジェクト コードをダウンロードする
フォルダ名は android-basics-kotlin-lunch-tray-app
です。Android Studio でプロジェクトを開くときは、このフォルダを選択してください。
- プロジェクト用に提供されている GitHub リポジトリ ページに移動します。
- ブランチ名が Codelab で指定されたブランチ名と一致していることを確認します。たとえば、次のスクリーンショットでは、ブランチ名は main です。
- プロジェクトの GitHub ページで、[Code] ボタンをクリックすると、ポップアップが表示されます。
- ポップアップで、[Download ZIP] をクリックして、プロジェクトをパソコンに保存します。ダウンロードが完了するまで待ちます。
- パソコンに保存したファイルを見つけます([ダウンロード] フォルダなど)。
- ZIP ファイルをダブルクリックして展開します。プロジェクト ファイルが入った新しいフォルダが作成されます。
Android Studio でプロジェクトを開く
- Android Studio を起動します。
- [Welcome to Android Studio] ウィンドウで、[Open] をクリックします。
注: Android Studio がすでに開いている場合は、メニューから [File] > [Open] を選択します。
- ファイル ブラウザで、展開したプロジェクト フォルダがある場所([ダウンロード] フォルダなど)に移動します。
- そのプロジェクト フォルダをダブルクリックします。
- Android Studio でプロジェクトが開かれるまで待ちます。
- 実行ボタン をクリックして、アプリをビルドし、実行します。期待どおりにビルドされることを確認します。
ViewModel
とナビゲーションの実装を開始する前に、プロジェクトを正常にビルドし、プロジェクトについて習熟するための時間を設けてください。初めてアプリを実行した際に、空の画面が表示されます。ナビゲーション グラフをまだ設定していないため、MainActivity
ではフラグメントが提示されていません。
プロジェクトの構造は、これまでに取り組んだ他のプロジェクトと類似しているはずです。データ、モデル、UI の各パッケージと、リソース用の個別ディレクトリがあります。
ユーザーが注文できるランチ オプション(主菜、副菜、付け合わせ)はすべて、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
最初の段階で、previousEntreePrice
、previousSidePrice
、previousAccompanimentPrice
の変数も用意されています。ユーザーが選択を行うと小計が更新される(最後に合計されるのではなく)ため、これらの変数はユーザーが次の画面に移動する前に選択内容を変更する場合に過去の選択内容を記録する目的で使用されます。これらを使用して、前に選択された料理と現在選択されている料理の価格の差が小計の金額に確実に反映されるようにします。
private var previousEntreePrice = 0.0
private var previousSidePrice = 0.0
private var previousAccompanimentPrice = 0.0
現在の選択内容を保存するための非公開変数 _entree
、_side
、_accompaniment
もあります。これらは MutableLiveData<MenuItem?>
型です。それぞれに公開バッキング プロパティ entree
、side
、accompaniment
が付属しています。これらは不変の型 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()
は次のことを行います。
_entree
がnull
でない場合(ユーザーが、選択済みの主菜を別のものに変更した場合)は、previousEntreePrice
をcurrent _entree
の価格に設定します。_subtotal
がnull
でない場合は、小計からpreviousEntreePrice
を引きます。_entree
の値を、関数に渡された主菜に更新します(menuItems
を使用してMenuItem
にアクセスします)。updateSubtotal()
を呼び出して、新しく選択した主菜の価格を渡します。
setSide()
と setAccompaniment()
のロジックは、setEntree()
の実装と同じです。
updateSubtotal()
updateSubtotal()
は、小計に追加する新しい価格の引数で呼び出されます。このメソッドでは 3 つの処理を行う必要があります。
_subtotal
がnull
でない場合は、itemPrice
を_subtotal
に追加します。- または、
_subtotal
がnull
の場合は、_subtotal
をitemPrice
に設定します。 _subtotal
を設定(または更新)したら、calculateTaxAndTotal()
を呼び出して、新しい小計を反映するようにこれらの値を更新します。
calculateTaxAndTotal()
calculateTaxAndTotal()
は、小計に基づいて税金と合計の変数を更新する必要があります。メソッドを次のように実装します。
_tax
には、税率に小計を乗じた値を設定します。_total
には、小計に税金を加算した値を設定します。
resetOrder()
resetOrder()
は、ユーザーが注文を送信またはキャンセルしたときに呼び出されます。ユーザーが新規の注文を開始したときに、アプリにデータが残っていないようにする必要があります。
OrderViewModel
で変更したすべての変数を元の値(0.0 または null)に戻して resetOrder()
を実装します。
データ バインディング変数を作成する
レイアウト ファイルにデータ バインディングを実装します。レイアウト ファイルを開いて、タイプ OrderViewModel
または対応するフラグメント クラスのデータ バインディング変数、あるいはそれらの両方を追加します。
4 つのレイアウト ファイルにテキストとクリック リスナーを設定するには、すべての TODO
コメントを実装する必要があります。
fragment_entree_menu.xml
fragment_side_menu.xml
fragment_accompaniment_menu.xml
fragment_checkout.xml
各タスクはレイアウト ファイルの TODO コメントに記載されていますが、大まかな手順は次のとおりです。
fragment_entree_menu.xml
の<data>
タグ内に、EntreeMenuFragment
のバインディング変数を追加します。各ラジオボタンがオンにされたときに、ViewModel
で主菜を設定する必要があります。小計のテキストビューのテキストは、それに応じて更新する必要があります。また、注文をキャンセルする、あるいは次の画面に進むには、それぞれcancel_button
とnext_button
のonClick
属性を設定する必要があります。fragment_side_menu.xml
でも同じ操作を行い、SideMenuFragment
のバインディング変数を追加します。ただし、ラジオボタンがオンにされるたびにビューモデルの副菜を設定する点が異なります。小計テキストも更新する必要があります。また、キャンセル ボタンと「次へ」ボタンにonClick
属性を設定する必要があります。fragment_accompaniment_menu.xml
でも同じ操作を行いますが、今回はAccompanimentMenuFragment
のバインディング変数を設定し、ラジオボタンがオンにされるたびに付け合わせを設定します。再度、小計テキスト、キャンセル ボタン、「次へ」ボタンについても属性を設定する必要があります。fragment_checkout.xml
では、バインディング変数を定義するために、<data>
タグを追加する必要があります。<data>
タグ内に、2 つのバインディング変数(OrderViewModel
用とCheckoutFragment
用)を追加します。テキストビューでは、選択した主菜、副菜、付け合わせの名前と価格を、OrderViewModel
から設定する必要があります。また、OrderViewModel
の小計、税金、合計を設定する必要があります。次に、CheckoutFragment
の適切な関数を使用して、注文を送信する場合とキャンセルする場合のonClickAttributes
を設定します。
.
フラグメントでデータ バインディング変数を初期化する
メソッド onViewCreated()
内の対応するフラグメント ファイルで、データ バインディング変数を初期化します。
EntreeMenuFragment
SideMenuFragment
AccompanimentMenuFragment
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 パッケージ内にあります。
これは、このアプリのナビゲーション グラフです。ただし、ファイルは現在空です。ここでは、ナビゲーション グラフにデスティネーションを追加し、画面間の次のナビゲーションをモデル化します。
StartOrderFragment
からEntreeMenuFragment
へのナビゲーションEntreeMenuFragment
からSideMenuFragment
へのナビゲーションSideMenuFragment
からAccompanimentMenuFragment
へのナビゲーションAccompanimentMenuFragment
からCheckoutFragment
へのナビゲーションCheckoutFragment
からStartOrderFragment
へのナビゲーションEntreeMenuFragment
からStartOrderFragment
へのナビゲーションSideMenuFragment
からStartOrderFragment
へのナビゲーションAccompanimentMenuFragment
からStartOrderFragment
へのナビゲーション- 開始デスティネーション は
StartOrderFragment
です。
ナビゲーション グラフを設定したら、フラグメント クラスでナビゲーションを行う必要があります。フラグメントの残りの TODO
コメントと MainActivity.kt
を実装します。
EntreeMenuFragment
、SideMenuFragment
、AccompanimentMenuFragment
のgoToNextScreen()
メソッドについて、アプリの次の画面に移動します。EntreeMenuFragment
、SideMenuFragment
、AccompanimentMenuFragment
、CheckoutFragment
のcancelOrder()
メソッドについては、最初にsharedViewModel
でresetOrder()
を呼び出し、StartOrderFragment
に移動してください。StartOrderFragment
で、setOnClickListener()
を実装してEntreeMenuFragment
に移動します。CheckoutFragment
にsubmitOrder()
メソッドを実装します。sharedViewModel
でresetOrder()
を呼び出してから、StartOrderFragment
に移動します。- 最後に、
MainActivity.kt
で、navController
をNavHostFragment
のnavController
に設定します。
4. アプリをテストする
Lunch Tray プロジェクトには、MenuContentTests
、NavigationTests
、OrderFunctionalityTests
という複数のテストケース付きの「androidTest」ターゲットが含まれています。
テストを実行する
テストを実行するには、次のいずれかを行います。
テストケースが 1 つの場合は、テストケース クラスを開いて、クラス宣言の左側にある緑色の矢印をクリックします。メニューから [Run] オプションを選択します。これにより、テストケース内のすべてのテストが実行されます。
多くの場合、1 つのテストを実行するだけで済みます。たとえば、失敗したテストが 1 つしかなく、他のテストには合格している場合などです。テストケース全体を実行するときと同じように、1 つのテストを実行できます。緑色の矢印をクリックして [Run] オプションを選択します。
複数のテストケースがある場合は、テストスイート全体を実行することもできます。アプリを実行する場合と同様に、このオプションは [Run] メニューにあります。
Android Studio は、デフォルトでは最後に実行したターゲット(アプリやテスト ターゲットなど)を実行するため、メニューに [Run] > [Run 'app'] と表示されている場合は、[Run] > [Run] を選択してテスト ターゲットを実行できます。
ポップアップ メニューからテスト ターゲットを選択します。
5. 省略可: フィードバックをお寄せください
このプロジェクトに関するご意見をお寄せいただけければ幸いです。こちらの簡単なアンケートにお答えください。お寄せいただいたフィードバックは、このコースに関する今後のプロジェクトの参考にさせていただきます。