プラットフォーム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 usedactual 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フィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- KMPプロジェクト構造:commonMain、androidMain、iosMain
- プラットフォームAPI向けexpect/actual機構
- Repository層とUse Case層の共有
- KoinによるKMPの依存性注入