FeatureモジュールとCoreモジュール
モジュール境界を設計します。
「FeatureモジュールとCoreモジュール」はCoddyKit上の無料Android Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはAndroid Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Android Academyコースには全4レッスンが含まれています。
2種類のモジュール
モジュール化されたAndroidアプリの多くは、コードを主にfeatureモジュールとcoreモジュールの2種類に整理し、その上に薄い:appモジュールを置きます。
- Featureモジュールには、アプリのユーザー向けの一部分(画面やフロー)を配置します。
- Coreモジュールには、多くのfeatureで使う共有インフラを配置します。
このレッスンでは、これらの境界を適切に定義する方法を学びます。
Featureモジュールの構成
:feature:profileのようなfeatureモジュールには、1つのfeatureに必要なすべてのもの、つまりComposeの画面、ViewModel、UI状態が含まれます。UIからview-modelまでの垂直方向の一連の機能を所有するため、verticalな構成です。
共有部品のためにcoreモジュールへ依存しますが、他のfeatureモジュールには依存しないようにします。
// feature/profile/ProfileScreen.kt
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (state) {
is ProfileUiState.Loading -> CircularProgressIndicator()
is ProfileUiState.Success -> ProfileContent((state as ProfileUiState.Success).user)
is ProfileUiState.Error -> ErrorMessage()
}
}FeatureのViewModel
各featureは独自のViewModelを所有します。coreモジュールのリポジトリ経由でデータを取得し、UI状態を公開します。featureモジュールが知るのはデータの取得方法ではなく、リポジトリの契約だけです。
// feature/profile/ProfileViewModel.kt
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository // from :core:data
) : ViewModel() {
val uiState: StateFlow<ProfileUiState> =
userRepository.observeUser()
.map { ProfileUiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ProfileUiState.Loading)
}Coreモジュールの構成
coreモジュールはhorizontalな構成で、複数のfeatureで使われる1つの機能を提供します。一般的なcoreモジュールには次のようなものがあります。
:core:model— どこからでも共有する単純なデータクラス:core:network— Retrofit/Ktorクライアント:core:database— Roomの設定:core:data— ネットワークとデータベースを組み合わせるリポジトリ:core:designsystem— テーマと再利用可能なcomposable
// core/model/User.kt
data class User(
val id: String,
val name: String,
val avatarUrl: String
)デザインシステムモジュール
:core:designsystemは、最も再利用されるモジュールの1つです。MaterialTheme、カラースキーム、タイポグラフィ、ボタンやカードなどの再利用可能なcomposableを保持します。すべてのfeatureでコードをコピーすることなく、同じ見た目を適用できます。
// core/designsystem/AppTheme.kt
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}データモジュールがリポジトリを所有する
:core:dataモジュールは、featureが依存するリポジトリインターフェースを公開し、実装は隠蔽します。通常は:core:networkと:core:databaseに依存し、それらを組み合わせて単一の信頼できる情報源を提供します。
// core/data/UserRepository.kt
interface UserRepository {
fun observeUser(): Flow<User>
suspend fun refresh()
}
// core/data/OfflineFirstUserRepository.kt
internal class OfflineFirstUserRepository @Inject constructor(
private val api: UserApi, // :core:network
private val dao: UserDao // :core:database
) : UserRepository {
override fun observeUser(): Flow<User> = dao.observe().map { it.toUser() }
override suspend fun refresh() { dao.upsert(api.fetch().toEntity()) }
}model と designsystem を軽量に保つ
最下層のコアモジュールは、できるだけ少ない依存関係だけを持つようにします。:core:model は理想的にはAndroidへの依存関係を一切持たず、通常のKotlinデータクラスだけで構成します。これにより、どこでも利用でき、ビルドも高速になります。
もし:core:model が Retrofit や Room に依存し始めると、データクラスを利用するすべてのモジュールに、それらの重いライブラリまで取り込まれてしまいます。
// core/model/build.gradle.kts
plugins {
id("myapp.jvm.library") // pure Kotlin, no Android
}
// No Retrofit, no Room, no Compose here — just data classes薄い :app モジュール
:app モジュールは組み立て役です。ここには、Application クラス、単一のMainActivity、トップレベルのNavHost、依存性注入の配線だけを置き、ロジックはほとんど含めないようにします。実際の画面はすべて機能モジュールに配置します。
appモジュールを薄くすると、変更の大半が機能モジュール内で発生するため、appモジュールが再ビルドされることはほとんどありません。
// app/MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme { // from :core:designsystem
AppNavHost() // routes into :feature:* screens
}
}
}
}適切な境界を設ける
何をモジュールにするかは、どのように決めればよいのでしょうか。実用的な目安をいくつか紹介します。
- 機能 = ユーザーが名前を挙げられる画面またはフロー(ホーム、プロフィール、購入手続きなど)です。
- コアモジュール = 2つ以上の機能で再利用される能力です。
- 2つの機能が同じコードを必要とする場合は、そのコードをコアモジュールに下ろします。
- 1つのモジュールが無関係な処理を多く担っている場合は、分割します。
オプション: api と impl の分割
大規模なアプリでは、機能を公開用の:feature:profile:api(インターフェース、ナビゲーションルート)と、非公開の:feature:profile:impl(画面、ViewModel)に分割することがあります。他の機能は小さなapiにのみ依存し、実装には依存しません。
これは高度な設計です。ほとんどのアプリでは、機能ごとに1つのモジュールがあれば十分です。このパターンが非常に大規模なコードベースで使われることだけ知っておいてください。
// Other features see only the contract, not the screens
// feature/home depends on :feature:profile:api
interface ProfileEntry {
val route: String
fun NavGraphBuilder.register(navController: NavController)
}グラフを組み立てる
こちらが、今回の例におけるレイヤーの接続方法です。依存関係が常に下向きにのみ向いていることに注目してください。app -> feature -> data -> network/database -> model という構成です。
// :app -> :feature:home, :feature:profile
// :feature:home -> :core:data, :core:designsystem
// :feature:profile -> :core:data, :core:designsystem
// :core:data -> :core:network, :core:database, :core:model
// :core:network -> :core:model
// :core:database -> :core:model
// :core:model -> (nothing)理解度チェック
:feature:home と :feature:profile の両方で、ユーザーデータの取得とキャッシュが必要です。このリポジトリのコードは、どこに配置すべきでしょうか。
まとめ: 機能モジュールとコアモジュール
コードを2つのレイヤーに分割する方法を学びました。
- 機能モジュールは、画面、ViewModel、UI状態を含む垂直的なスライスです。
- コアモジュールは、model、network、database、data、designsystemなどの水平的な機能です。
:appモジュールは薄く保ち、機能を組み立てるだけにします。- 共有コードはコアへ下ろし、
:core:modelはAndroidに依存しない軽量な状態を保ちます。
次は、これらのモジュール間の依存関係を管理し、グラフをきれいに保つ方法を学びます。
よくある質問
「FeatureモジュールとCoreモジュール」レッスンは無料ですか?
はい。「FeatureモジュールとCoreモジュール」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Android Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Android Academyコースには全4レッスンが含まれています。
「FeatureモジュールとCoreモジュール」で何を学びますか?
モジュール境界を設計します。 ブラウザで直接実行するハンズオンコードでAndroid Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
Android Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのAndroid Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。
「FeatureモジュールとCoreモジュール」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このAndroid Academyレッスンでコードを書いて実行できますか?
はい。すべてのAndroid Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- モジュール化する理由
- FeatureモジュールとCoreモジュール
- モジュール依存関係の管理
- モジュール間のナビゲーション