Optimize channel list scrolling performance#6197
Draft
aleksandar-apostolov wants to merge 6 commits intov7from
Draft
Optimize channel list scrolling performance#6197aleksandar-apostolov wants to merge 6 commits intov7from
aleksandar-apostolov wants to merge 6 commits intov7from
Conversation
Replace Thread.sleep busy-wait in awaitInitializationState with runBlocking + coroutine suspension — wakes instantly on state change instead of polling every 100ms (eliminates ~2.9s Davey on cold start). Move channel list combine pipeline off Main via flowOn(Default), add 100ms debounce to collapse rapid WebSocket event floods, and reuse ChannelItemState instances by CID to let Compose skip recomposition for unchanged items.
Memoize channel name and message preview with remember(). Wrap Timestamp formatting in remember(date, formatType). Add contentType to LazyList for better item reuse. Replace collectAsState() with direct .value access for stable user state in lambda blocks. Remove duplicate Crossfade from Avatar (Coil already handles crossfade).
Replace fire-and-forget per-call insertMany with a batched flush. Cache updates remain immediate; only Room writes are deferred. Entities are deduplicated by user ID (last-write-wins) at flush time.
Contributor
PR checklist ✅All required conditions are satisfied:
🎉 Great job! This PR is ready for review. |
Contributor
SDK Size Comparison 📏
|
Channel.updateUsers checks structural equality before copy, cacheUsers skips emission when data unchanged, and watcher events are filtered from user extraction pipeline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Goal
Optimize channel list scrolling performance — eliminate cold-start frozen frames, reduce scroll jank during WebSocket event floods, batch redundant DB writes, and eliminate unnecessary object allocation during watcher event bursts.
Implementation
1. Eliminate cold-start Davey (~2.9s)
Replace
Thread.sleepbusy-wait inawaitInitializationStatewithrunBlocking+ coroutine suspension — wakes instantly on state change instead of polling every 100ms.2. Reduce scroll jank from WebSocket event floods
Move channel list
combinepipeline off Main viaflowOn(Default), add 100ms debounce to collapse rapid events (e.g. 30user.watching.startevents), and reuseChannelItemStateinstances by CID so Compose skips recomposition for unchanged items.3. Memoize Compose work during scroll
remember()for channel name formatting, message preview, and timestamp;contentTypeonLazyListfor better item reuse; remove redundantCrossfadefrom Avatar (Coil handles it); replacecollectAsState()with direct.valueaccess in lambda blocks.4. Batch user DB writes
Replace fire-and-forget per-call
insertManywith a 3s batched flush inDatabaseUserRepository. Cache updates remain immediate; only Room writes are deferred. Entities are deduplicated by user ID (last-write-wins) at flush time — measured 732 enqueued → 14 written (98% dedup).5. Eliminate redundant object allocation on watcher events
When 30 channels load, 30
user.watching.startWebSocket events fire for the same user. Each triggeredChannel.copy()on all 30 channels even when user data was identical, creating ~900 unnecessary Channel allocations. Three layered fixes:copy().Useris a data class, so!=is correct. Skip copy when user data hasn't changed.snapshot()emission when cache content is unchanged.MutableStateFlowalready deduplicates downstream, butsnapshot()creates an expensiveLinkedHashMapcopy we can avoid entirely.UserStartWatchingEvent/UserStopWatchingEventfrom user extraction. These only carry the current user (whose data doesn't change). Real profile changes arrive viaUserUpdatedEvent.Result: GC cycles reduced from 4 to 2, garbage collected from 110MB to 66MB on Xiaomi Redmi 13.
Results (cold start, 30 channels, emulator)
Results (cold start, 30 channels, Xiaomi Redmi 13)
UI Changes
No visual changes — this is a performance-only PR.
Testing
DatabaseUserRepository,ChannelListViewModelReviewer Checklist