본문 바로가기

안드로이드/Compose Paging3

[안드로이드 Jetpack Compose Paging3 시리즈] LazyVerticalGrid 구현 및 Navigation #5


1. LazyColumn을 LazyVerticalGrid로 변경

안녕하세요. 이번 포스팅에서는 안드로이드 Jetpack Compose Paging3 라이브러리에서 불러온 데이터를 LazyColumn에서 LazyVerticalGrid로 변경하는 과정에 대해 알아보고, 영화 아이템을 클릭했을 때 클린 아키텍처 시리즈에서 구현한 영화 상세 화면으로 이동하는 기능을 Navigation을 통해 구현하겠습니다.

LazyVerticalGrid로 구현한 영화목록화면
LazyVerticalGrid로 구현한 영화 목록 화면

1-1. LazyVerticalGrid 설명 및 구현

LazyColumn이 리스트 형태의 데이터를 세로로 표시하는 데 사용되는 것과 달리, LazyVerticalGrid는 격자(grid) 형태로 데이터를 표시할 때 사용됩니다. 이를 통해 사용자에게 더 다양한 형태의 UI를 제공할 수 있습니다.

이제 LazyColumn에서 구현했던 목록을 LazyVerticalGrid로 변경하겠습니다. Paging3 시리즈를 순서대로 보신 분들이라면 Paging3 라이브러리와 함께 사용하던 코드를 그대로 유지하되, UI 구성 부분만 변경하면 됩니다.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MovieListScreen(
    mainNavController: NavHostController,
    movies: LazyPagingItems<MovieEntity>,
) {
    val favoriteIcon = drawable.ic_favorite_fill_white // drawable.ic_favorite_border_white

    Scaffold(
        floatingActionButton = {
            FloatingActionButton(onClick = { /*TODO*/ }) {
                Image(
                    painter = painterResource(id = favoriteIcon),
                    contentDescription = null,
                    Modifier.size(24.dp),
                )
            }
        },
        topBar = {
            TopAppBar(
                title = { Text(text = "영화 목록") },
                navigationIcon = {
                    IconButton(onClick = { mainNavController.popBackStack() }) {
                        Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
                    }
                }
            )
        }
    ) { paddingValues ->
        LazyVerticalGrid(
            modifier = Modifier.padding(paddingValues),
            columns = GridCells.Fixed(3),
            state = rememberLazyGridState()
        ) {
            items(movies.itemCount, span = { index ->
                GridItemSpan(1)
            }) { index ->
                val movie = movies[index]
                MovieItem(movie!!, ImageSize.getImageFixedSize()) { movieId ->
                    mainNavController.navigate(Page.MovieDetail.route + "/${movieId}")
                }
            }
        }
    }
}

2. Navigation 구성

위 과정을 통해 LazyVerticalGrid를 사용해서 영화 목록을 구현했다면, 영화를 눌렀을 때 해당 영화의 상세 화면으로 이동해 주는 기능을 구현하겠습니다. 클린 아키텍처 시리즈에서 Compose Navigation을 구현했기 때문에 NavGraph를 설정하고 NavController를 통해 화면을 이동하겠습니다.

2-1. NavGraph에 경로 추가

이전에 구현했던 MainGraph에 MovieDetailPage Composable을 추가합니다. 영화 아이템을 누르면 해당 Id값을 영화 상세 화면으로 전달해주어야 하기 때문에 argument 인자에 movieId에 대한 정의와 route 인자에 {movieId}를 추가해 줍니다. 이렇게 하면 NavController의 navigate 함수를 호출할 때, route 경로에 전달한 영화 Id값을 받을 수 있습니다.

sealed class Page(val route: String) {
    data object Movies: Page("movies")
    data object MovieDetail: Page("movie_detail")
}

@Composable
fun MainGraph(
    mainNavController: NavHostController
) {
    NavHost(navController = mainNavController, startDestination = Page.Movies.route) {
        composable(
            route = Page.Movies.route
        ) {
            val viewModel = hiltViewModel<MovieListViewModel>()
            MovieListPage(
                mainNavController = mainNavController,
                viewModel = viewModel
            )
        }

        composable(
            route = "${Page.MovieDetail.route}/{movieId}",
            arguments = listOf(navArgument("movieId") {
                type = NavType.IntType
                defaultValue = 0
                nullable = false
            })
        ) {
            val viewModel = hiltViewModel<MovieDetailViewModel>()
            MovieDetailPage(
                mainNavController = mainNavController,
                viewModel = viewModel
            )
        }
    }
}

2-2. NavController를 통해 화면 이동

영화 아이템을 눌렀을 때 해당 영화의 상세 화면으로 이동되어야 합니다.

1번 과정에서 구현했던 LazyVerticalGrid에서 사용하는 MovieItem Composable에서 onMovieClick 함수를 통해 해당 영화의 Id 값을 전달하도록 구현했습니다.

onMovieClick 함수의 구현부에서는 전달받은 영화 Id를 사용해서 NavController를 통해 화면 이동을 실행합니다.

@Composable
private fun MovieItem(
    movie: MovieEntity,
    imageSize: ImageSize,
    onMovieClick: (movieId: Int) -> Unit = {}
) {
    SubcomposeAsyncImage(
        model = movie.image,
        loading = { MovieItemPlaceholder() },
        error = { MovieItemPlaceholder() },
        contentDescription = null,
        contentScale = ContentScale.Crop,
        modifier = Modifier
            .padding(3.dp)
            .size(imageSize.width, imageSize.height)
            .clickable { onMovieClick(movie.id) }
            .clip(RoundedCornerShape(2))
    )
}
MovieItem(movie!!, ImageSize.getImageFixedSize()) { movieId ->
	mainNavController.navigate(Page.MovieDetail.route + "/${movieId}")
}

3. LazyVerticalGrid 구현 및 Navigation 정리

이번 포스팅에서는 안드로이드 Jetpack Compose의 LazyColumn에서 LazyVerticalGrid로의 전환, 그리고 영화 아이템을 클릭했을 때 해당 영화의 상세화면으로 이동하는 기능에 대해서 알아보았습니다. LazyColumn과 LazyVerticalGrid 모두 Paging3 라이브러리와 호환이 되며 무한스크롤을 지원합니다. 구현하고자 하는 화면 디자인에 맞춰서 적절한 composable을 사용하는데 도움이 되었으면 좋겠습니다.

다음 포스팅에서는 로컬 데이터베이스를 사용해서 캐시 저장을 하기 위한 작업을 할 예정입니다. RemoteMediator를 추가하기에 앞서, 로컬 데이터베이스를 Room 라이브러리를 통해 구현하겠습니다.