ysfcyln
ysfcyln

Reputation: 3115

Jetpack Compose - Restore LazyColumn Scroll State

I have a LazyColumn that contains multiple LazyRow. In old terms, nested RecyclerView.

My problem is, LazyColumn does not restore scroll state when move to a new composable (Different tab). But inner LazyRows restore their states.

For example, open home screen, scroll to bottom then scroll LazyRow to end then open a different tab and back to home tab again. LazyColumn starts from top (does not restore state) but the last LazyRow restore it's scroll state.

HomeScreen that contains LazyColumn

@Composable
fun HomeScreen(
    homeViewModel: HomeViewModel = hiltViewModel()
) {

    val scrollState = rememberLazyListState()

    LazyColumn(contentPadding = PaddingValues(vertical = 8.dp),
        horizontalAlignment = Alignment.Start,
        verticalArrangement = Arrangement.spacedBy(8.dp),
        state = scrollState,
        modifier = Modifier
            .fillMaxSize()
            .background(MaterialTheme.colors.background)
    ) {
        items(5) {
            TopRatedProducts(homeViewModel = homeViewModel)
        }
    }
}

TopRatedProducts that contains LazyRow

@Composable
fun TopRatedProducts(
    homeViewModel: HomeViewModel = hiltViewModel()
) {
    val topRatedProducts by rememberFlowWithLifecycle(homeViewModel.topRatedProducts)
        .collectAsState(initial = emptyList())

    LazyRow(
        contentPadding = PaddingValues(horizontal = 8.dp), // Space between start and end
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp), // Space between items
        modifier = Modifier
            .background(Color.Green)
    ) {
        items(topRatedProducts) {
            ProductCardItem(item = it)
        }
    }
}

How can I restore LazyColumn scroll state?

Upvotes: 4

Views: 4354

Answers (2)

Luis
Luis

Reputation: 3571

Try the following at the end of your HomeScreen() function:

    LaunchedEffect(scrollState.firstVisibleItemScrollOffset) {
        scrollState.scrollToItem(
            scrollState.firstVisibleItemIndex,
            scrollState.firstVisibleItemScrollOffset
        )
    }

Upvotes: 0

Ponsuyambu
Ponsuyambu

Reputation: 9036

You should mention ViewCompositionStrategy when you want this behaviour with ComposeView in Fragments.

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    return ComposeView(requireContext()).apply {
        // Dispose the Composition when viewLifecycleOwner is destroyed
        setViewCompositionStrategy(
            ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner)
        )
        setContent {
            // content
        }
    }
}

From docs: By default, Compose disposes of the Composition whenever the view becomes detached from a window. Compose UI View types such as ComposeView and AbstractComposeView use a ViewCompositionStrategy that defines this behavior.

By default, Compose uses the DisposeOnDetachedFromWindow strategy. However, this default value might be undesirable in some situations when the Compose UI View types are used in:

  • Fragments. The Composition must follow the fragment's view lifecycle for Compose UI View types to save state.

  • Transitions. Anytime the Compose UI View is used as part of a transition, it will be detached from its window when the transition starts instead of when the transition ends, thus causing your composable to dispose of its state while it is still on screen.

  • RecyclerView view holders, or your own lifecycle-managed custom View

Docs link: https://developer.android.com/jetpack/compose/interop/interop-apis#composition-strategy

Upvotes: 4

Related Questions