【勉強法】設計を考える〜Netflixどう作る？〜

# この記事は

## アプリの設計は大切
最近私がコツコツやっている勉強方法を紹介させてください。
Androidに特化しているというよりかは、**アプリの設計を考える練習**です。

ここでいう設計とはMVVMとかクリーンアーキテクチャなどの**アーキテクチャ**とはまた異なる概念です。
どういうデータ構造にしているとデータを画面に表示しやすくなるかや、どのメソッドをどのレイヤーで管理するかなどを考えます。

アプリをつくる時にどのような設計にするかは、アプリの今後に大きく影響します。
設計がめちゃくちゃだと、たとえばホーム画面とプロフィール画面でいいねの数が違ってしまったりします。

表示されるデータに一貫性がなく、画面によって異なる情報が提示されるのはバグです。
アプリの設計を疎かにするとバグを生みやすくなり、機能追加などがむずかしくなります。

## アプリの設計を考える勉強法

新卒で超スタートアップに行って全部一人でつくっている方などは例外ですが、基本的に設計などの上流工程を考えるにはチーム内で一番えらくなる必要があると思っています。若手エンジニアにとって実際の業務で設計を考えるチャンスは少ないものです。

ではチャンスが与えられるまでの間は一人でこっそり設計について想いを巡らせ、練習します。

私は、**既存のアプリの一部分を自分が実装するとしたらどういう設計にするか**について考えています。

この勉強方法で大切なのは**実装は大体でいい**ということです。
ついつい、わからないところは調べ上げて完璧な動くコードを書いてしまいたくなりますが、その衝動を抑えてまずは全体の設計にだけ焦点を当てて考えます。

なのでコードは動かなくていいのです。

全体の設計が終わった時にアプリをつくるイメージが湧ききって、「やばい！いますぐこのアプリを作りたい！！」と思えるくらいがちょうどいいのです。

ちなみに私はこの勉強方法が楽しすぎて平日の22:00から朝の4:00までやってしまうことがしばしばあります。

楽しいので、気をつけて取り組んでください。


# Netflixの設計を考えてみよう！
今回はNetfixのホーム画面をどのように設計するかについて考えていきます。

🚨コードは実際に動くものではありません！設計をイメージするための最低限のものという認識でお願いします！

## View
データを表示するための画面を構成していきます。
また、UIは**JetpackCompose**で実装することを想定します。

今回はNetflixのホーム画面の設計を考えるので、ホーム画面のデザインがどうなっているか見てみましょう。

| ホーム画面1 | ホーム画面2 |
|---|---|
|<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/a8953daa-1f87-8a7b-cffa-def5dde1e970.jpeg">|<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/e53eccd1-f596-ac9b-ed7e-f39962897b4e.jpeg">|

ホーム画面を開くと、ホーム画面１がまず表示されてスクロールするとホーム画面２が表示されていきます。

それでは、適当に画面構成していきましょう。
大体こんな感じになるかな〜と思います。

```kotlin
// === HomeScreen ===

LazyColumn {
	PickUpContents() // ホーム画面１に表示されるでかいコンテンツ（キャプチャだと「セブン」てやつ）
	WatchingContents() // 視聴中のコンテンツ
	CategorySection() // 視聴中のコンテンツより下のコンテンツ全て
}

private fun WatchingContents() {
	LazyCulumn {
		WatchingContentsItem()
	}
}

private fun WatchingContentsItem() { }

private fun PickUpContents() {
	Column {
		Image()
		Row { CategoryTag() }
		Row{ 
			MyListButton()
			ReplayButton()
			InfoButton()
		}
	}
}

private fun CategorySection(categoryList: List<Category>) {
    Column {
        // categoryListに対してforeachをぶん回し、データの数だけCategoryを表示
        categoryList.foreach {Category(/*categoryListの中にあるデータを渡す*/)}
    }
}

private fun Category(/*categoryListの中にあるデータをもらってTextやImageに表示*/) {
    Text() // Categoryのタイトル
    // カテゴリ内にあるコンテンツの画像
    LazyRow {
        Image()
    }
}
```

### CategorySectionの型
そんなに複雑なところはないかと思いますが、CategorySectionの型についてだけ説明させてください。

CategorySectionはキャプチャのホーム画面２の`視聴中のコンテンツ`より下にある、表示されている全てのコンテンツをさします。画像の黄色い枠の部分です。

<img width=300 src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/72df6064-0fcd-6b43-2ac8-1ccbc9779217.jpeg">


タイトル（`漫画が原作のTVアニメ`, `人気急上昇の作品`など）とコンテンツ分の画像のひとかたまりのビューが縦に並ぶことになるので次のような構造になります。

```kotlin
// Columnで縦に並べる
Column {
    Category()
    Category()
    .
    .
    .
}

fun Category() {
    Text() // `漫画が原作のTVアニメ`とかを表示する
    // コンテンツ画像を横に並べる
    LazyRow{
        Image()
        Image()
        .
        .
    }
}

```

サンプルコードからもわかるとおり、データの数だけComposableを繰り返す必要が出てきそうです。

では、どのような形で（どのような型で）データを渡してあげれば、効率よく上記のようなUIを実装できそうでしょうか。

CategorySectionは各Category（`漫画が原作のTVアニメ`, `人気急上昇の作品`など）を持っており、各々のCategoryはそのCategoryに従ったアイテム（`CategoryItem`）を複数持っています。

キャプチャの場合だと、`漫画が原作のTVアニメ`というCategoryは`サマータイムレンダ`、`ブラッククローバー`、`推しの子`などのアイテム(`CategoryItem`)を持っていますね。

つまり、次の図の右側にあるようなデータの構造となると考え、CategoryのListという形（型でいうと`List<Category>`）で`CategorySection()`にデータを渡してあげれば、あとはこのリストに対してforeachをかけてComposableを繰返しできそうです。

![スクリーンショット 2023-06-03 025322.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/e3367555-7dba-e82c-9f30-0010d0b94c63.jpeg)


### ポイント
ここでのポイントは

**どういう画面構成にするか → どういう型のデータを作れば考えた画面にデータを表示させやすいか**

という順番で考えることです。多少行ったり来たりしてもいいかなとは思います。

さて、ここまでで「どういう型のデータを作ればいいか」の先っちょまで考えることができました。

次はよりその部分を深掘りしていきます。

「どういう型のデータを作ればいいか」というのはつまり、**UiState**のことです。
どういうUiStateを作ろうか考えていきましょう。

## UiState
先ほど作成したViewに取得してきたデータを表示するにはどのような形(データ構造)にすれば表示させやすいかを考えて実装します。

Home画面は

- **PickupContents**
- **WhatchingContents**
- **CategorySection**

の3つで構成されています

参考：
```kotlin
// === HomeScreen ===

LazyColumn {
	PickUpContents()
	WatchingContents()
	CategorySection()
}
```

それぞれのUiStateを考えていきましょう。

### PickupContents
`PickupContents`を構成する情報を書きます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/b9bc57b1-cee7-ea73-a074-745441cbab7a.jpeg">

```kotlin
data class PickupContents(
	val id: String,
	val image: String, // コンテンツの画像
	val contentsTags: List<String>, // 「ゾクっとする、人間の心理に迫る」などの部分
) {}
```

### WhatchingContents
`WhatchingContents`を構成する情報を書きます。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/a9bc1214-539e-4108-5251-70447ef38dc8.jpeg">


```kotlin
data class WatchingContents(
	val id: String,
	val image: String, // コンテンツの画像
	val watchingPercentage: Float, // 「パート2-5」とかインジケータとか、"どこまで見たか"を表示するのに必要な情報
) {}
```

### CategorySection


CategorySectionに関しては先にも触れましたが、次の図の通り、Categoryはそのカテゴリのタイトル（`categoryTitle`）とカテゴリのコンテンツ（`categoryItemList`）で構成されています。

従ってCategoryは`List<Category>`という型で表現できます。

また、`categoryItemList`はそのコンテンツの`id`や画像（`image`）で構成されています。

![スクリーンショット 2023-06-03 025322.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906559/851c8df5-19a7-8745-93b1-e1cc4c4b537f.jpeg)

UiStateとして表現すると次の通りです。

```kotlin
data class Category(
	val categoryTitle: String,
	val categoryItemList: List<CategoryItem>,
) {}

data class CategoryItem(
	val id: String,
	val image: String,
) {
	// Repositoryから取得してきたデータをViewに表示しやすい形(CategoryItem)に変換する
    companion object {
        // categoryはEntityとする
        fun from(category): CategoryItem {
            return CategoryItem(
                // categoryのデータをCategoryItemに突っ込む
            )
        }
    }
}
```

### Home画面全体のUiState

Home画面全体のUiStateは次のようになります。

```kotlin
data class HomeUiState(
	val shouldShowSnackBar: Boolean // Home画面にSnackBarとか出す時のことを考えて

	val pickupContents: PickupContents,
	val watchingContents: List<WatchingContents>,
	val categorySection: List<Category>
) {}
```


## ViewModel
先ほど作成したUiStateを型にしてサーバーやDBから取得してきたデータを更新します。

少し力付きているのでサーバから取得してきた`PickupContents`のデータを更新するところだけ *本当に適当に* 書いています。

この辺りは正直、実際に実装してみないとどういうコードになるか分かりにくいので、**ここでデータを更新する**ということだけ分かればいいかなと考えています。

```kotlin
class HomeViewModel: viewModel() {
	private val _uiState: MutableStateFlow<HomeUiState>
	val uiState: StateFlow = _uiState 

	init {
		val result = usecase.hoge() // resultの形はAPIによる
		
        // PickupContentsを更新する
		_uiState.value = HomeUiState(
		   pickupContents = PickupContents.from(result.pickupContent)
		)
	}
}
```


## おわり と おわび
実際に動くわけではない、ほぼイメトレレベルのコードなので、命名がおかしかったり、コードに違和感があるところも多々あるかもしれません。分かりにくく、申し訳ございません。 :bow: 

本記事では設計の大切さと設計を考えるための具体的なトレーニング方法について説明させていただきましたが、いかがでしたでしょうか。

少しでも皆さんの日々の勉強のtipsになれば幸いに思います。