Lilly
06/12/2020, 10:18 PMScannerScreenContent
ist called twice and everytime val state by viewModel.device.observeAsState()
is triggered, ScannerScreenContent
is called again which leads to a list with always 1 item. Does someone have an idea?Zach Klippenstein (he/him) [MOD]
06/12/2020, 10:28 PMviewModel.device
work? Does it just emit every time there’s a new device?Lilly
06/12/2020, 10:34 PMval device: LiveData<BluetoothDeviceWrapper>
get() = bluetoothManager.device
Zach Klippenstein (he/him) [MOD]
06/12/2020, 10:45 PMobserveAsState()
isn’t really ideal. This would be a great use case for launchInComposition
though. Basically you could do something like:
// Key the coroutine on the device Observable, so if the observable changes for some reason you'll resubscribe.
launchInComposition(viewModel.device) {
// Now you're in a coroutine that will run
// continuously across recompositions.
viewModel.device.asFlow().collect { device ->
modelList.add(device)
}
}
This would replace lines 22-28 of your snippet.
https://developer.android.com/reference/kotlin/androidx/compose/package-summary#launchincomposition_1Lilly
06/12/2020, 10:49 PMZach Klippenstein (he/him) [MOD]
06/12/2020, 10:50 PMLilly
06/12/2020, 10:53 PMbodyContent = {
Log.d("Debug", "This is called twice!")
}
Zach Klippenstein (he/him) [MOD]
06/12/2020, 11:14 PMlaunchInComposition
snippet.Lilly
06/13/2020, 12:01 AM@Composable
fun ScannerScreenContent(
onItemAction: () -> Unit
): @Composable() (Modifier) -> Unit = { modifier ->
val viewModel = ScannerViewModelAmbient.current
val context = ContextAmbient.current as MainActivity
val modelList = modelListOf<BluetoothDeviceWrapper>()
launchInComposition(viewModel.device) {
viewModel.device.observe(context, Observer {
Log.d("Found", "here")
modelList.add(it)
})
}
AdapterModelListComponent(modelList, modifier, onItemAction)
}
but the AdapterList is not updating on new devices. modelList is not empty.val modelList = modelListOf<BluetoothDeviceWrapper>()
launchInComposition(modelList) {
viewModel.device.asFlow().collect { device ->
modelList.add(device)
Log.d("Found", "size: ${modelList.size}")
}
}
Now it works. I had to change
launchInComposition(viewModel.device)
to
launchInComposition(modelList)
.
To avoid duplicates I just write
if (modelList.contains(device)) return@collect
right?Zach Klippenstein (he/him) [MOD]
06/13/2020, 2:05 AMif (device in modelList)
, but that’s really just a style matter.modelListOf()
in a remember
, so
val modelList = remember { mutableListOf<BluetoothDeviceWrapper>() }
ModelList
on every compose pass, so I’m not sure why that would work at alllaunchInComposition
, since that would restart the coroutine every time the list instance changesLilly
06/13/2020, 3:18 PM@Composable
fun ScannerScreenContent(
onItemAction: () -> Unit
): @Composable() (Modifier) -> Unit = { modifier ->
val viewModel = ScannerViewModelAmbient.current
val modelList = remember { mutableListOf<BluetoothDeviceWrapper>() }
launchInComposition(viewModel.device) {
viewModel.device.asFlow().collect { device ->
if (device in modelList) return@collect
modelList.add(device)
Log.d("Found", "size: ${modelList.size}")
}
}
AdapterModelListComponent(modelList, modifier, onItemAction)
}
The AdapterList is not updating, that says, no items are listed. Any ideas? 😕Zach Klippenstein (he/him) [MOD]
06/13/2020, 3:27 PMmodelListOf
, not mutableListOf
Lilly
06/13/2020, 3:32 PMremember
, I mean when I don't make something stupid the function ScannerScreenContent
wouldn't be called again, so modelList would always remain the same.Zach Klippenstein (he/him) [MOD]
06/13/2020, 4:06 PMLilly
06/13/2020, 4:48 PMlaunchInComposition
. Is it used to call blocking code + a concise way of doing something in a coroutine within a composable? I mean when I place
viewModel.device.asFlow().collect { device ->
if (device in modelList) return@collect
modelList.add(device)
Log.d("Found", "size: ${modelList.size}")
}
directly in the composable function it would block the code. What did you mean with side effect...I found these words in the docs too.ScannerScreenContent
function:
val state by viewModel.device.observeAsState()
state?.let { device ->
modelList.add(device)
modelList.forEach {
Log.d("Items", it.bluetoothDevice.name)
}
}
Sorry for the annoying questionsZach Klippenstein (he/him) [MOD]
06/13/2020, 6:28 PMwhat aboutIt launches a new coroutine the first time it is included in the composition at a given position. That coroutine will remain running until the. Is it used to call blocking code + a concise way of doing something in a coroutine within a composable?launchInComposition
launchInComposition
call is removed from the composition (i.e. if you surround the call in an if
and eventually stop calling it). It is very useful for performing long-running asynchronous actions (such as collecting a flow/subscribing to an Observable), or for performing long-running side effects in the background.I’m also wondering why my old code triggered a recompose of theI believe it’s becausefunctionScannerScreenContent
ModelList
is a type that is tracked by the compose runtime, so when you read from it directly in a Composable function body, the runtime knows to re-invoke your function when the list changes. Your code was reading from the list (via forEach
) immediately after creating the list, which I think is what triggered the second composition.
Moving the read into the launchInComposition
fixed the issue because the lambda passed to launchInComposition
isn’t a Composable function, so reading from it in that context doesn’t trigger a recomposition.Sorry for the annoying questionsNot annoying! 🙂 These are subtle behaviors, and the
launchInComposition
documentation could probably some more detail about how/why/when to use it. This is probably also helpful for the team to figure out what kind of stuff it is important to cover in the guides as they flesh out the documentation.Lilly
06/13/2020, 7:00 PMZach Klippenstein (he/him) [MOD]
06/13/2020, 7:07 PMLilly
06/15/2020, 10:49 AM