kotlin kmp 副作用函数 effect

在 Kotlin Multiplatform (KMP) Compose 中,"effect functions"(或"effect handlers")是专门的可组合函数,用于在 UI 中管理副作用。

在 Compose 中,可组合函数应该是"纯"的和声明式的。这意味着它们应该理想地只接受输入并生成 UI,而不引起其作用域之外的任何变化。然而,现实世界中的应用程序通常需要与"外部世界"进行交互,例如:

  • 网络请求:从 API 获取数据。
  • 数据库操作:保存或加载数据。
  • 日志/分析:将数据发送到外部服务。
  • 管理生命周期依赖的资源:注册/注销监听,器管理订阅。
  • 触发一次性 UI 事件:显示一个 Snackbar,导航到另一个屏幕。
  • 更新非 Compose 状态:与 ViewModel 或其他非 Compose 状态持有者交互。

这些操作被称为"副作用",因为它们改变了应用程序在即时 UI 渲染过程之外的状态。如果处理不当,它们可能会导致不可预测的行为、错误或性能问题。

为什么需要 Effect Functions?

Compose 的重组模型意味着可组合函数可能会频繁且以任意顺序被调用。如果将副作用直接放入普通的可组合函数中,它们可能会被重复执行或在不适当的时间执行,从而导致问题。Effect functions 提供了一种受控且具有生命周期感知能力的方式来执行这些副作用。

KMP Compose 中最常见的 effect functions:

  1. LaunchedEffect
  • 用途:启动一个与可组合生命周期绑定的协程(异步操作),并依赖于一组"keys"。

  • 何时使用:当需要在可组合进入组合时执行挂起函数(例如,网络请求、数据库调用),或者当特定"key"(状态变量或参数)发生变化时。如果 key 发生变化,LaunchedEffect 启动的前一个协程将自动取消,并启动一个新的协程。

  • 示例代码:

    复制代码
      @Composable
      fun UserProfileScreen(userId: String) {
          var userData by remember { mutableStateOf<UserData?>(null) }
      
          LaunchedEffect(key1 = userId) {
              userData = fetchUserData(userId)
          }
      
          if (userData != null) {
              Text(text = "Name: ${userData?.name}")
              Text(text = "Email: ${userData?.email}")
          } else {
              CircularProgressIndicator()
          }
      }
      
      suspend fun fetchUserData(userId: String): UserData {
          // 模拟网络请求
          delay(1000)
          return UserData(name = "John Doe", email = "[email protected]")
      }
      
      data class UserData(val name: String, val email: String)```
    1. DisposableEffect
    • 用途:执行与可组合生命周期和 keys 相关联的设置和清理操作。
    • 何时使用:当需要管理需要显式清理的资源时。它提供了一个 onDispose 块,当可组合离开组合或者其 keys 发生变化时(意味着 effect 被"处置",可能会设置一个新的 effect)执行。
    • 示例代码:

@Composable

fun LocationScreen() {

var location by remember { mutableStateOf<String?>(null) }

复制代码
    DisposableEffect(Unit) {
        val listener = object : LocationListener {
            override fun onLocationChanged(loc: Location) {
                location = "${loc.latitude}, ${loc.longitude}"
            }
        }

        // 注册监听器
        registerLocationListener(listener)

        onDispose {
            // 注销监听器
            unregisterLocationListener(listener)
        }
    }

    if (location != null) {
        Text(text = "Current Location: $location")
    } else {
        Text(text = "No location available")
    }
}
  1. SideEffect
  • 用途:在可组合函数成功重组时运行非挂起代码。

  • 何时使用:在成功重组后,将 Compose 状态与外部、非 Compose 管理的对象同步。它确保代码在 UI 更新后运行。

  • 示例代码:

    @Composable

    fun LoggingScreen() {

    var counter by remember { mutableStateOf(0) }

    复制代码
      SideEffect {
          log("Counter value is now $counter")
      }
    
      Button(onClick = { counter++ }) {
          Text(text = "Increment")
      }

    }

    fun log(message: String) {

    println("LOG: $message")

    }```

  1. rememberCoroutineScope
  • 用途:获取一个与可组合生命周期绑定的 CoroutineScope。

  • 何时使用:当需要从 Compose 作用域之外的回调(例如,Button 的 onClick lambda)中启动协程,但仍然希望当可组合离开组合时取消协程。可以在回调中手动调用 .launch { ... }。

  • 示例代码:

    @Composable

    fun DelayedActionScreen() {

    var message by remember { mutableStateOf<String?>(null) }

    val scope = rememberCoroutineScope()

    复制代码
      Button(onClick = {
          scope.launch {
              delay(2000)
              message = "Action completed after 2 seconds"
          }
      }) {
          Text(text = "Perform Delayed Action")
      }
    
      if (message != null) {
          Text(text = message!!)
      }

    }

  1. produceState
  • 用途:将非 Compose 可观察状态(例如,基于回调的 API,Android 中的 Flow 或 LiveData)转换为 Compose State,以便其他可组合函数可以观察它。

  • 何时使用:当需要将现有的命令式 API 或数据流集成到 Compose UI 中时。它启动一个协程来随时间更新 State。

  • 示例代码:

    @Composable
    fun DataScreen() {
    val data = produceState(initialValue = "Loading...") {
    val flow = fetchDataFlow()
    collect {
    value = it
    }
    }

    复制代码
          Text(text = data.value)
      }
      
      fun fetchDataFlow(): Flow<String> {
          return flow {
              delay(1000)
              emit("Data loaded")
          }
      }```
    1. derivedStateOf
    • 用途:从其他 State 对象创建一个 State,但只有当派生值实际发生变化时才会重组,而不是只要底层状态发生变化就重组。
    • 何时使用:当基于其他状态有复杂的计算或转换,并且只想在派生值的结果发生变化时才触发重组时,用于性能优化。
    • 示例代码:

    @Composable
    fun DerivedStateScreen() {
    var x by remember { mutableStateOf(0) }
    var y by remember { mutableStateOf(0) }

    复制代码
      val derivedState = derivedStateOf {
          x + y
      }
    
      Text(text = "Derived State: ${derivedState.value}")
    
      Button(onClick = { x++ }) {
          Text(text = "Increment x")
      }
    
      Button(onClick = { y++ }) {
          Text(text = "Increment y")
      }

    }

    1. snapshotFlow
    • 用途:将 Compose State 对象转换为 Kotlin Flow。
    • 何时使用:当想利用 Kotlin Flows 的强大功能(例如,map、filter、debounce 等操作符)与 Compose State 时。
    • 示例代码:

@Composable

fun DebouncedSearchScreen() {

var query by remember { mutableStateOf("") }

复制代码
    val flow = snapshotFlow { query }
        .debounce(300)
        .collect { 
            performSearch(it)
        }

    TextField(value = query, onValueChange = { query = it })
}

fun performSearch(query: String) {
    println("Performing search for: $query")
}
复制代码
相关推荐
yzpyzp5 小时前
Kotlin的MutableList和ArrayList区别
android·kotlin
帅次7 小时前
Flutter Container 组件详解
android·flutter·ios·小程序·kotlin·iphone·xcode
帅次12 小时前
Flutter setState() 状态管理详细使用指南
android·flutter·ios·小程序·kotlin·android studio·iphone
移动开发者1号16 小时前
Compose列表项动画实现指南
android·kotlin
移动开发者1号16 小时前
Compose与View系统互操作方案
android·kotlin
纳于大麓1 天前
Kotlin基础语法一
android·开发语言·kotlin
KotlinKUG贵州2 天前
Spring开发,从Kotlin开始
spring boot·spring·kotlin
移动开发者1号2 天前
Android中Activity、Task与Process的关系
android·kotlin
移动开发者1号2 天前
Activity onCreate解析
android·kotlin