kotlin Jetpack合成行为异常

ryoqjall  于 2023-02-09  发布在  Kotlin
关注(0)|答案(1)|浏览(180)

我正在使用JSON资产文件将一些数据作为列表加载到应用程序中。

fun onGeneral(context: Context): List<List<General>> {
    try {
        val json = context.assets.open("general.json").bufferedReader().use { it.readText() }
        val list = Gson().fromJson<List<General>>(json, object : TypeToken<List<General>>() {}.type)
        return listOf(
            list.subList(0, 30),
            list.subList(30, 60),
            list.subList(60, 90),
            list.subList(90, 120),
            list.subList(120, 150),
            list.subList(150, 180),
            list.subList(180, 210),
            list.subList(210, 240),
            list.subList(240, 270),
            list.subList(270, 300)
        )
    } catch (e: Exception) {
        return emptyList()
    }
}

由于列表很大,我将其划分为子列表,结果,我在屏幕中得到了一个子列表列表:

var items = remember { mutableStateListOf<Item>() }

val context = LocalContext.current
val general = onGeneral(context)[0] // The first entry

然后,我将一般列表添加到项目中:

general.forEach { items.add(Item(it)) }

在屏幕的某个地方我显示了计数器

Text("Item ${position + 1} of ${items.size}")

问题是当进入屏幕时,文本应显示

Item 1 of 30

但它显示了一个不停的计数器

它一直在计数。
此外,我添加了一个日志,看看有什么正在发生:

Log.wtf("TST_Output", "Say Hello, when screen entered!")

输出:

....
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
E/TST_Output: Say Hello, when screen entered!
....

它无止境...
我理解,日志输出是由此行引起的

general.forEach { items.add(Item(it)) }

但是,当forEarch循环完成后,不应该再重复一次,为什么它一直在运行?
我尝试了一下,找到了一个不用重复的解决方案:

var items: MutableList<Item> = remember { mutableStateListOf() }

val temp = arrayListOf<Item>()
general.forEach { temp.add(Item(it)) }
items = temp

items变量指定为MutableList<Item>似乎可以工作。
另外,不管上面的问题,我将日志行添加到另一个屏幕,在那里我不检索任何数据。在这种情况下,日志行应该触发一次,但这里的日志:

......
D/ViewRootImpl@e8d5d7b[MainActivity]: reportDrawFinished (fn: -1) 
E/TST_Output: Say Hello, when screen entered!     <!!!!!!!!!!----------!!!!!!!!
D/CompatibilityChangeReporter: Compat change id reported: 171228096; UID 11701; state: ENABLED
I/ViewRootImpl@e8d5d7b[MainActivity]: Relayout returned: old=(0,0,1440,3040) new=(0,0,1440,3040) req=(1440,3040)0 dur=6 res=0x1 s={true 500687993696} ch=false fn=2
I/OpenGLRenderer: Davey! duration=793ms; Flags=0, FrameTimelineVsyncId=8404895, IntendedVsync=215478232101981, Vsync=215478448768639, InputEventId=0, HandleInputStart=215478449436685, AnimationStart=215478449438954, PerformTraversalsStart=215478749464762, DrawStart=215478991676915, FrameDeadline=215478265435313, FrameInterval=215478449418954, FrameStartTime=16666666, SyncQueued=215479007135992, SyncStart=215479007221531, IssueDrawCommandsStart=215479007348107, SwapBuffers=215479020229915, FrameCompleted=215479025430838, DequeueBufferDuration=20731, QueueBufferDuration=1477346, GpuCompleted=215479025430838, SwapBuffersCompleted=215479022577992, DisplayPresentTime=1554322967633985549, 
E/TST_Output: Say Hello, when screen entered!      <!!!!!!!!!!----------!!!!!!!!
I/ViewRootImpl@e8d5d7b[MainActivity]: MSG_WINDOW_FOCUS_CHANGED 1 1
D/InputMethodManager: startInputInner - Id : 0
I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus
D/InputMethodManager: startInputInner - Id : 0
W/System: A resource failed to call close. 
E/TST_Output: Say Hello, when screen entered!      <!!!!!!!!!!----------!!!!!!!!
......

如您所见,该行(标记为<!!!!!!!!!!----------!!!!!!!!)被触发了三次。
wong是什么?为什么喷气背包组合的行为如此奇怪?我做错了什么吗?
任何帮助都会很有帮助。非常感谢。
编辑:
屏幕代码:

@Composable
@Preview(showBackground = true)
@OptIn(ExperimentalPagerApi::class)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
fun GeneralScreen(controller: NavController = rememberNavController()) {

    val context = LocalContext.current

    var items: MutableList<Item> = remember { mutableStateListOf() }
    var list by remember { mutableStateOf(1) }
    var position by remember { mutableStateOf(0) }

    val general = onGeneral(context)[0]

    val temp = arrayListOf<Item>()
    general.forEach { 
        temp.add(Item(it)) 
    }
    items = temp

    Log.wtf("TST_Output", "Say Hello, when screen entered!")

    Column(Modifier.fillMaxWidth().padding(10.dp), Arrangement.spacedBy(8.dp), Alignment.CenterHorizontally) {
        Text("General list $list", fontSize = 18.sp)
        Text("Item ${position + 1} of ${items.size}", fontSize = 16.sp, color = Color.Gray)
    }
}
uemypmqf

uemypmqf1#

项目列表增长的原因是因为您在可组合范围内向列表添加项目。因此,每次屏幕重新组合时,您的项目都将重新添加到列表中。这同样适用于您之前添加的日志条目。这也将记录每次屏幕重新组合时的情况。
您可以通过将逻辑封装在LaunchedEffect块中来防止这种情况。此块仅在其键更改时才在重新组合后运行。以下是一个示例:

var items: MutableList<Item> = remember { mutableStateListOf() }
val context = LocalContext.current

val general = onGeneral(context)[0]
LaunchedEffect(Unit) {
    Log.wtf("TST_Output", "Say Hello, when screen entered!")

    general.forEach { items.add(Item(it)) }
}

由于我们传递Unit作为键给LaunchedEffect,这将只运行一次,因为Unit不会改变。如果你想在每次general改变时运行LaunchedEffect内的代码,你可以使用general作为键,如下所示:

var items: MutableList<Item> = remember { mutableStateListOf() }
val context = LocalContext.current

val general = onGeneral(context)[0]
LaunchedEffect(general) {
    Log.wtf("TST_Output", "Say Hello, when screen entered!")
    
    // clearing the list since we are re-adding the items
    items.clear()
    
    general.forEach { items.add(Item(it)) }
}

我希望这是有帮助的,如果你想了解更多关于副作用,你可以阅读this

相关问题