My Android app is written using Jetpack Compose and utilising an MVVM architecture. It is a basic movie list app utilising the TMDB API.
The app displays a list of items using a LazyVerticalGrid
with two columns of tiles. The user will scroll the list and click on one at which point a details page is navigated to. I am using a NavController
for this. This is an independent screen, not a bottom sheet dialogue (although I am considering changing to that).
Navigation is handled via the MainActivity
with each screen being a separate Screen
file utilising a viewModel and repository as appropriate.
The user scrolls and let's say he's scrolled down three pairs rows and clicked on the 7th item. When the user is finished with the details screen he/she will return back to the previous screen, the list. The problem I have is that this screen is redrawn which means that it will resend a request to the cloud and repaint and the user is returned to the top of the list.
I can't see what the reason for this is but i =. Any ideas please on how I can resolve the issue or is my only option to remember the last scrolled to index and then re-scroll to it?
This is the MovieListScreen
@Composable
fun MovieListScreen(movieDetailsNavigationCallback: (Int) -> Unit) {
val viewModel: MovieListViewModel = hiltViewModel()
val movieDataList = viewModel.getMovieListPage().collectAsLazyPagingItems()
Box(
modifier = Modifier
.fillMaxSize()
.background(
brush = Brush.verticalGradient(
colors = listOf(
SplashGradientStart,
SplashGradientEnd
)
)
),
contentAlignment = Alignment.TopCenter
) {
VerticalGridButtons(movieDataList = movieDataList, movieDetailsNavigationCallback)
}
}
@Composable
fun VerticalGridButtons(
movieDataList: LazyPagingItems<MovieData>,
navigationCallback: (Int) -> Unit,
) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(
start = 8.dp,
end = 8.dp,
bottom = 8.dp,
top = 100.dp
),
) {
items(
movieDataList.itemCount
) { index ->
movieDataList[index]?.let { MenuItemTile(it, navigationCallback) }
}
}
// TODO: Add error handling
}
@Composable
fun MenuItemTile(movieData: MovieData, navigationCallback: (Int) -> Unit) {
Card(
Modifier
.padding(8.dp)
.background(Color.Black.copy(alpha = 0.4f)),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.cardElevation(
defaultElevation = 8.dp
),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(4.dp)
.background(color = Color.Black.copy(alpha = 1.0f))
.fillMaxWidth(),
) {
AsyncImage(
model = BASE_URL + movieData.posterPath,
contentDescription = "Menu Thumbnail",
contentScale = ContentScale.FillBounds,
modifier = Modifier
.size(250.dp)
.padding(top = 0.dp)
.align(Alignment.CenterHorizontally)
.clickable(onClick = {
Timber.d("onClick event for movie ID == " + movieData.id)
navigationCallback(movieData.id)
}
)
)
Spacer(modifier = Modifier.height(height = 15.dp))
val movieTitleScroll = rememberScrollState(0)
Text(modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(bottom = 10.dp)
.horizontalScroll(movieTitleScroll),
text = movieData.originalTitle,
style = MaterialTheme.typography.displaySmall,
color = Color.White,
maxLines = 1
)
}
}
}
@Preview(showBackground = true)
@Composable
fun MovieListScreenPreview() {
DisneyMoviesTheme() {
MovieListScreen({})
}
}
This is the MovieListViewModel
@HiltViewModel
class MovieListViewModel @Inject constructor(private val repository: MoviesListRepository) : ViewModel() {
init {
viewModelScope.launch(Dispatchers.IO) {
}
}
fun getMovieListPage(): Flow<PagingData<MovieData>> = repository.getDiscoverMoviesPage().cachedIn(viewModelScope)
}
MainActivity
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val stringResourceProvider: StringResourceProviderImpl = StringResourceProviderImpl(resources)
setContent {
DMoviesTheme {
DMDBApp(stringResourceProvider)
}
}
}
}
@Composable
private fun DMDBApp(stringResourceProvider: StringResourceProviderImpl) {
val navController = rememberNavController()
NavHost(navController, startDestination = "splash_screen") {
composable(route = "splash_screen") {
SplashScreen {
navController.navigate("movie_list_screen")
}
}
composable(route = "movie_list_screen") {
MovieListScreen() { id ->
navController.navigate("destination_movie_details_screen/${id}")
}
}
composable(
route = "destination_movie_details_screen/{id}",
arguments = listOf (
navArgument("id") { type = NavType.IntType }
)
) {
val viewModel: MovieDetailsViewModel = hiltViewModel()
MovieDetailsScreen(viewModel.movieDetailsState.value)
}
}
}