245
227

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-06-05

この記事は

アプリの設計は大切

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

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

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

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

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

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

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

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

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

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

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

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

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

Netflixの設計を考えてみよう!

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

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

View

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

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

ホーム画面1 ホーム画面2

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

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

// === HomeScreen ===

LazyColumn {
	PickUpContents() // ホーム画面1に表示されるでかいコンテンツ(キャプチャだと「セブン」てやつ)
	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はキャプチャのホーム画面2の視聴中のコンテンツより下にある、表示されている全てのコンテンツをさします。画像の黄色い枠の部分です。

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

// 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

ポイント

ここでのポイントは

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

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

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

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

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

UiState

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

Home画面は

  • PickupContents
  • WhatchingContents
  • CategorySection

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

参考:

// === HomeScreen ===

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

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

PickupContents

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

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

WhatchingContents

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

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

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

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は次のようになります。

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

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

ViewModel

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

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

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

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になれば幸いに思います。

245
227
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
245
227

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?