Android のアーキテクチャで何が Google に「強く推奨」されているか図で理解する

👉 Recommendations for Android architecture  |  Android Developers

分かりやすいドキュメントなのでさらに分かりやすくなるように図にしてみます。

 

🤔 Layered Architecture


UI Layer
  |
Coroutine
  |
DataL Layer
  |
Repository

👉 Layered architecture - Recommendations for Android architecture  |  Android Developers

 

🤔 UI Layer


UI Layer
  UDF
  AAC ViewModel
  Lifecycle
  User Event

👉 UI layer - Recommendations for Android architecture  |  Android Developers

 

🤔 ViewModel


ViewModel
  Depencencies
  Coroutine Flow
  State Holder

👉 ViewModel - Recommendations for Android architecture  |  Android Developers

 

🤔 Lifecycle


Lifecycle
  LifecycleObserver

👉 Lifecycle - Recommendations for Android architecture  |  Android Developers


 

🤔 Handle dependencies


Constructor Injection
Container

👉 Handle dependencies - Recommendations for Android architecture  |  Android Developers

 

🤔 Test


ViewModel
Data Entity (Repository + DataSource)
FakeData
StateFlow

👉 Test - Recommendations for Android architecture  |  Android Developers

 

🤔 まとめ

しれっと、公式リファレンスも分かりやすく更新されていて驚きました。

👉 【Android】アーキテクチャーとして Google が推奨していること #Kotlin - Qiita


Hilt で KSP の依存関係の設定 (build.gradle.kts)

kapt から KSP に移行しようとしてハマる。

 

🚀 Dagger + KSP

今回は使わなったが動く。


plugins {
  id("org.jetbrains.kotlin.android") version "1.9.0"
  id("com.google.devtools.ksp") version "1.9.0-1.0.12"
}

dependencies {
  ksp("com.google.dagger:dagger-compiler:2.48") // Dagger compiler
  ksp("com.google.dagger:hilt-compiler:2.48")   // Hilt compiler
}

👉 Dagger KSP hatena-bookmark

 

🚀 Hilt + kapt


// build.gradle.kts (Project)

plugins {
  id("com.google.dagger.hilt.android") version "2.44" apply false
}


// build.gradle.kts (Module)

plugins {
  kotlin("kapt")
  id("com.google.dagger.hilt.android")
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.44")
  kapt("com.google.dagger:hilt-android-compiler:2.44")
}

kapt {
  correctErrorTypes = true
}

👉 Hilt を使用した依存関係の注入  |  Android デベロッパー  |  Android Developers hatena-bookmark

 

🚀 Hilt + KSP


// build.gradle.kts (Project)

plugins {
  id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
  id("com.google.dagger.hilt.android") version "2.44" apply false
}


// build.gradle.kts (Module)

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.44")
  ksp("com.google.dagger:hilt-android-compiler:2.44")
}

👉 kapt から KSP に移行する  |  Android デベロッパー  |  Android Developers hatena-bookmark

👉 Revisions · Hilt + kapt → KSP hatena-bookmark

 

🚀 まとめ

Hilt で公式リファレンスを見ながら、kapt → KSP と順番に変化させていけばスムーズに対応できたのに、Dagger KSP ページを見ながら進んだのがハマった原因。

Version Catalog を使っていくことになりそうなので、抜粋しておく。




【JetpackCompose】 Composable 関数の Modifier について知らなかった3つのルール

【JetpackCompose】 Composable 関数の Modifier について知らなかった流儀

detekt に指摘されながら、ドキュメントを読む。


こんなコードがあったとして。


Column {
    InnerContent()
}

@Composable
private fun InnerContent() {
    Text(...)
    Image(...)
    Button(...)
}

以下、知らなかった流儀。

 

🧩 Composable 関数はレイアウトを一つだけ出力する

A composable function should emit either 0 or 1 pieces of layout, but no more. A composable function should be cohesive, and not rely on what function it is called from.

コンポーザブル関数は、レイアウトのピースを0個または1個だけ発行するべきで、それ以上は発行してはいけません。コンポーザブル関数は結束性を持ち、呼び出される関数に依存すべきではありません。

レイアウトのネストのコストはあまり気にしなくて良い、とのこと。

Nesting of layouts has a drastically lower cost vs the view system, so developers should not try to minimize UI layers at the cost of correctness.

レイアウトのネストには、ビューシステムに比べてはるかに低いコストがかかるため、UIの階層を正確性の犠牲にして最小化しようとしないべきです。

👉 Do not emit multiple pieces of content - Twitter Jetpack Compose Rules hatena-bookmark

ということで、一見不要に見える Column を追加する。


@Composable
private fun InnerContent() {
    Column {
        Text(...)
        Image(...)
        Button(...)
    }
}

 

🧩 デフォルトを持つパラメータ modifier: Nodifier = Modifier は必須

They are especially important for your public components, as they allow callers to customize the component to their wishes.

特に、公開コンポーネントにとって Modifier は非常に重要であり、呼び出し元がコンポーネントを希望に合わせてカスタマイズできるようにします。

👉 When should I expose modifier parameters? - Jetpack Compose Rules hatena-bookmark

Composables that accept a Modifier as a parameter to be applied to the whole component represented by the composable function should name the parameter modifier and assign the parameter a default value of Modifier.

Composable 関数内でコンポーザブル関数を表すコンポーネント全体に適用するための修飾子をパラメータとして受け入れる場合、そのパラメータは "modifier" という名前を付け、パラメータに Modifier のデフォルト値を割り当てるべきです。

👉 Modifiers should have default parameters - Jetpack Compose Rules hatena-bookmark

ということで、親からの Modifier を受け入れるようにします。デフォルト値もつけておきます。


@Composable
private fun InnerContent(modifier: Modifier = Modifier) {
    Column {
        Text(...)
        Image(...)
        Button(...)
    }
}

 

🧩 受け取った Modifier パラメータは最上位のレイアウトにのみ適用する

Modifiers should be applied once as a first modifier in the chain to the root-most layout in the component implementation. Since modifiers aim to modify the external behaviors and appearance of the component, they must be applied to the top-most layout and be the first modifiers in the hierarchy. It is allowed to chain other modifiers to the modifier passed as a param if needed.

Modifier は、コンポーネントの実装内でルートのレイアウトに最初の Modifier として一度適用すべきです。Modifier はコンポーネントの外部の動作や外観を変更することを目的としているため、最上位のレイアウトに適用し、階層内で最初の Modifier である必要があります。必要に応じて、パラメータとして渡された Modifier に他の Modifier を連鎖させることは許可されています。

👉 Modifiers should be used at the top-most layout of the component - Jetpack Compose Rules hatena-bookmark

ということで、最上位ルートの Column で適用します。


@Composable
private fun InnerContent(modifier: Modifier = Modifier) {
    Column(modifier = modifier) {
        Text(...)
        Image(...)
        Button(...)
    }
}

 

🧩 まとめ

@Composable 内のレイアウトに関する Modifier は、親 (呼び出し元) から持ってきて、最上位のレイアウトで一度だけ適用する。

detekt を使うことで、なんとなく記述していた部分がスッキリしてきます。

👉 【AndroidStudio】detekt で JetpackCompose 記述をチェックする hatena-bookmark