はじめに
Jetpack Composeについて学びたいと思ったので、勉強がてら名刺アプリを個人開発してみました。その時に検討した内容を備忘録的に書いていきます。
この記事では、フォルダ構成検討時に学んだこととを書いてます。(時間があれば他のことも書いてみる予定)
アーキテクチャーとフォルダ構成
Kotlinは、MVVM(Model/View/ViewModel)が主流のようなのでそのまま採用します。
ただ、UIロジックを分離させないとViewModel層がかなり肥大化するので、そこを考慮した上でフォルダ構成を考えてみました。
フォルダ構成
/app
├── /src
│ ├── /main
│ │ ├── /java/com/example/app
│ │ │ ├── /data
│ │ │ │ ├── /model
│ │ │ │ │ └── BusinessCard.kt
│ │ │ │ ├── /repository
│ │ │ │ │ └── BusinessCardRepository.kt
│ │ │ │ └── /network
│ │ │ │ └── BusinessCardNetwork.kt
│ │ │ ├── /domain
│ │ │ │ └── /usecase
│ │ │ │ └── RegisterBusinessCardUseCase.kt
│ │ │ ├── /ui
│ │ │ │ ├── /screen
│ │ │ │ │ ├── /home
│ │ │ │ │ │ ├── HomeScreen.kt
│ │ │ │ │ │ └── HomeViewModel.kt
│ │ │ │ │ ├── /detail
│ │ │ │ │ │ ├── DetailScreen.kt
│ │ │ │ │ │ └── DetailViewModel.kt
│ │ │ │ │ └── /profile
│ │ │ │ │ │ ├── ProfileScreen.kt
│ │ │ │ │ │ └── ProfileViewModel.kt
│ │ │ │ └── /component
│ │ │ │ ├── CustomButton.kt
│ │ │ │ └── CustomCard.kt
│ │ │ ├── /di
│ │ │ │ └── AppModule.kt
│ │ │ ├── /navigation
│ │ │ │ └── AppNavigation.kt
│ │ │ ├── /util
│ │ │ │ └── Constants.kt
│ │ │ └── MainActivity.kt
│ │ └── /res
│ │ └── /image
│ │ └── /icon
│ │ └── /values
フォルダごとの役割
ここではそれぞれの役割について簡単に説明します。
Data層
フォルダ名 | 説明 |
---|---|
model | データ型を管理する |
repository | データの取得先・取得方法を管理する |
network | API通信などを管理する |
Domain層
フォルダ名 | 説明 |
---|---|
usecase | ビジネスロジックを管理する |
UI層
フォルダ名 | 説明 |
---|---|
screen | 画面を管理する同じ階層にViewModelファイルも配置する |
component | ボタンやダイアログなどのパーツを管理する |
DI層 (Dependency Injection)
フォルダ名 | 説明 |
---|---|
di | 依存性の注入(Repository、ViewModelなどをまとめて組み立てる) |
Navigation層
フォルダ名 | 説明 |
---|---|
navigation | 画面遷移を管理する |
Util層
フォルダ名 | 説明 |
---|---|
util | よく使う定数や関数を管理 |
Res層
フォルダ名 | 説明 |
---|---|
image | 画像を管理する |
icon | アイコンを管理する |
values | 色や文字サイズを管理する |
フォルダ構成検討時に学んだこと
1、UI 層は「画面」と「コンポーネント」を分離する
UI のロジックは Composableアノテーションを使って呼び出すらしいので、再利用できるパーツは componentフォルダに分離しておくことで「画面」と「ロジック」をスッキリさせました。
例:Composeアノテーションの使い方
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApplicationTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
HomeScreen(Home().text())
}
}
}
}
}
@Composable
fun HomeScreen(text: String) {
Text(text = text)
}
2、ViewModelの状態管理にStateとFlowを使う
Stateは聴き馴染みがありましたが、Flowは初めて知りました。
State: 現在の状態を表すオブジェクト。UIがどのようなデータを表示するか、またはどのような状態(読み込み中、エラー中、成功した状態など)であるかを管理してる
Flow: データのストリームを提供するKotlinの機能らしい。非同期でデータを受け取り続けることができるのでデータの変化を監視できる。
3、sealed classでUIの状態 (Loading, Success, Error) を管理する
sealed class(シールドクラス)は、例えば、以下のようなUIの状態を管理できるらしい。
Loading: データを読み込んでいる最中
Success: データの取得が成功した状態
Error: データの取得に失敗した状態
4、ビジネスロジックの考え方
ビジネスロジックは、pureなJava(Kotlin)で実装してフレームワークなどに依存しない作りにした方がよい。
将来的にフレームワークを変更する必要が出てきた時に移行することが容易になり、ビジネスロジックをそのまま使える。
まとめ
小規模〜中規模:ちょっと工夫は必要だけど、基本的にはMVVM構成でよさそう
大規模:クリーンアーキテクチャーを採用したい
マルチプラットフォーム:Modelにビジネスロジックを集約し、異なるプラットフォームでも再利用しやすい構成になっているMVI(Model-View-Intent)がいいのかも?
という感じで、フォルダ構成を検討するだけでも、いろんな考え方、やり方があるんだなと大変勉強になりました。