0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Kotlin】Kotlin 拡張関数 × Reified 型パラメータによる Intent DSL設計

Last updated at Posted at 2025-10-13

はじめに

型安全に Activity を起動できる、直感的で安全な DSL の作り方


1. なぜ Intent DSL が必要なのか

通常の Intent は以下のように書きます:

val intent = Intent(this, DetailActivity::class.java).apply {
    putExtra("id", 123)
    putExtra("name", "Anna")
}
startActivity(intent)

問題点:

  • 文字列キー "id" "name"型安全でない
  • 毎回 Intent()putExtra() を繰り返す冗長さ
  • 型ミスは コンパイル時に検出不可

これを Kotlin の reified 型パラメータ × DSL 構文
次のように宣言的に書けるようにします:

startActivity<DetailActivity> {
    putExtra("id", 123)
    putExtra("name", "Anna")
}

2. reified 型パラメータと inline の関係

通常、ジェネリック型 T実行時に消去(Type Erasure) されるため、
Intent(this, T::class.java) のような呼び方は不可能です。

しかし Kotlin では inline + reified によって、
実行時にも型情報を保持できます:

inline fun <reified T> startActivity(context: Context) {
    val intent = Intent(context, T::class.java)
    context.startActivity(intent)
}

T::class.java が利用可能になるため、
DetailActivity の型を直接指定できるようになります。


3. 基本:型安全な startActivity()

inline fun <reified T : Activity> Context.startActivity(
    block: Intent.() -> Unit = {}
) {
    val intent = Intent(this, T::class.java).apply(block)
    startActivity(intent)
}

利用例:

startActivity<DetailActivity> {
    putExtra("id", 100)
    putExtra("name", "Anna")
}

ポイント

  • Intent.() -> Unit により、受容者付き関数型 DSL
  • inline + reified により、型を明示せずクラス参照を解決

4. Intent DSL の構成設計(Builderパターン化)

もう少し柔軟な DSL にするため、
IntentBuilder を定義してビルダー形式にします。

@DslMarker
annotation class IntentDsl

@IntentDsl
class IntentBuilder(private val context: Context, private val clazz: Class<out Activity>) {
    private val intent = Intent(context, clazz)

    fun extra(key: String, value: Any?) {
        when (value) {
            is Int -> intent.putExtra(key, value)
            is String -> intent.putExtra(key, value)
            is Boolean -> intent.putExtra(key, value)
            else -> throw IllegalArgumentException("Unsupported extra type: ${value?.javaClass}")
        }
    }

    fun build(): Intent = intent
}

inline fun <reified T : Activity> Context.intent(block: IntentBuilder.() -> Unit): Intent =
    IntentBuilder(this, T::class.java).apply(block).build()

利用:

val intent = intent<DetailActivity> {
    extra("id", 99)
    extra("name", "Anna")
}
startActivity(intent)

DSLマーカーを付与しておくことで、
スコープが衝突しない安全なビルダー構文が可能になります。


5. withReceiver + DSLマーカーで宣言的化

より宣言的に書けるように、
Intent 内でブロック構文を直接書ける DSL に拡張します。

inline fun <reified T : Activity> Context.start(block: IntentBuilder.() -> Unit) {
    intent<T>(block).also { startActivity(it) }
}

使用例:

start<DetailActivity> {
    extra("id", 123)
    extra("title", "Kotlin DSL入門")
}

6. Extras の型安全化(reified + sealed class)

キー名・型を文字列ではなく「型安全」に管理する仕組みを導入します。

sealed class ExtraKey<T>(val key: String)

object Id : ExtraKey<Int>("id")
object Name : ExtraKey<String>("name")

@IntentDsl
class SafeIntentBuilder(
    private val context: Context,
    private val clazz: Class<out Activity>
) {
    private val intent = Intent(context, clazz)

    fun <T> put(key: ExtraKey<T>, value: T) {
        when (value) {
            is Int -> intent.putExtra(key.key, value)
            is String -> intent.putExtra(key.key, value)
            else -> error("Unsupported type")
        }
    }

    fun build() = intent
}

inline fun <reified T : Activity> Context.startSafe(block: SafeIntentBuilder.() -> Unit) {
    SafeIntentBuilder(this, T::class.java).apply(block).build().also { startActivity(it) }
}

使用例:

startSafe<DetailActivity> {
    put(Id, 42)
    put(Name, "Anna")
}

これで "id""name" の typo によるミスがコンパイル時に防止可能


7. 応用:結果取得対応(ActivityResult API)

Kotlin DSL で registerForActivityResult も型安全にラップできます。

inline fun <reified T : Activity, reified R> ComponentActivity.registerForResult(
    crossinline onResult: (R) -> Unit
): ActivityResultLauncher<Intent> {
    return registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == Activity.RESULT_OK) {
            val data = result.data?.getSerializableExtra("result") as? R
            data?.let(onResult)
        }
    }
}

利用例:

val launcher = registerForResult<DetailActivity, String> { result ->
    Log.d("Result", "Activity returned: $result")
}

launcher.launch(intent<DetailActivity> {
    extra("id", 55)
})

まとめ — Intent DSL 設計のエッセンス

概念 内容
inline インライン展開でパフォーマンス・型保持を両立
reified 実行時にも型情報を利用可能 (T::class.java)
Intent.() -> Unit 受容者付き関数で宣言的なDSL構文
@DslMarker スコープ衝突を防ぎ、型安全化
ExtraKey<T> 型安全なキー定義で安全なデータ受け渡し
ActivityResult API DSLで結果ハンドリングも統一化

最終形:シンプルで安全な Intent DSL

startSafe<DetailActivity> {
    put(Id, 10)
    put(Name, "Anna")
}

これが Kotlin の 拡張関数 × inline × reified × DSL の威力です。
型安全・宣言的・再利用可能という3つの設計原則をすべて満たします。

0
0
0

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?