Compose Multiplatform 入门:跨平台 UI 开发实战

2024-05-22 · 28 min · 跨平台

Compose Multiplatform 是 JetBrains 基于 Jetpack Compose 打造的跨平台 UI 框架,让你可以用 Kotlin 和 Compose 编写一次代码,运行在 Android、iOS、Desktop 和 Web 上。

一、与其他框架对比

特性Compose MultiplatformFlutterReact Native
语言KotlinDartJavaScript
UI 渲染原生/SkiaSkia原生组件
与原生互操作无缝需要 Channel需要 Bridge

二、项目结构

my-kmp-app/
├── composeApp/
│   ├── src/
│   │   ├── commonMain/     # 共享代码
│   │   ├── androidMain/    # Android 特定
│   │   ├── iosMain/        # iOS 特定
│   │   └── desktopMain/    # Desktop 特定
├── iosApp/                 # iOS 宿主应用
└── build.gradle.kts

三、共享 UI

// commonMain/kotlin/App.kt
@Composable
fun App() {
    MaterialTheme {
        var count by remember { mutableStateOf(0) }
        
        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text("Count: $count")
            Button(onClick = { count++ }) {
                Text("Increment")
            }
            Text("Running on: ${getPlatform().name}")
        }
    }
}

四、expect/actual 机制

// commonMain - 声明
interface Platform {
    val name: String
}
expect fun getPlatform(): Platform

// androidMain - 实现
class AndroidPlatform : Platform {
    override val name = "Android ${android.os.Build.VERSION.SDK_INT}"
}
actual fun getPlatform(): Platform = AndroidPlatform()

// iosMain - 实现
class IOSPlatform : Platform {
    override val name = UIDevice.currentDevice.systemName()
}
actual fun getPlatform(): Platform = IOSPlatform()

五、网络请求(Ktor)

class ApiClient {
    private val httpClient = HttpClient {
        install(ContentNegotiation) {
            json(Json { ignoreUnknownKeys = true })
        }
    }
    
    suspend fun getUsers(): List<User> {
        return httpClient.get("https://api.example.com/users").body()
    }
}

六、ViewModel 共享

class HomeViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(HomeUiState())
    val uiState = _uiState.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            val users = apiClient.getUsers()
            _uiState.update { it.copy(users = users) }
        }
    }
}

// 使用
@Composable
fun HomeScreen(viewModel: HomeViewModel = viewModel { HomeViewModel() }) {
    val uiState by viewModel.uiState.collectAsState()
}

七、最佳实践

总结