What currently is happening:
- I have a grid screen and on clicking on the list I navigate to the detail screen
- Now on clicking the back button I navigate back to the list screen
What is the issue:
- If I have scrolled to the 20th item and navigated to detail and pressed the back button
- Once I navigate back entire screen is recomposed in a grid screen and as a result, I scroll to the top as if entire screen is reloaded
How to prevent this so I remain in the 20th item
MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PokedexTheme {
// Always create one nav controller and pass into the nav-host
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "pokemon_list_screen"
) {
composable("pokemon_list_screen") {
PokemonListScreen(navController = navController)
}
composable(
route = "pokemon_detail_screen/{dominantColor}/{pokemonName}",
arguments = listOf(
navArgument("dominantColor") {
type = NavType.IntType
},
navArgument("pokemonName") {
type = NavType.StringType
}
)
) {
val dominantColor = remember {
val color = it.arguments?.getInt("dominantColor")
color?.let { Color(it) } ?: Color.White
}
val pokemonName = remember {
it.arguments?.getString("pokemonName")
}
PokemonDetailScreen(
dominantColor = dominantColor,
pokemonName = pokemonName?.lowercase(Locale.ROOT) ?: "",
navController = navController
)
}
}
}
}
}
}
PokemonListScreen.kt
@Composable
fun PokemonListScreen(
navController: NavController
) {
Column(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)
) {
Spacer(modifier = Modifier.height(20.dp))
PokemonBanner()
PokemonLazyList(
onItemClick = { entry ->
navController.navigate(
"pokemon_detail_screen/${entry.dominentColor.toArgb()}/${entry.pokemonName}"
)
}
)
}
}
PokemonDetailScreen.kt
@Composable
fun PokemonDetailScreen(
dominantColor: Color,
pokemonName: String,
navController: NavController,
topPadding: Dp = 20.dp,
pokemonImageSize: Dp = 200.dp,
viewModel: PokemonDetailVm = hiltViewModel()
) {
var pokemonDetailData by remember { mutableStateOf<PokemonDetailView>(PokemonDetailView.DisplayLoadingView) }
val pokemonDetailScope = rememberCoroutineScope()
LaunchedEffect(key1 = true){
viewModel.getPokemonDetails(pokemonName)
viewModel.state.collect{ pokemonDetailData = it }
}
Box(
modifier = Modifier
.fillMaxSize()
.background(dominantColor)
.padding(bottom = 16.dp)
) {
PokemonHeader(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.2f)
.align(Alignment.TopCenter)
) {
navController.popBackStack()
}
PokemonBody(
pokemonInfo = pokemonDetailData,
topPadding = topPadding,
pokemonImageSize = pokemonImageSize
) {
pokemonDetailScope.launch {
viewModel.getPokemonDetails(pokemonName)
}
}
Box(
contentAlignment = Alignment.TopCenter,
modifier = Modifier
.fillMaxSize()
) {
if(pokemonDetailData is PokemonDetailView.DisplayPokemonView){
val data = (pokemonDetailData as PokemonDetailView.DisplayPokemonView).data
data.sprites.let {
// Image is available
val url = PokemonUtils.formatPokemonDetailUrl(it.frontDefault)
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
contentDescription = data.name,
contentScale = ContentScale.Fit,
modifier = Modifier
// Set the default size passed to the composable
.size(pokemonImageSize)
// Shift the image down from the top
.offset(y = topPadding)
)
}
}
}
}
}
New Code Added
PokemonListVm.kt
@HiltViewModel
class PokemonListVm @Inject constructor(
private val repository: PagingRepository
): ViewModel() {
fun getPokemonList(): Flow<PagingData<PokedexListEntry>> = repository.getPokemon().cachedIn(viewModelScope)
/**
* What it does: It calculates the dominant color based on a drawable
* What it returns: Color as a function callback
* @param drawable
* @param onFinish
*/
fun calcDominantColor(drawable: Drawable, onFinish: (Color) -> Unit) {
val bmp = (drawable as BitmapDrawable).bitmap.copy(Bitmap.Config.ARGB_8888, true)
Palette.from(bmp).generate { palette ->
palette?.dominantSwatch?.rgb?.let { colorValue ->
onFinish(Color(colorValue))
}
}
}
}
PokemonLazyList.kt
@Composable
fun PokemonLazyList(
onItemClick:(PokedexListEntry)-> Unit,
viewModel: PokemonListVm = hiltViewModel(),
loadingScreenState:() -> Unit = {},
errorScreenState:() -> Unit = {},
){
val pokemonList = viewModel.getPokemonList().collectAsLazyPagingItems()
val state = rememberLazyGridState()
LazyVerticalGrid(
columns = GridCells.Fixed(2),
state = state
) {
itemsCustom(pokemonList){
if (it != null) {
PokemonListItem(
item=it,
onItemClick=onItemClick
)
}
}
pokemonList.apply {
when {
loadState.refresh is LoadState.Loading -> {
item { LoadingView(modifier = Modifier.fillMaxSize()) }
}
loadState.append is LoadState.Loading -> {
item { LoadingView(modifier = Modifier.fillMaxSize()) }
}
loadState.refresh is LoadState.Error -> {
val e = pokemonList.loadState.refresh as LoadState.Error
item {
ErrorItem(
message = e.error.localizedMessage!!,
modifier = Modifier.fillMaxSize(),
onClickRetry = { retry() }
)
}
}
loadState.append is LoadState.Error -> {
val e = pokemonList.loadState.append as LoadState.Error
item {
ErrorItem(
message = e.error.localizedMessage!!,
onClickRetry = { retry() }
)
}
}
}
}
}
}
Full-SourceCode: Code
One option is to use LazyGridState.
In
PokemonListScreen
:In
PokemonLazyList
pass that state to the underlying Grid:The state must be saved somewhere, like
PokemonListVm
: