Patterning Dagger/Hilt Cases Where a Module Is or Is Not Required

 

🧑🏻‍💻 When a Module Is Not Required: Concrete Classes

In Hilt/Dagger, concrete classes with an @Inject constructor can be injected automatically.


class ApiClient @Inject constructor()

class UserRepository @Inject constructor(
    private val api: ApiClient
)

Point:
If the class can be instantiated directly, a Module is not required.

 

🧑🏻‍💻 When a Module Is Required (1): Interfaces

When injecting an interface, Hilt cannot determine which implementation to use.

You need to specify it explicitly using @Binds inside a Module.


interface Logger { 
    fun log(msg: String) 
}

class ConsoleLogger @Inject constructor() : Logger

@Module
@InstallIn(SingletonComponent::class)
interface LoggerModule {
    @Binds
    fun bindLogger(impl: ConsoleLogger): Logger
}

Point:
Interfaces always require a Module.

 

🧑🏻‍💻 When a Module Is Required (2): External Libraries

External libraries typically do not have @Inject constructor.

You must provide the creation logic inside a Module.


@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    fun provideClient(): OkHttpClient = OkHttpClient.Builder().build()
}

Point:
You make external classes injectable by defining how to create them in a Module.

 

🧑🏻‍💻 Summary

  • Your own concrete classes → auto-injectable
  • Interfaces & external libraries → Module required
  • Multiple implementations or singleton handling → use @Qualifier, @Named, @Singleton

👉 Dagger/HiltでModuleが必要か一目でわかるようにパターン化する


Kotlin StateFlow: value vs. update – Which One Should You Use?

When updating a MutableStateFlow, you have two options. Here is how to decide instantly.

 

🧑🏻‍💻 The Golden Rule

  • Use update { } if the new value depends on the current value (e.g., incrementing a counter, toggling a boolean).
  • Use value = if you are completely overwriting the state (e.g., setting a loading state, resetting data).

 

🧑🏻‍💻 Why does it matter?

Direct assignment (value = ...) is not thread-safe for "read-modify-write" operations.

If two coroutines try to update the state simultaneously using .value = .value + 1, you risk a Race Condition where one update is lost.

The update function is atomic. It uses a Compare-And-Set mechanism to ensure that updates happen sequentially and safely, even across multiple threads.

 

🧑🏻‍💻 Code Comparison

❌ Risky (Race Condition prone)


// If called concurrently, updates might be lost
_uiState.value = _uiState.value.copy(count = _uiState.value.count + 1)

✅ Safe (Thread-safe)


// Guarantees consistency
_uiState.update { it.copy(count = it.count + 1) }

✅ Safe (Overwrite)


// No race condition risk because we ignore the previous state
_uiState.value = UiState.Loading

 

🧑🏻‍💻 Summary

When in doubt, use update. It is safer by default and prevents subtle concurrency bugs.


Why are updates to Kotlin, Compose, and KSP such a hassle?

In Android development, you're constantly dealing with the same set of three: Kotlin, the Compose Compiler, and KSP.

They seem like a friendly group, but their update schedules are always completely different! You upgrade Kotlin, and the Compose Compiler isn't compatible. You change something, and KSP throws a build error because of an internal API change...

To better manage this "dependency triangle" situation, the main idea is to use Renovate's configuration to treat them as a single unit. The simple plan is: "Raise all Kotlin ecosystem dependencies at the same time!"

 

🧑🏻‍💻 Brief overview of the renovate.json file


{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:base",
    "group:all",
    ":dependencyDashboard",
    "schedule:daily"
  ],
  "baseBranches": ["main"],
  "commitMessageExtra": "{{{currentValue}}} to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMajor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}",
  "packageRules": [
    {
      "matchPackagePatterns": ["androidx.compose.compiler:compiler"],
      "groupName": "kotlin"
    },
    {
      "matchPackagePatterns": ["org.jetbrains.kotlin.*"],
      "groupName": "kotlin"
    },
    {
      "matchPackagePatterns": ["com.google.devtools.ksp"],
      "groupName": "kotlin"
    }
  ]
}

👉 architecture-samples/renovate.json at main · android/architecture-samples

Roughly summarized, here are the key points:

  • groupName: "kotlin" to bundle dependencies This setting specifies that the three elements—the Compose Compiler, Kotlin, and KSP—should be treated as belonging to the "same group." This allows Renovate to update them all together at once.
  • schedule: daily for a calm update pace This checks for updates once a day. You'll receive pull requests (PRs) on a daily basis, preventing a huge influx of dependency updates all at once, which makes things much easier to manage.
  • commitMessageExtra to see changes at a glance The version difference, like "2.0.10 → 2.0.20," is automatically added to the PR title. It's a small tweak, but surprisingly useful.

Setting up your configuration this way significantly reduces the tragedy of "Kotlin got updated, but Compose broke..."

 

🧑🏻‍💻 What We Found While Using It

Once this setup is in place, you can feel much more confident testing updates for everything Kotlin-related. Renovate diligently checks daily, automatically creating a PR whenever a new version drops.

But there's one small warning:

The Compose Compiler sometimes takes a little extra time to catch up to the latest Kotlin version. So, don't just merge the PR when you see it—it's highly recommended to verify the CI status first.

KSP is similar; because it depends on Kotlin's internal workings, it's safer to update it along with Kotlin and run your tests together.

 

🧑🏻‍💻 Summary: Teach Renovate that "These Three Are a Set"

The configuration we discussed treats the trio of Kotlin, the Compose Compiler, and KSP as a single group.

  • Bundle all Kotlin-related dependencies for simultaneous updates.
  • Check for updates at a manageable daily pace.
  • See version differences directly in the PR title.

Just implementing this significantly reduces the problems caused by versions getting out of sync and breaking your build.

💡 Key Takeaway: Use Renovate less as an "automatic update tool" and more as a "dependency rulebook."

We simply need to tell Kotlin, Compose, and KSP to cooperate and "work together."

👉 Kotlin・Compose・KSP の更新、どうしてこんなに面倒なの?


Activity x Compose Lifecycle: The Complete Visual Guide 🚀

 

👨‍💻 Activity Lifecycle


Activity.onCreate() <- Activity is created

↓

Activity.onStart() <- Preparing to become visible on screen

↓

Activity.onResume() <- Foreground state (interactive)

↓

Activity.onPause() <- Partially visible

↓

Activity.onStop() <- No longer visible

↓

Activity.onDestroy() <- Activity is destroyed

 

👨‍💻 Compose Lifecycle


[First Composition]
  - Composable is evaluated, Compose tree is built
  - LaunchedEffect -> Runs once after the commit
  - SideEffect -> Runs after every commit
  - DisposableEffect -> onDispose is called upon disposal

↓

[Recomposition]
  - UI is re-evaluated in response to state or data changes
  - Only the necessary parts are recomposed (efficient update)
  - SideEffect and DisposableEffect are also re-evaluated during recomposition

↓

[Dispose]
  - Depends on the ComposeView's disposal condition
  - DisposableEffect's onDispose is executed
  - Disposal timing is determined by the ViewCompositionStrategy

 

👨‍💻 Activity x Compose Lifecycle


Activity.onCreate() setContent { ... } <- Sets the ComposeView

↓

[First Composition]
  - Evaluates Composables and builds the UI
  - LaunchedEffect -> Runs once after commit
  - SideEffect -> Runs after each commit
  - DisposableEffect -> Defines onDispose

↓

Activity.onStart()

↓

Activity.onResume()

↓

[Recomposition]
  - Re-evaluates necessary parts in response to state changes
  - LaunchedEffect is not re-executed (only if its key changes)
  - SideEffect / DisposableEffect are re-evaluated

↓

Activity.onPause()

↓

(ComposeView is retained)
  - UI becomes partially obscured
  - State is maintained within the Composition

↓

Activity.onStop()

↓

[Preparing for Dispose (Detection)]
  - ViewCompositionStrategy monitors disposal conditions

↓

Activity.onDestroy()

↓

[Dispose]
  - ComposeView is destroyed
  - DisposableEffect.onDispose() is executed

This is the general flow.

Here are the notes for each item:

Activity.onCreate() A lifecycle method called when an Android app's Activity is created.

    setContent { ... }: Sets the UI using Jetpack Compose. This sets a ComposeView as the Activity's content view. The Compose lifecycle begins.

  • First Composition The Composable functions set in setContent are evaluated for the first time, and the UI is built.

LaunchedEffect: An Effect used for asynchronous processing. Runs only once after the first composition.

SideEffect: Runs after every composition commit (the timing when UI changes are applied).

DisposableEffect: An Effect used for resource cleanup. The logic defined in onDispose is executed when the Composition is disposed.

Activity.onStart() A lifecycle method called just before the Activity becomes visible to the user.

Activity.onResume() The Activity moves to the foreground and becomes fully interactive with the user.

  • Recomposition Only the necessary Composable functions are re-evaluated in response to changes in State.

LaunchedEffect: It is not re-executed during recomposition. However, it will be re-executed if its key changes.

SideEffect: It is re-evaluated on every recomposition.

DisposableEffect: It is re-evaluated on recomposition, and the cleanup logic (onDispose) from the old Effect is called.

Activity.onPause() The Activity enters a paused state and becomes partially obscured.

ComposeView Retention: The UI is not destroyed; it is retained. The state is also maintained within the Composition, allowing it to be reused upon re-display.

Activity.onStop() The Activity is no longer visible. A time to prepare for cleaning up state and resources.

Preparing for Dispose (Detection) ViewCompositionStrategy: A mechanism that monitors the conditions under which the ComposeView should be disposed. It triggers the Composition's disposal based on the View's lifecycle.

Activity.onDestroy() The timing when the Activity is completely destroyed.

  • Dispose The ComposeView is destroyed: The UI and state are completely released.
  • DisposableEffect.onDispose(): The resource cleanup logic is called.

 

👨‍💻 Summary

The Jetpack Compose and Activity lifecycles are closely linked, with specific processes occurring at each stage, from UI initialization (setContent) to disposal (Dispose).

It is especially important to understand the differences between effects like LaunchedEffect, SideEffect, and DisposableEffect, and to manage them appropriately.

Furthermore, by efficiently reusing the UI in response to Activity state changes (like onResume or onPause) and cleaning up resources as needed, you can achieve stable application behavior.


IDE × AIモデル別:プロンプトに食わせるべきファイルまとめ

主要IDEごとに、連携AI・推奨ファイル・目的・補足を整理した表です。プログラミング中心にまとめています。

1. 基本のセット

  • プロジェクト概要・設計
    README.md, architecture.md
    AIに全体像・設計方針・責務を理解させる
  • 依存・環境情報
    build.gradle(.kts), package.json, Podfile, .env.example
    SDK・ライブラリ・環境変数を正確に認識させる
  • コーディング方針・ルール
    .prompt.yaml, .copilot-instructions.md, .editorconfig
    命名規則、禁止API、コードスタイルを統一

2. IDE × AIモデル別の推奨ファイルと効果

IDE 推奨AIモデル 重点ファイル 効果 補足
Android Studio / IntelliJ Gemini Code Assist, Copilot .prompt.yaml, build.gradle, architecture.md Androidプロジェクト全体を理解した補完・設計提案 プロジェクト全体の構造を解析可能。方針ファイルで安定化。
Xcode Copilot, GPT-5 .prompt.yaml, Package.swift, README.md SwiftUI/MVVM設計に沿った正確なコード生成 Xcodeは依存解析が弱めなので .prompt.yaml を明示すると効果大。
VS Code Copilot Chat, GPT-5 .copilot-instructions.md, package.json, README.md 軽量環境で多言語対応、チーム開発の方針共有に有効 拡張機能単位でAI切替可能。指示ファイルが最重要。
Cursor / Windsurf / Aider GPT-5 / Claude 3.5 / Gemini 1.5 .prompt.yaml / .cursorconfig, README.md, architecture.md, build設定 設計・生成・リファクタを自動で分担 ファイル単位でAIが文脈キャッシュを保持。設計書参照可。
Jupyter / DataSpell / VSCode + Python GPT-4 Turbo, Gemini Advanced .ipynb / .csv / .xlsx, analysis.md / README.md, .prompt.yaml データ解析・統計・グラフ生成 データ+分析目的+出力形式を渡すと的確に解析可能。
Figma / Webflow / Framer Gemini 1.5 Pro (Vision), GPT-5 Vision .fig / .svg / layout.json, style_reference.jpg, .prompt.yaml UIデザイン→コード変換・スタイル抽出 構図+目的+出力フォーマット指定でSwiftUIやComposeコード化が容易。

3. 実務での運用Tips

  • サンプルコードを渡す
    /sample_code に小さな動作例を置くと、AIが文体やパターンを模倣しやすい
  • 変更履歴を渡す
    CHANGELOG.md や feature_list.md を読むことで、過去の修正意図を理解し、安全な提案が可能
  • 大きなファイルは要約して渡す
    設計書や長文ドキュメントは、AIの文脈理解の負荷を減らすために必要部分だけ渡す
  • 依存関係やAPI仕様は明示
    未定義関数や古いAPIの誤提案を防ぐため、build.gradle や .env.example を食わせる

4. まとめ

プログラミングAIを「単なるコード補完」ではなく、プロジェクト理解型のアシスタントとして活用するには、

「概要 + 環境 + 方針」をAIに与えることが最も重要です。

  • IDEや言語に合わせたファイルを食わせる
  • 設計方針と依存関係を明確化する
  • サンプルコードや履歴で文脈を補完する

この3ステップで、AIは理解に基づいたコード生成・設計提案を行い、開発効率と品質を大幅に向上させられます。