Hey folks, I've got an incredibly simple Compose M...
# android
s
Hey folks, I've got an incredibly simple Compose Multiplatform demo app for my library, and for some reason when I run the Android build for this demo, the network calls cause the UI to stutter, as if they're blocking the main thread. I'm making the network call using a
suspend fun
in a
LaunchedEffect
, and the client is backed by Ktor (via Ktorfit) and the OkHttp engine. As far as I can tell, these network calls shouldn't be blocking the main thread, but still the UI stutters when I scroll (triggering network calls as list items load). The problem only happens on Android. The demo on iOS, Desktop (JVM), macOS (native), JS, and WASM builds all have a totally smooth UI. I've tried: • Explicitly setting
<http://Dispatchers.IO|Dispatchers.IO>
◦ No change. Verified in Ktor source that the default is IO, so that makes sense. • Switching to the
CIO
engine on the JVM target. ◦ No change, still stutters when loading data on Android • Running as profileable to debug further ◦ The problem disappears when I run a profileable build; scrolling is smooth while data loads, so it's unclear to me how to debug further or gather data on the issue You can repro by cloning https://github.com/PokeAPI/pokekotlin and running the demo app (the
:demo-app
module)
c
Also there is #C04TPPEQKEJ for these kind of questions
☝️ 1
s
Interesting; I'd expect the performance of simple network calls in a list to not be too impacted by debug vs release builds, but I'll try a release build. Will x-post in #C04TPPEQKEJ
c
Well, you have not provided any code snippets so it’s hard to tell how you handle state and so on.
1
s
I linked to the code snippet. Will copy it here, though there's not much to see. I described it above; a simple
LaunchedEffect
calling into a
suspend fun
generated by Ktorfit and backed by the OkHttp engine. The Ktor http client:
Copy code
HttpClient(OkHttp.create()) {
  install(HttpCache) { }
  install(ContentNegotiation) { json(PokeApiJson, ContentType.Any) }
  expectSuccess = true
}
The list Composable:
Copy code
@Composable
fun PokemonList(padding: PaddingValues, pokemon: NamedApiResourceList) {
  Box(Modifier.consumeWindowInsets(padding).fillMaxSize()) {
    LazyColumn(contentPadding = padding) {
      items(pokemon.results) { summary ->
        var result by remember { mutableStateOf<Result<PokemonSpecies>?>(null) }
        LaunchedEffect(Unit) { result = PokeApi.getPokemonSpecies(summary.id) }
        result
          ?.onSuccess { PokemonListItem(it) }
          ?.onFailure { PokemonListItemError(summary, it.message ?: "Unknown error") }
          ?: PokemonListItemPlaceholder(summary)
      }
    }
  }
}
e
I would suggest you, to use
MutableStateFlow<Result<PokemonSpecies>?>
Also, I noticed you are using a lot of
!!
. I would suggest you to avoid that at all, then when debugging the crash, you should have a clear idea in what line, the crash is happening. I see, you are using
getOrThrow()
... in case of Throw... are you handling it with a try-catch statement? Would be good, if you posted the error log here.
👎🏾 1
s
There's no crash. I was asking about the UI freezing during network calls with Ktor (OkHttp engine) on debuggable builds, as if the main thread was being blocked, when it shouldn't be. This happens regardless of whether I turn the resulting state into a StateFlow or whether I process the Result instance with getOrThrow or onSuccess/onFailure or whatever.
e
Got it!... Have you try this?
Copy code
LaunchedEffect(Unit) {
withContext(IO) {
    result = PokeApi.getPokemonSpecies(summary.id)
}
}
s
Yup, I've tried explicitly setting the IO dispatcher. It made no difference, and that makes sense as I confirmed in the Ktor source code that IO is the default dispatcher there anyway
c
Did you try the release build?
e
Ok, two more suggestions... just for safety... enclose the api call into:
Copy code
withContext(<http://Dispatchers.IO|Dispatchers.IO>) {...}
but the result... should be assigned inside:
Copy code
withContext(Dispatchers.Main) {...}
and the second suggestion: would be refactor it to use a viewModel, then collecting a flow in the composable. That's usually the practice I recommend in every project.
s
Did you try the release build?
Yes I did; sorry I forgot to follow up on that! The release build works well, just like the profileable build. So the issue is only present on a debug build. Still, it's strange to me the UI would freeze with such a simple network call + display data demo with the overhead of debug build.
would be refactor it to use a viewModel, then collecting a flow in the composable. That's usually the practice I recommend in every project.
I understand the common practices for production apps. This is a small demo app for a library, not a production app that's ever going to get more complex than this. The problem happens even when there's only one network call loading a single item; it's just harder to see unless the network call is deliberately slowed down (say, from the developer settings toggle to throttle network speed). The problem I'm trying to solve here is: why is the UI freezing during a network call when on a debug build on Android?
e
there could be many reasons, such as the extra tracing in debug mode adding objects to be GCed, which when combined with other work, result in noticeable GC pauses