文章说明 本文内容基于Kotlin官方文档总结翻译而成,旨在为自己学习Kotlin使用。文中的代码示例、技术概念和最佳实践均参考自Kotlin官方资源和相关技术文档,如需获取最新或更详细的信息,建议查阅官方文档。
1. Kotlin与Java互操作 从Kotlin调用Java代码 Kotlin设计之初就考虑了与Java的互操作性。几乎所有Java代码都可以在Kotlin中自然地调用,无需特殊处理。
import java.util.*fun demo (source: List <Int >) { val list = ArrayList<Int >() for (item in source) { list.add(item) } for (i in 0. .source.size - 1 ) { list[i] = source[i] } }
Java中的Getter和Setter 遵循Java getter和setter命名规范的方法在Kotlin中会表示为属性。这些被称为合成属性。
import java.util.Calendarfun calendarDemo () { val calendar = Calendar.getInstance() if (calendar.firstDayOfWeek == Calendar.SUNDAY) { calendar.firstDayOfWeek = Calendar.MONDAY } if (!calendar.isLenient) { calendar.isLenient = true } }
注意:如果Java类只有setter方法,在Kotlin中不会显示为属性,因为Kotlin不支持只有setter的属性。
Java数组处理 Kotlin中的数组是不变的(invariant),而Java中则是协变的(covariant)。这意味着Kotlin不允许将Array<String>
赋值给Array<Any>
,以避免潜在的运行时失败。
对于原始类型数组,Kotlin提供了特殊的类(IntArray
、DoubleArray
、CharArray
等)以避免装箱/拆箱操作带来的性能开销。
fun passArrayToJava (javaObj: JavaArrayExample ) { val array = intArrayOf(0 , 1 , 2 , 3 ) javaObj.removeIndices(array) } fun passVarargToJava (javaObj: JavaArrayExample ) { val array = intArrayOf(0 , 1 , 2 , 3 ) javaObj.removeIndicesVarArg(*array) }
处理已检查异常 在Kotlin中,所有异常都是未检查的(unchecked),编译器不强制捕获任何异常。因此,当调用声明已检查异常的Java方法时,Kotlin不要求执行任何额外操作。
fun render (list: List <*>, to: Appendable ) { for (item in list) { to.append(item.toString()) } }
从Java调用Kotlin代码 Kotlin代码可以从Java中相当流畅地使用。本节介绍Java开发者在使用Kotlin编写的代码时应该了解的细节。
属性 Kotlin属性编译为以下Java元素:
一个getter方法,名称按照JavaBean约定派生(get<PropertyName>
)
一个setter方法(仅针对var
属性),名称按照JavaBean约定派生(set<PropertyName>
)
一个私有字段,与属性名称相同(仅针对具有backing field的属性)
例如,var firstName: String
会被编译为以下Java声明:
private String firstName;public String getFirstName () { return firstName; } public void setFirstName (String firstName) { this .firstName = firstName; }
包级函数 在Kotlin文件org.example/app.kt
中声明的所有函数和属性,包括扩展函数,都会编译为一个名为org.example.AppKt
的Java类的静态方法。
package org.examplefun processData (data : String ) : String { return "Processed: $data " }
从Java中调用:
String result = org.example.AppKt.processData("raw data" );
顶层属性 顶层属性会被编译为静态字段和相应的静态getter/setter方法:
package org.exampleconst val MAX_COUNT = 100 var debugMode = false
在Java中:
int max = org.example.ConstantsKt.MAX_COUNT;boolean debug = org.example.ConstantsKt.getDebugMode();org.example.ConstantsKt.setDebugMode(true );
互操作性的最佳实践
注解使用 :利用@JvmName
、@JvmStatic
和@JvmOverloads
等注解来改善Kotlin代码在Java中的可用性。
命名文件 :如果希望生成的Java类有特定名称,可使用@file:JvmName
注解:
@file:JvmName ("StringHelper" )package org.examplefun process (s: String ) : String = "Processed: $s "
在Java中调用:
String result = StringHelper.process("data" );
处理默认参数 :Kotlin的函数默认参数在Java中不可用。使用@JvmOverloads
注解生成重载方法:
@JvmOverloads fun createUser (name: String , email: String = "" , active: Boolean = true ) { }
在Java中可以调用以下重载版本:
createUser("John" , "john@example.com" , false ); createUser("John" , "john@example.com" ); createUser("John" );
处理命名参数 :Java不支持命名参数,因此在设计API时,注意参数顺序的逻辑性,或提供构建器模式作为替代方案。
2. Kotlin DSL构建 领域特定语言(Domain-Specific Language, DSL)是一种为特定问题领域设计的编程语言。Kotlin的特性使其非常适合创建内部DSL。
Lambda与接收者函数类型 Kotlin DSL的核心是Lambda表达式和接收者函数类型。接收者函数类型允许在Lambda内部调用接收者对象的方法而无需显式限定符。
class HTML { fun body () { } } fun html (init : HTML .() -> Unit ) : HTML { val html = HTML() html.init () return html } val result = html { body() }
构建HTML DSL示例 这个完整示例展示了如何构建一个用于生成HTML的DSL:
class Tag (val name: String) { val children = mutableListOf<Tag>() val attributes = mutableMapOf<String, String>() fun text (value: String ) { children.add(Text(value)) } override fun toString () : String { val attributesStr = attributes.entries.joinToString(" " ) { "${it.key} =\"${it.value} \"" } val openTag = if (attributesStr.isEmpty()) name else "$name $attributesStr " return if (children.isEmpty()) { "<$openTag />" } else { val childrenStr = children.joinToString("" ) "<$openTag >$childrenStr </$name >" } } } class Text (val text: String) : Tag("" ) { override fun toString () = text } @DslMarker annotation class HtmlTagMarker @HtmlTagMarker class HTML : Tag ("html" ) { fun head (init : Head .() -> Unit ) { val head = Head() head.init () children.add(head) } fun body (init : Body .() -> Unit ) { val body = Body() body.init () children.add(body) } } class Head : Tag ("head" ) { fun title (init : Title .() -> Unit ) { val title = Title() title.init () children.add(title) } } class Title : Tag ("title" )class Body : Tag ("body" ) { fun h1 (init : H1 .() -> Unit ) { val h1 = H1() h1.init () children.add(h1) } fun p (init : P .() -> Unit ) { val p = P() p.init () children.add(p) } fun a (href: String , init : A .() -> Unit ) { val a = A() a.attributes["href" ] = href a.init () children.add(a) } } class H1 : Tag ("h1" )class P : Tag ("p" )class A : Tag ("a" )fun html (init : HTML .() -> Unit ) : HTML { val html = HTML() html.init () return html }
使用上述DSL生成HTML:
val page = html { head { title { text("DSL示例" ) } } body { h1 { text("Kotlin DSL示例" ) } p { text("这是使用Kotlin DSL生成的HTML" ) } a(href = "https://kotlinlang.org" ) { text("Kotlin官网" ) } } } println(page)
输出结果:
<html > <head > <title > DSL示例</title > </head > <body > <h1 > Kotlin DSL示例</h1 > <p > 这是使用Kotlin DSL生成的HTML</p > <a href ="https://kotlinlang.org" > Kotlin官网</a > </body > </html >
@DslMarker注解 @DslMarker
注解有助于避免DSL中的歧义问题。当在嵌套Lambda中使用时,它可以防止隐式访问外部接收者:
@DslMarker annotation class ConfigurationDsl @ConfigurationDsl class ServerConfig { var host: String = "localhost" var port: Int = 8080 } @ConfigurationDsl class AuthConfig { var username: String = "" var password: String = "" } @ConfigurationDsl class AppConfig { lateinit var server: ServerConfig lateinit var auth: AuthConfig fun server (init : ServerConfig .() -> Unit ) { server = ServerConfig().apply(init ) } fun auth (init : AuthConfig .() -> Unit ) { auth = AuthConfig().apply(init ) } } fun appConfig (init : AppConfig .() -> Unit ) : AppConfig { return AppConfig().apply(init ) } val config = appConfig { server { host = "example.com" port = 9000 } auth { username = "admin" password = "password" } }
4. Kotlin多平台开发 Kotlin多平台允许在不同平台之间共享代码,无论是移动设备、Web还是桌面应用。
多平台项目结构 多平台项目包含几个关键组件:
目标平台 :代码编译到的平台,如JVM、JS、iOS等。
源集 :源文件的集合,每个都有自己的依赖项和编译器选项。
通用源集 :共享给所有平台的代码。
平台特定源集 :只针对特定平台的代码。
基本项目结构示例:
kotlin-multiplatform-sample/ ├── src/ │ ├── commonMain/ // 所有平台共享的代码 │ │ └── kotlin/ │ ├── jvmMain/ // JVM平台特定代码 │ │ └── kotlin/ │ ├── jsMain/ // JavaScript平台特定代码 │ │ └── kotlin/ │ └── iosMain/ // iOS平台特定代码 │ └── kotlin/ └── build.gradle.kts
预期声明与实际声明 Kotlin多平台使用”预期和实际”声明机制,允许在共享代码中声明预期API,但在各个平台中提供不同的实现。
expect class PlatformLogger () { fun log (message: String ) } actual class PlatformLogger { actual fun log (message: String ) { println("[JVM] $message " ) } } actual class PlatformLogger { actual fun log (message: String ) { console.log("[JS] $message " ) } } actual class PlatformLogger { actual fun log (message: String ) { NSLog("[iOS] $message " ) } }
共享业务逻辑:
class UserManager { private val logger = PlatformLogger() fun login (username: String , password: String ) { logger.log("登录尝试: $username " ) } }
多平台库依赖 多平台项目可以依赖其他多平台库,Kotlin会自动解析并添加适当的平台特定部分:
kotlin { sourceSets { val commonMain by getting { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" ) implementation("io.ktor:ktor-client-core:2.2.4" ) } } val jvmMain by getting { dependencies { implementation("io.ktor:ktor-client-cio:2.2.4" ) } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-darwin:2.2.4" ) } } } }
跨平台网络请求示例 下面是一个使用Ktor客户端的跨平台网络请求实现:
import io.ktor.client.*import io.ktor.client.request.*import io.ktor.client.statement.*import kotlinx.coroutines.*expect fun createHttpClient () : HttpClientclass ApiService { private val client = createHttpClient() suspend fun fetchData (url: String ) : String { val response: HttpResponse = client.get (url) return response.bodyAsText() } } import io.ktor.client.engine.cio.*actual fun createHttpClient () : HttpClient { return HttpClient(CIO) { } } import io.ktor.client.engine.darwin.*actual fun createHttpClient () : HttpClient { return HttpClient(Darwin) { } }
5. Kotlin与Android开发 Android开发自2019年Google I/O大会以来一直以Kotlin为优先语言。超过60%的专业Android开发者使用Kotlin作为主要开发语言。
Android中Kotlin的优势
简洁性 :Kotlin代码比Java更简洁,减少了样板代码。
空安全 :通过可空类型和非空类型,减少了NullPointerException
。
Kotlin扩展 :允许为现有类添加新功能,而无需继承或使用设计模式。
协程支持 :简化异步编程和后台任务处理。
互操作性 :与现有Java代码完全兼容。
Android KTX :专为Android设计的Kotlin扩展库。
29.2 Android项目中使用Kotlin 在Android项目中使用Kotlin,首先需要在项目的build.gradle
文件中配置Kotlin:
plugins { id 'org.jetbrains.kotlin.android' version '1.8.0' apply false } plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' } android { }
Android视图绑定与Kotlin 视图绑定提供了一种类型安全的方式来与XML布局交互:
class MainActivity : AppCompatActivity () { private lateinit var binding: ActivityMainBinding override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.welcomeText.text = "Hello Kotlin!" binding.actionButton.setOnClickListener { showMessage("Button clicked" ) } } private fun showMessage (message: String ) { Toast.makeText(this , message, Toast.LENGTH_SHORT).show() } }
Android中的Kotlin协程 在Android中使用协程需要添加依赖:
dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" }
使用协程处理网络请求和UI更新:
class UserRepository (private val apiService: ApiService) { suspend fun fetchUser (userId: String ) : User { return withContext(Dispatchers.IO) { apiService.getUser(userId) } } } class UserViewModel (private val repository: UserRepository) : ViewModel() { private val _userData = MutableLiveData<User>() val userData: LiveData<User> = _userData fun loadUser (userId: String ) { viewModelScope.launch { try { val user = repository.fetchUser(userId) _userData.value = user } catch (e: Exception) { } } } } class UserActivity : AppCompatActivity () { private lateinit var binding: ActivityUserBinding private val viewModel: UserViewModel by viewModels() override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) binding = ActivityUserBinding.inflate(layoutInflater) setContentView(binding.root) viewModel.userData.observe(this ) { user -> binding.userName.text = user.name binding.userEmail.text = user.email } viewModel.loadUser("user123" ) } }
Jetpack Compose与Kotlin Jetpack Compose是Android的现代化UI工具包,专为Kotlin设计:
class ComposeActivity : ComponentActivity () { override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContent { MyAppTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Greeting("Kotlin" ) } } } } } @Composable fun Greeting (name: String ) { Column( modifier = Modifier .padding(16. dp) .fillMaxWidth() ) { Text( text = "Hello $name !" , style = MaterialTheme.typography.headlineMedium ) Spacer(modifier = Modifier.height(8. dp)) Button(onClick = { }) { Text("Click Me" ) } } } @Preview @Composable fun GreetingPreview () { MyAppTheme { Greeting("Preview" ) } }
6. 项目实战与最佳实践 代码组织与架构 构建Kotlin项目时的架构选择:
**MVVM (Model-View-ViewModel)**:
Model:数据层,包含业务逻辑和数据源
View:UI层,展示数据并报告用户操作
ViewModel:连接Model和View,处理UI逻辑和状态管理
class UserRepository (private val apiService: ApiService, private val database: UserDatabase) { suspend fun getUser (id: String ) : User { } } class UserViewModel (private val repository: UserRepository) : ViewModel() { private val _userState = MutableStateFlow<UserState>(UserState.Loading) val userState = _userState.asStateFlow() fun loadUser (id: String ) { viewModelScope.launch { _userState.value = UserState.Loading try { val user = repository.getUser(id) _userState.value = UserState.Success(user) } catch (e: Exception) { _userState.value = UserState.Error(e.message ?: "Unknown error" ) } } } } sealed class UserState { object Loading : UserState() data class Success (val user: User) : UserState() data class Error (val message: String) : UserState() } class UserActivity : AppCompatActivity () { private val viewModel: UserViewModel by viewModels() override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.userState.collect { state -> when (state) { is UserState.Loading -> showLoading() is UserState.Success -> showUser(state.user) is UserState.Error -> showError(state.message) } } } } } }
**清洁架构 (Clean Architecture)**:分离关注点,提高可测试性和可维护性
interface UserRepository { suspend fun getUser (id: String ) : Result<User> } class GetUserUseCase (private val repository: UserRepository) { suspend operator fun invoke (id: String ) : Result<User> { return repository.getUser(id) } } class UserRepositoryImpl ( private val apiService: ApiService, private val database: UserDatabase ) : UserRepository { override suspend fun getUser (id: String ) : Result<User> { } } class UserViewModel (private val getUserUseCase: GetUserUseCase) : ViewModel() { }
测试策略 全面的测试策略应包括多个层次:
单元测试 :测试独立组件和函数
class CalculatorTest { @Test fun `adding two numbers returns their sum`() { val calculator = Calculator() val result = calculator.add(2 , 3 ) assertEquals(5 , result) } }
使用MockK进行模拟 :Kotlin专用的模拟库
class UserViewModelTest { @MockK lateinit var getUserUseCase: GetUserUseCase private lateinit var viewModel: UserViewModel @Before fun setup () { MockKAnnotations.init (this ) viewModel = UserViewModel(getUserUseCase) } @Test fun `loading user emits correct states`() = runTest { val user = User("1" , "Test User" ) coEvery { getUserUseCase("1" ) } returns Result.success(user) viewModel.loadUser("1" ) coVerify { getUserUseCase("1" ) } assertEquals(UserState.Success(user), viewModel.userState.value) } }
集成测试 :测试组件之间的交互
UI测试 :使用Espresso或Compose测试API
@RunWith(AndroidJUnit4::class) class UserActivityTest { @get:Rule val composeRule = createComposeRule() @Test fun userInfoIsDisplayed () { val user = User("1" , "Test User" ) composeRule.setContent { UserInfoScreen(user = user) } composeRule.onNodeWithText("Test User" ).assertIsDisplayed() } }
性能优化 优化Kotlin代码性能的关键技术:
适当使用懒加载 :使用lazy
和lateinit
延迟初始化昂贵的资源
val expensiveResource by lazy { println("Initializing expensive resource" ) "Resource data" }
使用序列处理大集合 :对于大型集合的链式操作,使用sequence
val result = list.filter { it > 10 }.map { it * 2 }.take(5 )val efficientResult = list.asSequence() .filter { it > 10 } .map { it * 2 } .take(5 ) .toList()
内联函数 :减少Lambda调用的开销
inline fun <T> withLock (lock: Lock , action: () -> T ) : T { lock.lock() try { return action() } finally { lock.unlock() } }
使用值类 :针对简单包装类的性能优化
@JvmInline value class UserId (val value: String) fun processUser (userId: UserId ) { }
安全编码实践
利用Kotlin的空安全系统 :
fun getUserName (user: User ?) : String { return user?.name ?: "Unknown" } user?.let { println("User found: ${it.name} " ) }
使用密封类处理有限状态 :
sealed class Result <out T > { data class Success <T >(val data : T) : Result<T>() data class Error (val exception: Exception) : Result<Nothing >() object Loading : Result<Nothing >() } fun processResult (result: Result <User >) { when (result) { is Result.Success -> displayUser(result.data ) is Result.Error -> showError(result.exception) is Result.Loading -> showLoading() } }
避免使用非空断言(!!) :除非绝对确定值不为空
使用扩展函数增强安全性 :
fun String?.isValidEmail () : Boolean { if (this == null ) return false return matches("[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}" .toRegex()) }
多平台项目案例 创建一个简单的跨平台待办事项应用,展示Kotlin Multiplatform的实际应用:
package modeldata class TodoItem ( val id: String, val title: String, val description: String = "" , val isCompleted: Boolean = false ) package repositoryimport model.TodoItemimport kotlinx.coroutines.flow.Flowexpect class TodoRepository () { suspend fun addTodo (todo: TodoItem ) suspend fun updateTodo (todo: TodoItem ) suspend fun deleteTodo (id: String ) suspend fun getTodo (id: String ) : TodoItem? fun observeTodos () : Flow<List<TodoItem>> } package repositoryimport model.TodoItemimport kotlinx.coroutines.flow.Flowimport kotlinx.coroutines.flow.flowimport android.content.Contextimport androidx.room.我将继续编写第六部分:Kotlin实战应用中的多平台项目案例部分,继续保持Markdown格式输出,不使用第一人称和第三人称。 ```kotlin package repositoryimport model.TodoItemimport kotlinx.coroutines.flow.Flowimport android.content.Contextimport androidx.room.*import kotlinx.coroutines.flow.map@Entity(tableName = "todos" ) data class TodoEntity ( @PrimaryKey val id: String, val title: String, val description: String, val isCompleted: Boolean ) @Dao interface TodoDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTodo (todo: TodoEntity ) @Update suspend fun updateTodo (todo: TodoEntity ) @Query("DELETE FROM todos WHERE id = :id" ) suspend fun deleteTodo (id: String ) @Query("SELECT * FROM todos WHERE id = :id LIMIT 1" ) suspend fun getTodo (id: String ) : TodoEntity? @Query("SELECT * FROM todos ORDER BY isCompleted, title" ) fun observeTodos () : Flow<List<TodoEntity>> } @Database(entities = [TodoEntity::class], version = 1) abstract class TodoDatabase : RoomDatabase () { abstract fun todoDao () : TodoDao } actual class TodoRepository (private val context: Context) { private val database: TodoDatabase by lazy { Room.databaseBuilder( context, TodoDatabase::class .java, "todo-database" ).build() } private val dao: TodoDao by lazy { database.todoDao() } actual suspend fun addTodo (todo: TodoItem ) { dao.insertTodo(todo.toEntity()) } actual suspend fun updateTodo (todo: TodoItem ) { dao.updateTodo(todo.toEntity()) } actual suspend fun deleteTodo (id: String ) { dao.deleteTodo(id) } actual suspend fun getTodo (id: String ) : TodoItem? { return dao.getTodo(id)?.toModel() } actual fun observeTodos () : Flow<List<TodoItem>> { return dao.observeTodos().map { entities -> entities.map { it.toModel() } } } private fun TodoItem.toEntity () : TodoEntity { return TodoEntity(id, title, description, isCompleted) } private fun TodoEntity.toModel () : TodoItem { return TodoItem(id, title, description, isCompleted) } } package repositoryimport model.TodoItemimport kotlinx.coroutines.flow.Flowimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.updateimport platform.Foundation.*actual class TodoRepository { private val todos = MutableStateFlow<List<TodoItem>>(emptyList()) private val userDefaults = NSUserDefaults.standardUserDefaults init { loadFromStorage() } actual suspend fun addTodo (todo: TodoItem ) { todos.update { currentList -> currentList + todo } saveToStorage() } actual suspend fun updateTodo (todo: TodoItem ) { todos.update { currentList -> currentList.map { if (it.id == todo.id) todo else it } } saveToStorage() } actual suspend fun deleteTodo (id: String ) { todos.update { currentList -> currentList.filter { it.id != id } } saveToStorage() } actual suspend fun getTodo (id: String ) : TodoItem? { return todos.value.find { it.id == id } } actual fun observeTodos () : Flow<List<TodoItem>> { return todos } private fun saveToStorage () { val todoList = todos.value val data = NSMutableArray() todoList.forEach { todo -> val dict = NSMutableDictionary() dict["id" ] = todo.id dict["title" ] = todo.title dict["description" ] = todo.description dict["isCompleted" ] = if (todo.isCompleted) 1 else 0 data .addObject(dict) } userDefaults.setObject(data , "todos" ) userDefaults.synchronize() } private fun loadFromStorage () { val data = userDefaults.arrayForKey("todos" ) ?: return val loadedTodos = mutableListOf<TodoItem>() for (i in 0 until data .count.toInt()) { val dict = data .objectAtIndex(i.toULong()) as NSDictionary val id = dict["id" ] as String val title = dict["title" ] as String val description = dict["description" ] as String val isCompleted = (dict["isCompleted" ] as Int ) == 1 loadedTodos.add(TodoItem(id, title, description, isCompleted)) } todos.value = loadedTodos } } package viewmodelimport kotlinx.coroutines.CoroutineScopeimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.flow.MutableStateFlowimport kotlinx.coroutines.flow.StateFlowimport kotlinx.coroutines.flow.updateimport kotlinx.coroutines.launchimport model.TodoItemimport repository.TodoRepositoryimport kotlin.random.Randomclass TodoViewModel (private val repository: TodoRepository) { private val scope = CoroutineScope(Dispatchers.Main) private val _uiState = MutableStateFlow<TodoListState>(TodoListState.Loading) val uiState: StateFlow<TodoListState> = _uiState init { scope.launch { repository.observeTodos().collect { todos -> _uiState.value = TodoListState.Success(todos) } } } fun addTodo (title: String , description: String ) { if (title.isBlank()) return val newTodo = TodoItem( id = generateId(), title = title, description = description ) scope.launch { repository.addTodo(newTodo) } } fun toggleTodoCompleted (id: String ) { scope.launch { val todo = repository.getTodo(id) ?: return @launch repository.updateTodo(todo.copy(isCompleted = !todo.isCompleted)) } } fun deleteTodo (id: String ) { scope.launch { repository.deleteTodo(id) } } private fun generateId () : String { return Random.nextInt(1000000 ).toString() } } sealed class TodoListState { object Loading : TodoListState() data class Success (val todos: List<TodoItem>) : TodoListState() data class Error (val message: String) : TodoListState() }
完成上述多平台项目的实现后,可以为Android和iOS创建相应的UI。下面是Android平台的Compose UI示例:
package uiimport androidx.compose.foundation.layout.*import androidx.compose.foundation.lazy.LazyColumnimport androidx.compose.foundation.lazy.itemsimport androidx.compose.material3.*import androidx.compose.runtime.*import androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.unit.dpimport kotlinx.coroutines.flow.collectimport model.TodoItemimport viewmodel.TodoListStateimport viewmodel.TodoViewModel@Composable fun TodoListScreen (viewModel: TodoViewModel ) { val uiState by viewModel.uiState.collectAsState() Scaffold( topBar = { TopAppBar( title = { Text("Todo List" ) } ) }, floatingActionButton = { FloatingActionButton( onClick = { } ) { Text("+" ) } } ) { paddingValues -> Box( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { when (val state = uiState) { is TodoListState.Loading -> { CircularProgressIndicator( modifier = Modifier.align(Alignment.Center) ) } is TodoListState.Success -> { TodoList( todos = state.todos, onToggleTodo = { viewModel.toggleTodoCompleted(it) }, onDeleteTodo = { viewModel.deleteTodo(it) } ) } is TodoListState.Error -> { Text( text = state.message, modifier = Modifier.align(Alignment.Center) ) } } } } } @Composable fun TodoList ( todos: List <TodoItem >, onToggleTodo: (String ) -> Unit , onDeleteTodo: (String ) -> Unit ) { LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues(16. dp) ) { items(todos) { todo -> TodoItemRow( todo = todo, onToggle = { onToggleTodo(todo.id) }, onDelete = { onDeleteTodo(todo.id) } ) Spacer(modifier = Modifier.height(8. dp)) } } } @Composable fun TodoItemRow ( todo: TodoItem , onToggle: () -> Unit , onDelete: () -> Unit ) { Card( modifier = Modifier.fillMaxWidth() ) { Row( modifier = Modifier .fillMaxWidth() .padding(16. dp), verticalAlignment = Alignment.CenterVertically ) { Checkbox( checked = todo.isCompleted, onCheckedChange = { onToggle() } ) Column( modifier = Modifier .weight(1f ) .padding(start = 8. dp) ) { Text( text = todo.title, style = MaterialTheme.typography.titleMedium ) if (todo.description.isNotBlank()) { Spacer(modifier = Modifier.height(4. dp)) Text( text = todo.description, style = MaterialTheme.typography.bodyMedium ) } } IconButton(onClick = onDelete) { Icon( imageVector = Icons.Default.Delete, contentDescription = "Delete" ) } } } }
下面是共享业务逻辑和特定平台UI的完整结构:
kotlin-multiplatform-todo/ ├── src/ │ ├── commonMain/ │ │ └── kotlin/ │ │ ├── model/ │ │ │ └── TodoItem.kt │ │ ├── repository/ │ │ │ └── TodoRepository.kt │ │ └── viewmodel/ │ │ └── TodoViewModel.kt │ ├── androidMain/ │ │ └── kotlin/ │ │ ├── repository/ │ │ │ └── TodoRepository.kt │ │ └── ui/ │ │ └── TodoListScreen.kt │ └── iosMain/ │ └── kotlin/ │ ├── repository/ │ │ └── TodoRepository.kt │ └── TodoAPI.kt // 导出给Swift代码使用的API └── build.gradle.kts
Swift端可以这样调用共享逻辑:
import UIKitimport shared class TodoListViewController : UIViewController , UITableViewDelegate , UITableViewDataSource { private let tableView = UITableView () private var todos: [TodoItem ] = [] private let viewModel: TodoViewModel private var observer: Ktor_ioCloseable ? init () { let repository = TodoRepository () viewModel = TodoViewModel (repository: repository) super .init (nibName: nil , bundle: nil ) } required init? (coder : NSCoder ) { fatalError ("init(coder:) has not been implemented" ) } override func viewDidLoad () { super .viewDidLoad() title = "Todo List" setupTableView() observeTodos() navigationItem.rightBarButtonItem = UIBarButtonItem ( barButtonSystemItem: .add, target: self , action: #selector (addTodoTapped) ) } private func setupTableView () { view.addSubview(tableView) tableView.delegate = self tableView.dataSource = self tableView.register(TodoCell .self , forCellReuseIdentifier: "TodoCell" ) tableView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint .activate([ tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } private func observeTodos () { observer = viewModel.uiState.watch { [weak self ] state in if let state = state as? TodoListState .Success { self ? .todos = state.todos DispatchQueue .main.async { self ? .tableView.reloadData() } } } } @objc private func addTodoTapped () { let alert = UIAlertController (title: "Add Todo" , message: nil , preferredStyle: .alert) alert.addTextField { textField in textField.placeholder = "Title" } alert.addTextField { textField in textField.placeholder = "Description" } let addAction = UIAlertAction (title: "Add" , style: .default) { [weak self ] _ in guard let title = alert.textFields? [0 ].text, ! title.isEmpty else { return } let description = alert.textFields? [1 ].text ?? "" self ? .viewModel.addTodo(title: title, description: description) } let cancelAction = UIAlertAction (title: "Cancel" , style: .cancel) alert.addAction(addAction) alert.addAction(cancelAction) present(alert, animated: true ) } func tableView (_ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int { return todos.count } func tableView (_ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "TodoCell" , for: indexPath) as! TodoCell let todo = todos[indexPath.row] cell.configure(with: todo) cell.onToggleCompletion = { [weak self ] in self ? .viewModel.toggleTodoCompleted(id: todo.id) } return cell } func tableView (_ tableView : UITableView , commit editingStyle : UITableViewCell .EditingStyle , forRowAt indexPath : IndexPath ) { if editingStyle == .delete { let todo = todos[indexPath.row] viewModel.deleteTodo(id: todo.id) } } } class TodoCell : UITableViewCell { private let titleLabel = UILabel () private let descriptionLabel = UILabel () private let completionButton = UIButton () var onToggleCompletion: (() -> Void )? override init (style : UITableViewCell .CellStyle , reuseIdentifier : String ?) { super .init (style: style, reuseIdentifier: reuseIdentifier) setupViews() } required init? (coder : NSCoder ) { fatalError ("init(coder:) has not been implemented" ) } private func setupViews () { completionButton.addTarget(self , action: #selector (completionButtonTapped), for: .touchUpInside) } func configure (with todo : TodoItem ) { titleLabel.text = todo.title descriptionLabel.text = todo.description let image = todo.isCompleted ? UIImage (systemName: "checkmark.circle.fill" ) : UIImage (systemName: "circle" ) completionButton.setImage(image, for: .normal) } @objc private func completionButtonTapped () { onToggleCompletion? () } }
参考链接