はじめに
型安全に
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つの設計原則をすべて満たします。