Skip to content

Conversation

@chauhan-abhi
Copy link
Owner

@chauhan-abhi chauhan-abhi commented Dec 30, 2024

Two issues remaining:

Collect data in lazylist non composable scope from ViewModel in RecentSearchesListComposer

 override fun LazyListScope.content(
        viewModel: RecentSearchesViewModel,
        config: ComponentConfig,
        modifier: Modifier,
        onAction: (Any) -> Unit
    ) {
        // @TODO ISSUE #2
        // How to use viewmodel.uiState.collectAsStateWithLifecycle() in
        // non composable function LazyListScope.content()
        val jobItems by viewModel.uiState
        items(
            items = jobItems.items,
            key = { it.id },
            itemContent = { data ->
                RecentSearch(
                    recentSearchItem = data,
                    modifier = modifier
                )
            }
        )
    }

Sealed class uistate recomposing whole lazy column in PersonalisedJobViewModel

val uiState: StateFlow<UiState> = inputFlow.flatMapLatest { input ->
        Log.d("PersonalisedJobViewModel", "Fetching jobs with input: ${this.hashCode()}")
        useCase.fetch(input).map {
            UiState.Success(it.toUiModel())
        }.catch {
            UiState.Error("Failed to fetch jobs")
        }
    }.stateIn(
        scope = coroutineScope,
        started = SharingStarted.WhileSubscribed(5000L),
        initialValue = UiState.Loading
    )

@chauhan-abhi chauhan-abhi self-assigned this Dec 31, 2024
@chauhan-abhi chauhan-abhi added the help wanted Extra attention is needed label Dec 31, 2024
// FIXME: We cannot use viewmodel.uiState.collectAsStateWithLifecycle() in
// non composable function LazyListScope.content() and
// items api require size or list reference
lifecycle.lifecycleScope.launch {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @chauhan-abhi, I noticed a critical issue in the usage of LazyListScope.content() here.

The current implementation launches a coroutine inside LazyListScope, which is not a composable function. This introduces two major problems:

  • It breaks the declarative nature of Compose by injecting side effects inside a non-reactive scope.
  • Dynamically calling items() within a coroutine won't work as expected, since LazyListScope is meant for static composition. Compose will not trigger recomposition when the collected data changes.

Suggested Fix:

Move the collection logic into a proper composable and build the list reactively using LazyColumn. Here's an example:

@Composable
fun RecentSearchesContent(
    viewModel: RecentSearchesViewModel,
    modifier: Modifier = Modifier
) {
    val state by viewModel.uiState.collectAsStateWithLifecycle()

    LazyColumn(modifier = modifier) {
        if (state.items.isEmpty()) {
            item {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    color = MaterialTheme.colorScheme.tertiary
                )
            }
        } else {
            items(
                items = state.items,
                key = { it.id }
            ) {
                RecentSearch(it)
            }
        }
    }
}

If you're constrained by the AbstractMicroFeatureComposer interface and can't use a regular @Composable, I would recommend re-evaluating that architecture or exposing a hook to inject a Composable content block instead of raw LazyListScope.

Hope this helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants