How to write a logic for Horizontal view pager screens I have 4 main screen and in this 4 screen each screen has multiple screen or view depends how logic fits because there is a also back button in which all screen back navigation will need to handle.
Back button is in topBar which I take as a common.
I'm building an Android app using Jetpack Compose, where I have a horizontal view pager (HorizontalPager
) with 4 main screens and a pager indicator to show the user's current position. Each main screen has its own set of sub-screens, structured as follows:
Requirements:
I'm looking for the best approach to handle this type of nested navigation in Jetpack Compose, specifically on managing back stacks within each main screen and ensuring smooth transitions across sub-screens and main screens in the view pager.
@Composable
fun AddDeviceFlow() {
Scaffold(modifier = Modifier
.fillMaxSize()
.statusBarsPadding(),
topBar = {
AddDeviceAppBar(modifier = Modifier,
showCloseButton = true,
showBackButton = false,
onClickBackButton = {
// handle viewpager screens and nested screens pop back navigation
},
onClickCloseButton = {})
}
) { innerPadding ->
Surface(modifier = Modifier.padding(innerPadding)) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
HorizontalPagerWithGradientIndicators()
}
}
}
}
@Composable
fun HorizontalPagerWithGradientIndicators() {
val pagerState = rememberPagerState(pageCount = { 4 })
val isLastPage = pagerState.currentPage == pagerState.pageCount - 1
val coroutineScope = rememberCoroutineScope()
val constraintSet = ConstraintSet {
val (
pager,
pagerIndicator
) = createRefsFor(
"pager",
"pagerIndicator"
)
constrain(pagerIndicator) {
top.linkTo(parent.top)
bottom.linkTo(pager.top)
centerHorizontallyTo(parent)
}
constrain(pager) {
top.linkTo(pagerIndicator.bottom)
bottom.linkTo(parent.bottom)
centerHorizontallyTo(parent)
}
}
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier
) {
// here is stepper code I removed for brevity
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.layoutId("pager"),
) {
}
}
}
This is how I handle Back Navigation
@Composable
fun AddDeviceFlow(onDeviceBackPressed:(Boolean) -> Unit) {
val keyboardController = LocalSoftwareKeyboardController.current
val context = LocalContext.current
val pagerState = rememberPagerState(pageCount = { 4 })
val coroutineScope = rememberCoroutineScope()
var currentSubScreen by remember { mutableStateOf(SubScreenType.DEVICE_SOFTWARE) }
BackHandler(enabled = currentSubScreen == SubScreenType.DEVICE_HARDWARE) {
if (currentSubScreen == SubScreenType.DEVICE_HARDWARE) {
currentSubScreen = SubScreenType.DEVICE_SOFTWARE
} else {
// Handle other cases or default back navigation
}
}
Scaffold(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding(),
topBar = {
AddDeviceAppBar(
modifier = Modifier,
showCloseButton = true,
showBackButton = currentSubScreen == SubScreenType.DEVICE_HARDWARE,
onClickBackButton = {
handleBackNavigation(
pagerState = pagerState,
currentSubScreen = currentSubScreen,
setSubScreen = { newScreen -> currentSubScreen = newScreen },
coroutineScope = coroutineScope
)
},
onClickCloseButton = {
onDeviceBackPressed(true)
}
)
}
) { innerPadding ->
Surface(modifier = Modifier.padding(innerPadding)) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
) {
HorizontalPagerWithGradientIndicators(
pagerState = pagerState,
currentSubScreen = currentSubScreen,
setSubScreen = { newScreen -> currentSubScreen = newScreen }
)
}
}
}
}
@Composable
fun HorizontalPagerWithGradientIndicators(
pagerState: PagerState,
currentSubScreen: SubScreenType,
setSubScreen: (SubScreenType) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
val isLastPage = pagerState.currentPage == pagerState.pageCount - 1
val constraintSet = ConstraintSet {
val (
pager,
pagerIndicator
) = createRefsFor(
"pager",
"pagerIndicator"
)
constrain(pagerIndicator) {
top.linkTo(parent.top)
bottom.linkTo(pager.top)
centerHorizontallyTo(parent)
}
constrain(pager) {
top.linkTo(pagerIndicator.bottom)
bottom.linkTo(parent.bottom)
centerHorizontallyTo(parent)
}
}
ConstraintLayout(
constraintSet = constraintSet,
modifier = Modifier
) {
HorizontalPager(
state = pagerState,
userScrollEnabled = false,
modifier = Modifier
.padding(top = 22.dp)
.layoutId("pager"),
) { page ->
when (page) {
0 -> AddDeviceScreen {
coroutineScope.launch {
pagerState.animateScrollToPage(page + 1, animationSpec = tween(512))
}
}
1 -> {
when (currentSubScreen) {
SubScreenType.DEVICE_SOFTWARE -> ChooseSoftwareScreen(
onSoftWareSelected = { setSubScreen(SubScreenType.DEVICE_HARDWARE) }
)
SubScreenType.DEVICE_HARDWARE -> ChooseHardwareScreen(
onHardWareSelected = {
}
)
}
}
}
}
}
}
// Helper function for back navigation logic
private fun handleBackNavigation(
pagerState: PagerState,
currentSubScreen: SubScreenType,
setSubScreen: (SubScreenType) -> Unit,
coroutineScope: CoroutineScope
) {
when (currentSubScreen) {
SubScreenType.DEVICE_HARDWARE -> setSubScreen(SubScreenType.DEVICE_HARDWARE)
SubScreenType.DEVICE_HARDWARE -> {
if (pagerState.currentPage > 0) {
coroutineScope.launch {
pagerState.animateScrollToPage(pagerState.currentPage - 1)
}
} else {
// Handle case where the user is at the first page and wants to go back
}
}
}
}
enum class SubScreenType {
DEVICE_HARDWARE,
DEVICE_SOFTWARE
}