jpskill.com
🛠️ 開発・MCP コミュニティ

shared-coroutines

Kotlinマルチプラットフォームでコルーチンを共有し、プラットフォームごとのディスパッチャやFlowの共有、共通スコープパターンなどを設定することで、効率的な並行処理を実装するSkill。

📜 元の英語説明(参考)

Shared coroutines configuration for Kotlin Multiplatform. Platform dispatchers, Flow sharing, and common scope patterns.

🇯🇵 日本人クリエイター向け解説

一言でいうと

Kotlinマルチプラットフォームでコルーチンを共有し、プラットフォームごとのディスパッチャやFlowの共有、共通スコープパターンなどを設定することで、効率的な並行処理を実装するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o shared-coroutines.zip https://jpskill.com/download/10679.zip && unzip -o shared-coroutines.zip && rm shared-coroutines.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10679.zip -OutFile "$d\shared-coroutines.zip"; Expand-Archive "$d\shared-coroutines.zip" -DestinationPath $d -Force; ri "$d\shared-coroutines.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して shared-coroutines.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → shared-coroutines フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

KMP のための共有コルーチン

プラットフォームに適したディスパッチャを使用して、クロスプラットフォームの非同期操作のためにコルーチンを構成します。

依存関係

// build.gradle.kts
sourceSets {
    val commonMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:1.6.0")
        }
    }
    val androidMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
        }
    }
}

プラットフォームディスパッチャ

// commonMain/kotlin/coroutines/PlatformDispatcher.kt
interface PlatformDispatcher {
    val main: CoroutineDispatcher
    val io: CoroutineDispatcher
    val default: CoroutineDispatcher
    val unconfined: CoroutineDispatcher
}

expect object PlatformDispatchers : PlatformDispatcher

Android の実装

// androidMain/kotlin/coroutines/PlatformDispatcher.android.kt
actual object PlatformDispatchers : PlatformDispatcher {
    actual val main: CoroutineDispatcher = Dispatchers.Main
    actual val io: CoroutineDispatcher = Dispatchers.IO
    actual val default: CoroutineDispatcher = Dispatchers.Default
    actual val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
}

iOS の実装

// iosMain/kotlin/coroutines/PlatformDispatcher.ios.kt
actual object PlatformDispatchers : PlatformDispatcher {
    actual val main: CoroutineDispatcher =
        NSQueueDispatcher(dispatch_get_main_queue())

    actual val io: CoroutineDispatcher =
        DispatchQueueDispatcher(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.toLong(), 0))

    actual val default: CoroutineDispatcher =
        DispatchQueueDispatcher(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.toLong(), 0))

    actual val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
}

class NSQueueDispatcher(
    private val queue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(queue) { block.run() }
    }
}

class DispatchQueueDispatcher(
    private val queue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(queue) { block.run() }
    }
}

共通スコープ

// commonMain/kotlin/coroutines/Scopes.kt
// アプリケーションスコープ - アプリのライフタイムの間存続します
class ApplicationScope : CoroutineScope {
    private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = job + PlatformDispatchers.main +
        CoroutineExceptionHandler { _, throwable ->
            println("Unhandled exception: $throwable")
        }

    fun cancel() {
        job.cancel()
    }
}

// ビュースコープ - ビュー/プレゼンターのライフサイクルに関連付けられます
class ViewScope : CoroutineScope {
    private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = job + PlatformDispatchers.main

    fun cancel() {
        job.cancelChildren()
        job.cancel()
    }
}

// ユースケーススコープ - 単一の操作用
fun useCaseScope(): CoroutineScope =
    CoroutineScope(
        SupervisorJob() +
        PlatformDispatchers.io +
        CoroutineExceptionHandler { _, throwable ->
            println("Use case failed: $throwable")
        }
    )

Flow の共有パターン

UI 状態のための StateFlow

// commonMain/kotlin/coroutines/StateFlowExtensions.kt
fun <T> StateFlow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.WhileSubscribed(5000),
    replay: Int = 1
): StateFlow<T> {
    return this
}

// マルチプラットフォームキャッシュ
fun <T> Flow<T>.cachedIn(
    scope: CoroutineScope
): Flow<T> = cached(scope)

プラットフォームのライフサイクルへの接続

// commonMain/kotlin/coroutines/LifecycleAwareFlow.kt
fun <T> Flow<T>.collectWhileActive(
    lifecycle: Lifecycle,
    context: CoroutineContext = PlatformDispatchers.main,
    collector: suspend (value: T) -> Unit
): Job {
    val job = SupervisorJob()
    val scope = CoroutineScope(job + context)

    lifecycle.subscribe { event ->
        when (event) {
            Lifecycle.Event.ON_RESUME -> {
                // コレクションを開始
                scope.launch {
                    collect(collector)
                }
            }
            Lifecycle.Event.ON_PAUSE -> {
                // コレクションをキャンセル
                job.cancelChildren()
            }
            else -> { /* 無視 */ }
        }
    }

    return job
}

// ビューがアクティブな状態での単純な繰り返し
fun <T> Flow<T>.repeatOnViewActive(
    isActive: () -> Boolean,
    minInterval: Duration = 1.seconds
): Flow<T> = channelFlow {
    var lastValue: T? = null
    val collectorJob = launch {
        collect { value ->
            lastValue = value
            if (isActive()) {
                send(value)
            }
        }
    }

    while (isActive()) {
        lastValue?.let { send(it) }
        delay(minInterval)
    }

    collectorJob.cancel()
}

リポジトリ層のコルーチン

// commonMain/kotlin/data/repository/BaseRepository.kt
abstract class BaseRepository {

    protected suspend fun <T> execute(
        dispatcher: CoroutineDispatcher = PlatformDispatchers.io,
        block: suspend () -> T
    ): Result<T> = withContext(dispatcher) {
        try {
            Result.success(block())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    protected suspend fun <T> flowFrom(
        dispatcher: CoroutineDispatcher = PlatformDispatchers.io,
        block: suspend () -> T
    ): Flow<Result<T>> = flow {
        emit(withContext(dispatcher) {
            try {
                Result.success(block())
            } catch (e: CancellationException) {
                throw e
            } catch (e
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Shared Coroutines for KMP

Configure coroutines for cross-platform async operations with platform-appropriate dispatchers.

Dependencies

// build.gradle.kts
sourceSets {
    val commonMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
            implementation("org.jetbrains.kotlinx:kotlinx-datetime:1.6.0")
        }
    }
    val androidMain by getting {
        dependencies {
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
        }
    }
}

Platform Dispatchers

// commonMain/kotlin/coroutines/PlatformDispatcher.kt
interface PlatformDispatcher {
    val main: CoroutineDispatcher
    val io: CoroutineDispatcher
    val default: CoroutineDispatcher
    val unconfined: CoroutineDispatcher
}

expect object PlatformDispatchers : PlatformDispatcher

Android Implementation

// androidMain/kotlin/coroutines/PlatformDispatcher.android.kt
actual object PlatformDispatchers : PlatformDispatcher {
    actual val main: CoroutineDispatcher = Dispatchers.Main
    actual val io: CoroutineDispatcher = Dispatchers.IO
    actual val default: CoroutineDispatcher = Dispatchers.Default
    actual val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
}

iOS Implementation

// iosMain/kotlin/coroutines/PlatformDispatcher.ios.kt
actual object PlatformDispatchers : PlatformDispatcher {
    actual val main: CoroutineDispatcher =
        NSQueueDispatcher(dispatch_get_main_queue())

    actual val io: CoroutineDispatcher =
        DispatchQueueDispatcher(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.toLong(), 0))

    actual val default: CoroutineDispatcher =
        DispatchQueueDispatcher(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.toLong(), 0))

    actual val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
}

class NSQueueDispatcher(
    private val queue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(queue) { block.run() }
    }
}

class DispatchQueueDispatcher(
    private val queue: dispatch_queue_t
) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(queue) { block.run() }
    }
}

Common Scopes

// commonMain/kotlin/coroutines/Scopes.kt
// Application scope - lives for app lifetime
class ApplicationScope : CoroutineScope {
    private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = job + PlatformDispatchers.main +
        CoroutineExceptionHandler { _, throwable ->
            println("Unhandled exception: $throwable")
        }

    fun cancel() {
        job.cancel()
    }
}

// View scope - tied to a view/presenter lifecycle
class ViewScope : CoroutineScope {
    private val job = SupervisorJob()

    override val coroutineContext: CoroutineContext
        get() = job + PlatformDispatchers.main

    fun cancel() {
        job.cancelChildren()
        job.cancel()
    }
}

// Use case scope - for single operations
fun useCaseScope(): CoroutineScope =
    CoroutineScope(
        SupervisorJob() +
        PlatformDispatchers.io +
        CoroutineExceptionHandler { _, throwable ->
            println("Use case failed: $throwable")
        }
    )

Flow Sharing Patterns

StateFlow for UI State

// commonMain/kotlin/coroutines/StateFlowExtensions.kt
fun <T> StateFlow<T>.shareIn(
    scope: CoroutineScope,
    started: SharingStarted = SharingStarted.WhileSubscribed(5000),
    replay: Int = 1
): StateFlow<T> {
    return this
}

// Multiplatform caching
fun <T> Flow<T>.cachedIn(
    scope: CoroutineScope
): Flow<T> = cached(scope)

Connect to Platform Lifecycle

// commonMain/kotlin/coroutines/LifecycleAwareFlow.kt
fun <T> Flow<T>.collectWhileActive(
    lifecycle: Lifecycle,
    context: CoroutineContext = PlatformDispatchers.main,
    collector: suspend (value: T) -> Unit
): Job {
    val job = SupervisorJob()
    val scope = CoroutineScope(job + context)

    lifecycle.subscribe { event ->
        when (event) {
            Lifecycle.Event.ON_RESUME -> {
                // Start collecting
                scope.launch {
                    collect(collector)
                }
            }
            Lifecycle.Event.ON_PAUSE -> {
                // Cancel collection
                job.cancelChildren()
            }
            else -> { /* ignore */ }
        }
    }

    return job
}

// Simple repeat on view active
fun <T> Flow<T>.repeatOnViewActive(
    isActive: () -> Boolean,
    minInterval: Duration = 1.seconds
): Flow<T> = channelFlow {
    var lastValue: T? = null
    val collectorJob = launch {
        collect { value ->
            lastValue = value
            if (isActive()) {
                send(value)
            }
        }
    }

    while (isActive()) {
        lastValue?.let { send(it) }
        delay(minInterval)
    }

    collectorJob.cancel()
}

Repository Layer Coroutines

// commonMain/kotlin/data/repository/BaseRepository.kt
abstract class BaseRepository {

    protected suspend fun <T> execute(
        dispatcher: CoroutineDispatcher = PlatformDispatchers.io,
        block: suspend () -> T
    ): Result<T> = withContext(dispatcher) {
        try {
            Result.success(block())
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    protected suspend fun <T> flowFrom(
        dispatcher: CoroutineDispatcher = PlatformDispatchers.io,
        block: suspend () -> T
    ): Flow<Result<T>> = flow {
        emit(withContext(dispatcher) {
            try {
                Result.success(block())
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                Result.failure(e)
            }
        })
    }

    // For single-shot database operations
    protected suspend fun <T> databaseQuery(
        block: suspend () -> T
    ): T = withContext(PlatformDispatchers.io) {
        block()
    }

    // For network operations with timeout
    protected suspend fun <T> networkCall(
        timeout: Duration = 30.seconds,
        block: suspend () -> T
    ): T = withTimeout(timeout) {
        withContext(PlatformDispatchers.io) {
            block()
        }
    }
}

// Usage
class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) : BaseRepository() {

    suspend fun getUser(id: String): Result<User> = execute {
        remoteDataSource.getUser(id).also { user ->
            localDataSource.saveUser(user)
        }
    }

    fun observeUser(id: String): Flow<Result<User>> = flowFrom {
        localDataSource.getUser(id) ?: remoteDataSource.getUser(id).also {
            localDataSource.saveUser(it)
        }
    }
}

Use Case Patterns

// commonMain/kotlin/domain/base/BaseUseCase.kt
abstract class BaseUseCase<in P, R> {
    suspend operator fun invoke(parameters: P): Result<R> {
        return try {
            execute(parameters).let { Result.success(it) }
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    protected abstract suspend fun execute(parameters: P): R
}

// No parameters
abstract class BaseNoParamsUseCase<R> {
    suspend operator fun invoke(): Result<R> {
        return try {
            execute().let { Result.success(it) }
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    protected abstract suspend fun execute(): R
}

// Flow use case
abstract class FlowUseCase<in P, R> {
    operator fun invoke(parameters: P): Flow<Result<R>> = flow {
        try {
            emit(Result.success(execute(parameters)))
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            emit(Result.failure(e))
        }
    }

    protected abstract suspend fun execute(parameters: P): R
}

// Usage
class GetUsersUseCase(
    private val repository: UserRepository
) : BaseNoParamsUseCase<List<User>>() {
    override suspend fun execute(): List<User> {
        return repository.getAllUsers()
    }
}

class ObserveUserUseCase(
    private val repository: UserRepository
) : FlowUseCase<String, User>() {
    override suspend fun execute(parameters: String): User {
        return repository.getUser(parameters)
    }

    fun flow(id: String): Flow<Result<User>> {
        return repository.observeUser(id)
    }
}

Worker for Background Tasks

// commonMain/kotlin/coroutines/BackgroundWorker.kt
class BackgroundWorker(
    private val scope: CoroutineScope
) {
    private val workers = mutableMapOf<String, Job>()

    fun start(
        key: String,
        interval: Duration,
        dispatcher: CoroutineDispatcher = PlatformDispatchers.io,
        work: suspend () -> Unit
    ) {
        workers[key]?.cancel()
        workers[key] = scope.launch(dispatcher) {
            while (isActive) {
                try {
                    work()
                } catch (e: Exception) {
                    println("Worker $key failed: $e")
                }
                delay(interval)
            }
        }
    }

    fun stop(key: String) {
        workers[key]?.cancel()
        workers.remove(key)
    }

    fun stopAll() {
        workers.values.forEach { it.cancel() }
        workers.clear()
    }
}

Testing Helpers

// commonTest/kotlin/coroutines/TestDispatcher.kt
class TestPlatformDispatcher : PlatformDispatcher {
    private val testDispatcher = StandardTestDispatcher()

    override val main: CoroutineDispatcher = testDispatcher
    override val io: CoroutineDispatcher = testDispatcher
    override val default: CoroutineDispatcher = testDispatcher
    override val unconfined: CoroutineDispatcher = testDispatcher

    fun advanceUntilIdle() {
        testDispatcher.scheduler.advanceUntilIdle()
    }
}

// Usage in tests
class UserRepositoryTest {
    private lateinit var testDispatcher: TestPlatformDispatcher

    @BeforeTest
    fun setup() {
        testDispatcher = TestPlatformDispatcher()
    }

    @Test
    fun `fetches user from remote`() = runTest {
        val repository = UserRepository(mockRemote, mockLocal)
        val result = repository.getUser("123")

        testDispatcher.advanceUntilIdle()

        assertTrue(result.isSuccess)
    }
}

Best Practices

✅ DO

// ✅ Use platform dispatchers through abstraction
withContext(PlatformDispatchers.io) { ... }

// ✅ Scope coroutines to lifecycle
scope.launch { ... }

// ✅ Handle cancellation
try {
    longRunningTask()
} catch (e: CancellationException) {
    throw e  // Re-throw cancellation
}

// ✅ Use Flow for state
val users: StateFlow<List<User>>

// ✅ Provide meaningful coroutine names
scope.launch(Dispatchers.Default + CoroutineName("FetchUsers")) { ... }

❌ DON'T

// ❌ Don't use GlobalScope
GlobalScope.launch { ... }  // ❌

// ❌ Don't block threads
runBlocking { ... }  // ❌ in production code

// ❌ Don't swallow exceptions
launch {
    throw RuntimeException()
}  // ❌ unhandled

// ❌ Don't use Dispatchers.Main directly
// Use PlatformDispatchers.main for KMP

Remember: Coroutines are your async foundation. Abstract platform differences, scope properly, and handle cancellation.