Search code examples
androidkotlinpaginationretrofitandroid-jetpack-compose

Screen scrolls to the top (Jetpack Compose Pagination)


I am trying to do pagination in my application. First, I'm fetching 20 item from Api (limit) and every time i scroll down to the bottom of the screen, it increase this number by 20 (nextPage()). However, when this function is called, the screen goes to the top, but I want it to continue where it left off. How can I do that?

Here is my code:

CharacterListScreen:

@Composable
fun CharacterListScreen(
    characterListViewModel: CharacterListViewModel = hiltViewModel()
) {

    val state = characterListViewModel.state.value
    val limit = characterListViewModel.limit.value

    Box(modifier = Modifier.fillMaxSize()) {
        val listState = rememberLazyListState()

        LazyColumn(modifier = Modifier.fillMaxSize(), state = listState) {
            itemsIndexed(state.characters) { index, character ->
                characterListViewModel.onChangeRecipeScrollPosition(index)
                if ((index + 1) >= limit) {
                    characterListViewModel.nextPage()
                }
                CharacterListItem(character = character)
            }
        }

        if (state.error.isNotBlank()) {
            Text(
                text = state.error,
                color = MaterialTheme.colors.error,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 20.dp)
                    .align(Alignment.Center)
            )
        }
        if (state.isLoading) {
            CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
        }
    }
}

CharacterListViewModel:

@HiltViewModel
class CharacterListViewModel @Inject constructor(
    private val characterRepository: CharacterRepository
) : ViewModel() {

    val state = mutableStateOf(CharacterListState())
    val limit = mutableStateOf(20)
    var recipeListScrollPosition = 0

    init {
        getCharacters(limit.value, Constants.HEADER)
    }

    private fun getCharacters(limit : Int, header : String) {
        characterRepository.getCharacters(limit, header).onEach { result ->
            when(result) {
                is Resource.Success -> {
                    state.value = CharacterListState(characters = result.data ?: emptyList())
                }
                is Resource.Error -> {
                    state.value = CharacterListState(error = result.message ?: "Unexpected Error")
                }
                is Resource.Loading -> {
                    state.value = CharacterListState(isLoading = true)
                }
            }
        }.launchIn(viewModelScope)
    }

    private fun incrementLimit() {
        limit.value = limit.value + 20
    }

    fun onChangeRecipeScrollPosition(position: Int){
        recipeListScrollPosition = position
    }

    fun nextPage() {
            if((recipeListScrollPosition + 1) >= limit.value) {
                incrementLimit()
                characterRepository.getCharacters(limit.value, Constants.HEADER).onEach {result ->
                    when(result) {
                        is Resource.Success -> {
                            state.value = CharacterListState(characters = result.data ?: emptyList())
                        }
                        is Resource.Error -> {
                            state.value = CharacterListState(error = result.message ?: "Unexpected Error")
                        }
                        is Resource.Loading -> {
                            state.value = CharacterListState(isLoading = true)
                        }
                    }
                }.launchIn(viewModelScope)
            }
    }

}

CharacterListState:

data class CharacterListState(
    val isLoading : Boolean = false,
    var characters : List<Character> = emptyList(),
    val error : String = ""
)

Solution

  • I think the issue here is that you are creating CharacterListState(isLoading = true) while loading. This creates an object with empty list of elements. So compose renders an empty LazyColumn here which resets the scroll state. The easy solution for that could be state.value = state.value.copy(isLoading = true). Then, while loading, the item list can be preserved (and so is the scroll state)