Чтобы сохранить стабильный размер списка, я рендеринг 30 фиксированных слотов: реальные элементы в первом n, заполнители в остальных. ViewModel мутирует mutablestateListof на месте; Пользовательский интерфейс показывает эти слоты в Lazycolumn .
наблюдается: журналы показывают, что ViewModel мутирует слоты сразу после того, как я нажимаю на чип, но пользовательский интерфейс показывает изменение заметно (чувствует себя замороженным, затем догоняет). Я хочу подтвердить, неверна ли моя компоновская проводка (например, элементы (count =…) vs ements (list) , обнаружение tolist () и т. Д.) или если что -то еще блокирует. необходимо). < /p>
Код: Выделить всё
// MainActivity.kt
package com.example.minlag
import android.os.Bundle
import android.os.SystemClock
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.mapLatest
import kotlin.random.Random
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { App() }
}
}
@Composable
fun App(vm: BillboardViewModel = viewModel()) {
val chartId by vm.chartId.collectAsState()
val slots = vm.slots // SnapshotStateList
val listState = remember(chartId) { // per-chart scroll
androidx.compose.foundation.lazy.LazyListState()
}
Scaffold(
topBar = {
TopAppBar(title = { Text("MRE – 30 slots") },
actions = {
TextButton(onClick = { vm.setWindow(Window.WEEKLY) }) { Text("Weekly") }
TextButton(onClick = { vm.setWindow(Window.MONTHLY) }) { Text("Monthly") }
TextButton(onClick = { vm.setWindow(Window.QUARTERLY) }) { Text("Quarterly") }
}
)
}
) { padding ->
LazyColumn(modifier = Modifier.fillMaxSize().padding(padding), state = listState) {
items(
items = slots,
key = { it.index },
contentType = { slot -> if (slot.content is SlotContent.Real) 1 else 0 }
) { slot ->
when (val c = slot.content) {
is SlotContent.Real -> ListItem(
headlineContent = { Text("Rank ${c.item.rank}: ${c.item.title}") },
supportingContent = { Text("Artist ${c.item.artist}") }
)
SlotContent.Placeholder -> ListItem(
headlineContent = { Text("…loading…") }
)
}
Divider()
}
}
}
LaunchedEffect(slots) {
// When Compose sees slot list changes
println("UI saw slots @${SystemClock.uptimeMillis()} size=${slots.size}")
}
}
// ----------------- VM + fake domain -----------------
private const val SLOT_COUNT = 30
enum class Window { WEEKLY, MONTHLY, QUARTERLY }
data class UiItem(val id: Long, val rank: Int, val title: String, val artist: String)
sealed interface SlotContent {
data class Real(val item: UiItem) : SlotContent
data object Placeholder : SlotContent
}
data class Slot(val index: Int, val content: SlotContent)
class FakeDomain {
// Emits a "chart" (id + list) when window changes.
val selectedChart = MutableStateFlow(null)
suspend fun setWindow(w: Window) {
delay(50) // emulate DB/IO
val count = listOf(0, 7, 18, 30).random()
val items = List(count) { i ->
UiItem(id = i.toLong(), rank = i + 1, title = "${w.name} Song #$i", artist = "Artist $i")
}
selectedChart.value = Chart(id = w.ordinal.toLong(), items = items)
}
}
data class Chart(val id: Long, val items: List)
class BillboardViewModel : ViewModel() {
private val domain = FakeDomain()
private val _chartId = MutableStateFlow(null)
val chartId = _chartId
// Expose snapshot list directly (no toList())
private val _slots: SnapshotStateList = mutableStateListOf().apply {
repeat(SLOT_COUNT) { add(Slot(it, SlotContent.Placeholder)) }
}
val slots: SnapshotStateList get() = _slots
init {
// Start with weekly
viewModelScope.launch(Dispatchers.IO) { domain.setWindow(Window.WEEKLY) }
observeCharts()
}
fun setWindow(w: Window) {
viewModelScope.launch(Dispatchers.IO) { domain.setWindow(w) }
}
private fun observeCharts() {
val bg = viewModelScope + Dispatchers.Default
domain.selectedChart
.conflate()
.mapLatest { chart ->
_chartId.value = chart?.id
// CPU work off-main
chart?.items?.take(SLOT_COUNT).orEmpty()
}
.let { flow ->
bg.launch {
flow.collect { items ->
// Commit slot mutations on main for deterministic apply
withContext(Dispatchers.Main.immediate) {
val t = SystemClock.uptimeMillis()
for (i in 0 until SLOT_COUNT) {
_slots[i] = if (i < items.size)
Slot(i, SlotContent.Real(items[i]))
else
Slot(i, SlotContent.Placeholder)
}
println("VM mutated slots @$t (count=${items.size})")
}
}
}
}
}
}
Является ли моя подключение правильной для немедленного отменения при мутировании Mutablestatelistof по индексу?
Есть ли платы с элементами (список). Изменения в окне? Слот совершается на main.imdiate.
Подробнее здесь: https://stackoverflow.com/questions/797 ... statelisto
Мобильная версия