Passport Composable

Pager, in the context of Android development, refers to a user interface component that allows users to swipe or scroll through a set of pages or items in a horizontal or vertical manner. It is a common design pattern used to display a collection of related content, such as images, articles, or screens, in a visually appealing and interactive way.

Below is a simplified code block that illustrates the core structure of our code, focusing on the pager functionality for better understanding. Please note that the actual repository contains additional features and components that contribute to building the app, but they have been omitted here for clarity.

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Passport(navController: NavHostController,
horizontalPageIndex: Int = 0,
verticalPageIndex: Int = 0,
parks: List<Park>

) {
val horizontalPagerState = rememberPagerState(initialPage = horizontalPageIndex)

HorizontalPager(
modifier = Modifier.background(com.park.quest.Color.DarkGrey),
pageCount = parks.size,
state = horizontalPagerState,
beyondBoundsPageCount = 3
) {
val park = parks[it]

val verticalPagerState: PagerState = rememberPagerState()

VerticalPager(
pageCount = 7,
state = verticalPagerState,
beyondBoundsPageCount = 6
) { verticalPageIndex ->
if (verticalPageIndex == 0) {
HorizontalPagerItem(park = park)
} else {
VerticalPagerItem(park = park)
}

}
}
}
}

Assuming we receive a list of parks, we will use this list to determine the number of pages required for our horizontal pager. Additionally, to enable nesting, we will create a vertical pager at each index.

The first position (position 0) will serve as our passport page, allowing users to mark stamps. Subsequent pages starting from position 1 will display photos.

When building the App Composable, utilizing navController.navigate(route) facilitated seamless navigation between composables. However, when the need arises to navigate between pages within the Pager, we can leverage the pagerState to accomplish this task.

By using pagerState.scrollToPage(pageIndex), we can effortlessly move back and forth between pages. For our scenario, we will have one horizontalPagerState and a corresponding number of verticalPagerStates equal to the number of parks (e.g., 8).

To simplify navigation to a specific page, we can maintain all these states in a map, allowing for efficient access and management of the pager’s navigation functionality.

// map key is park at particular horizontal pager index, 
// value is vertical pager state at that index
val verticalPagerStatesMap: HashMap<Park, PagerState> = HashMap()

This would be useful if someone wishes to navigate form a photo in say Yosemite to a photo below Mount Rainier’s passport page or a photo in Grand Teton.

Another important point to consider is that when setting up the pagerState, we have the option to specify an initialPage. This allows us to control which page the pager should start with when it is initially displayed.

val horizontalPagerState = rememberPagerState(initialPage = horizontalPageIndex)

Now that we’ve explored navigation and scrolling within the pages, let’s return to our App Composable and create the route that allows us to navigate to a specific page within the passport.

The Missing Piece

@Composable
fun App(){
val navController = rememberNavController()

NavHost(navController = navController, startDestination = AppRoutes.HOME.name) {
composable(AppRoutes.HOME.name) {
Home(navController)
}

composable(AppRoutes.ABOUT.name) {
About(navController)
}

composable(AppRoutes.SEARCH.name) {
Search(navController)
}

composable("${AppRoutes.PASSPORT.name}/{horizontalPageIndex}/{verticalPageIndex}") { backStackEntry ->
val verticalPageIndex = backStackEntry.arguments?.getString("verticalPageIndex")?.toInt()
val horizontalPageIndex = backStackEntry.arguments?.getString("horizontalPageIndex")?.toInt()

if(verticalPageIndex != null && horizontalPageIndex != null) {
Passport(horizontalPageIndex, verticalPageIndex)
}
else if(verticalPageIndex == null && horizontalPageIndex != null) {
Passport(horizontalPageIndex)
} else {
Passport()
}
}
}
}

In this context, we utilize a lambda block that provides us with a backStackEntry when rendering a specific composable for the block. This value backStackEntry assists us in retrieving the horizontalPageIndex and verticalPageIndex that the user desires to navigate to. Consequently, we can effortlessly access the corresponding photo or passport page in the app, ensuring a smooth and intuitive navigation experience.

As mentioned earlier, we can take advantage of the initialPage parameter, which we discussed previously, to directly start the pager from specific positions horizontalPageIndex and verticalPageIndex. Additionally, we have the flexibility to use the scrollTo or animateScrollTo functions provided by pagerState to smoothly navigate the user to these desired pages.

In summary, by effectively utilizing horizontalPageIndex, verticalPageIndex, horizontalPagerState, and verticalPagerStatesMap, we can easily and efficiently navigate to a specific page anywhere within the Passport Composable. This comprehensive approach ensures a seamless and user-friendly experience within the app.

Source link