0Pricing
Kotlin Academy · レッスン

プラットフォームAPI向けexpect/actual機構

共通コードでexpect宣言を行い、プラットフォームごとにactual実装を提供します。

「プラットフォームAPI向けexpect/actual機構」はCoddyKit上の無料Kotlin Academyレッスンです。 これはレッスン2/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはKotlin Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Kotlin Academyコースには全4レッスンが含まれています。

expect/actualパターン

expectはcommonMainでAPIコントラクトを宣言します。各プラットフォームはactual実装を提供します。コンパイラーは、すべてのターゲットにactualが存在することを検証します。

// commonMain:
expect fun platformName(): String

// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"

// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()

expect class

expect classは、commonMainでAPIを持つクラスを宣言します。各プラットフォームが完全な実装を提供します。

// commonMain:
expect class DateTime {
    fun format(pattern: String): String
    val timestamp: Long
}

// androidMain:
actual class DateTime {
    actual val timestamp = System.currentTimeMillis()
    actual fun format(pattern: String): String =
        java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}

expect object

expect objectは、コントラクトを持つシングルトンを宣言します。ロギング、設定、アナリティクスなど、プラットフォーム固有のユーティリティに便利です。

// commonMain:
expect object Logger {
    fun debug(msg: String)
    fun error(msg: String, cause: Throwable? = null)
}

// androidMain:
actual object Logger {
    actual fun debug(msg: String) = android.util.Log.d("App", msg)
    actual fun error(msg: String, cause: Throwable?) =
        android.util.Log.e("App", msg, cause)
}

トップレベルの expect val / fun

トップレベルのexpect valとexpect funはシンプルで、状態を持たないプラットフォームユーティリティに適しています。

// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String

// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()

// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()

デフォルト実装を持つ expect fun

Kotlin 1.9以降では、expect funにexpect fun f() { }のようなデフォルト実装を持たせることができます。プラットフォーム側では、これを上書きすることも、デフォルトを使うこともできます。

// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
    println("[DEFAULT] $msg") // used if no actual overrides
}

// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)

// iosMain (uses default):
// no actual needed — default is used

actual typealias

プラットフォームに必要なクラスがすでに存在する場合は、ラッパーを作成する代わりにactual typealiasを使って、そのクラスを参照します。

// commonMain:
expect class AtomicInt {
    fun get(): Int
    fun incrementAndGet(): Int
}

// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger

// iosMain:
actual class AtomicInt {
    private var value = 0
    actual fun get() = value
    actual fun incrementAndGet() = ++value
}

expect interface(間接的なパターン)

より柔軟にするには、commonMainでインターフェースを宣言し、プラットフォームのインスタンスを作成するファクトリに対してのみexpect/actualを使用します。

// commonMain:
interface FileStorage {
    suspend fun readFile(path: String): ByteArray
    suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage

// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()

// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()

expect/actualのテスト

commonTestでは共通APIをテストします。actual実装は、プラットフォーム固有のテストタスク(AndroidではtestDebugUnitTest、iOSではiosX64Test)の実行時に動作します。

// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
    @Test
    fun testUUID() {
        val uuid = generateUUID()
        assertNotNull(uuid)
        println("UUID: $uuid")
    }
}

よくある落とし穴

宣言したすべてのexpectには、設定済みのすべてのターゲットでactualが必要です。actualが不足していると、ビルドは失敗します。不足はコンパイル時に検出されます。

// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }

Expectアノテーションクラス

expect annotation classを使うと、プラットフォーム固有のアノテーションマッピングが可能になります。たとえば、共通の@Parcelizeに似たアノテーションをAndroidのParcelizeプラグインにマッピングできます。

// commonMain:
expect annotation class CommonParcelize()

// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize

実例: Keychain/Preferences

典型的な実例は、安全なストレージインターフェースのexpect/actualです。AndroidではEncryptedSharedPreferencesを、iOSではKeychainを使用します。

// commonMain:
expect class SecureStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
}

// androidMain:
actual class SecureStorage {
    private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
    actual fun save(key: String, value: String) { /* ... */ }
    actual fun get(key: String): String? = null // simplified
}

確認問題

expect宣言に、あるターゲット向けのactualがない場合、コンパイル時に何が起こりますか?

まとめ

expectはcommonMainでAPIを宣言します。actualはプラットフォーム実装を提供します。プラットフォームに必要な型がすでに存在する場合は、actual typealiasを使用します。actualの不足はコンパイル時に検出されるため、実行時に予期せぬ問題が発生することはありません。

よくある質問

「プラットフォームAPI向けexpect/actual機構」レッスンは無料ですか?

はい。「プラットフォームAPI向けexpect/actual機構」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Kotlin Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Kotlin Academyコースには全4レッスンが含まれています。

「プラットフォームAPI向けexpect/actual機構」で何を学びますか?

共通コードでexpect宣言を行い、プラットフォームごとにactual実装を提供します。 ブラウザで直接実行するハンズオンコードでKotlin Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Kotlin Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのKotlin Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン2/4です。

「プラットフォームAPI向けexpect/actual機構」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このKotlin Academyレッスンでコードを書いて実行できますか?

はい。すべてのKotlin Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. KMPプロジェクト構造:commonMain、androidMain、iosMain
  2. プラットフォームAPI向けexpect/actual機構
  3. Repository層とUse Case層の共有
  4. KoinによるKMPの依存性注入
← Kotlin Academyに戻る