te')); return $arr; } /* 遍历用户所有主题 * @param $uid 用户ID * @param int $page 页数 * @param int $pagesize 每页记录条数 * @param bool $desc 排序方式 TRUE降序 FALSE升序 * @param string $key 返回的数组用那一列的值作为 key * @param array $col 查询哪些列 */ function thread_tid_find_by_uid($uid, $page = 1, $pagesize = 1000, $desc = TRUE, $key = 'tid', $col = array()) { if (empty($uid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('uid' => $uid), array('tid' => $orderby), $page, $pagesize, $key, $col); return $arr; } // 遍历栏目下tid 支持数组 $fid = array(1,2,3) function thread_tid_find_by_fid($fid, $page = 1, $pagesize = 1000, $desc = TRUE) { if (empty($fid)) return array(); $orderby = TRUE == $desc ? -1 : 1; $arr = thread_tid__find($cond = array('fid' => $fid), array('tid' => $orderby), $page, $pagesize, 'tid', array('tid', 'verify_date')); return $arr; } function thread_tid_delete($tid) { if (empty($tid)) return FALSE; $r = thread_tid__delete(array('tid' => $tid)); return $r; } function thread_tid_count() { $n = thread_tid__count(); return $n; } // 统计用户主题数 大数量下严谨使用非主键统计 function thread_uid_count($uid) { $n = thread_tid__count(array('uid' => $uid)); return $n; } // 统计栏目主题数 大数量下严谨使用非主键统计 function thread_fid_count($fid) { $n = thread_tid__count(array('fid' => $fid)); return $n; } ?>android - Jetpack Compose State Capture in long-lived lambda - Stack Overflow
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

android - Jetpack Compose State Capture in long-lived lambda - Stack Overflow

programmeradmin4浏览0评论

In the Test Composable, action is a variable of type mutableStateOf, initialized with ::a. Then, in the LaunchedEffect, action is changed to ::b.

However, because LaunchedEffect(Unit) is by default bound to the Unit key in Compose, it means that it triggers when the Composable is first executed, and during this process, it captures a reference to perform the specified action. As a result, changes to action do not immediately reflect in ExecuteInLongLambda because action was captured by Compose during the initial suspension.

After the delay in LaunchedEffect completes, even though action has been updated to ::b, ExecuteInLongLambda still executes the initially captured action (i.e., ::a) from the first render, so ::a is executed in the end. I know this should be solved using rememberUpdatedState(), My doubt is that I created a lambda which capture State (i.e counter) and pass the lambda as action2 to ExecuteInLongLiveLambda. When counter changes, the lambda that captures counter should be recreated. Why does it behave differently from action1? Shouldn't the printed counter be 0? Can someone explain in detail what is happening?

fun a() {
  Log.e("MainActivity", "a")
}

fun b() {
  Log.e("MainActivity", "b")
}

@Composable
fun Test() {
  var action by remember { mutableStateOf(::a) }
  var counter by remember { mutableIntStateOf(0) }

  LaunchedEffect(Unit) {
    delay(1000)
    action = ::b
    counter++
  }

  ExecuteInLongLiveLambda(
    action1 = action,
    action2 = {
      // read State<Int>
      Log.e("MainActivity", "counter = $counter")
    }
  )
}

@Composable
fun ExecuteInLongLiveLambda(
  action1: () -> Unit,
  action2: () -> Unit,
) {
  LaunchedEffect(Unit) {
    delay(2000)
    action1() // a
    action2() // counter = 1
  }
}

In the Test Composable, action is a variable of type mutableStateOf, initialized with ::a. Then, in the LaunchedEffect, action is changed to ::b.

However, because LaunchedEffect(Unit) is by default bound to the Unit key in Compose, it means that it triggers when the Composable is first executed, and during this process, it captures a reference to perform the specified action. As a result, changes to action do not immediately reflect in ExecuteInLongLambda because action was captured by Compose during the initial suspension.

After the delay in LaunchedEffect completes, even though action has been updated to ::b, ExecuteInLongLambda still executes the initially captured action (i.e., ::a) from the first render, so ::a is executed in the end. I know this should be solved using rememberUpdatedState(), My doubt is that I created a lambda which capture State (i.e counter) and pass the lambda as action2 to ExecuteInLongLiveLambda. When counter changes, the lambda that captures counter should be recreated. Why does it behave differently from action1? Shouldn't the printed counter be 0? Can someone explain in detail what is happening?

fun a() {
  Log.e("MainActivity", "a")
}

fun b() {
  Log.e("MainActivity", "b")
}

@Composable
fun Test() {
  var action by remember { mutableStateOf(::a) }
  var counter by remember { mutableIntStateOf(0) }

  LaunchedEffect(Unit) {
    delay(1000)
    action = ::b
    counter++
  }

  ExecuteInLongLiveLambda(
    action1 = action,
    action2 = {
      // read State<Int>
      Log.e("MainActivity", "counter = $counter")
    }
  )
}

@Composable
fun ExecuteInLongLiveLambda(
  action1: () -> Unit,
  action2: () -> Unit,
) {
  LaunchedEffect(Unit) {
    delay(2000)
    action1() // a
    action2() // counter = 1
  }
}
Share Improve this question edited Feb 17 at 18:55 tyg 15.6k4 gold badges35 silver badges48 bronze badges asked Feb 17 at 14:12 bqliangbqliang 4634 silver badges12 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

TL;DR:

Due to the delegation you only capture the underlying MutableState, not the Int. And this always stays the same object, only its content is changed. When you actually access the content (after two seconds), the value will be 1, not 0.


Detailed answer, this is what happens:

First composition

  1. action is set to ::a and counter is set to 0.
  2. LaunchedEffect is started, which executs with a second delay.
  3. ExecuteInLongLiveLambda is called, with these parameters:
    • action1: ::a
    • action2: Captures counter which is a MutableState with value 0
  4. ExecuteInLongLiveLambda starts a LaunchedEffect that captures the parameters from 3. and executes the lambda. The first thing it does is waiting for 2 seconds, so nothing more happens right now.

So far so good. Now, after a second the LaunchedEffect from 2. sets action to ::b and counter to 1. Since both are backed by a MutableState that is accessed by Test, a recomposition is triggered.

Second composition

  1. Since action and counter are remembered they keep their current value (i.e. ::b and 1).
  2. The LaunchedEffect is skipped because the key (the first parameter, Unit) didn't change.
  3. ExecuteInLongLiveLambda is called again, with these parameters:
    • action1: ::b
    • action2: Captures counter which is a MutableState with value 1
  4. The LaunchedEffect in ExecuteInLongLiveLambda is skipped because the key didn't change.

This LaunchedEffect (the one from ExecuteInLongLiveLambda) never sees the two updated parameters. The coroutine that was internally launched on first composition is still running (still waiting for two seconds where only one has passed by now).

After another second passes the two seconds this LaunchedEffect was waiting are over and the rest from the lambda is executed. Keep in mind, this is the lambda with the parameters 3. from the first composition.

  1. action1() is executed. Since that was ::a when the LaunchedEffect was created, that is executed which logs a.
  2. action2() is executed. It didn't change during recomposition, so even when the LaunchedEffect hadn't captured it it would behave the same. The counter may have changed, but the lambda is still the same.
    As seen in 3. (both versions), counter was captured. And this is where it gets interesting: Due to the by delegation this looks like an Int, but it actually is a MutableState. Under the hood the delegation calls getValue() and setValue() respectively everytime you access it. This means, what was really captured was the MutableState and that did never change. Only its value changed. But the first time that is accessed is when the lambda actually is executed. And the then current value is retrieved. At this point of time it is 1, not 0.

Alternatives

This behavior changes if you actually capture the Int. Let's assume you have an additional variable like this in Test:

val counterValue = counter

When you access this counterValue in the action2 lambda, then you will actually capture the Int 0 (not a MutableState with value 0), and that is also what will be logged later on.

Now, you might say that action is also a delegate, like counter, so that should behave the same way. That's not the case, though, because you never access action in a lambda. You just pass it as a parameter to ExecuteInLongLiveLambda, and only that parameter is then captured in the ExecuteInLongLiveLambda's LaunchedEffect. ExecuteInLongLiveLambda never sees a MutableState, only ::a and ::b directly.

发布评论

评论列表(0)

  1. 暂无评论