https://kotlinlang.org logo
#compose
Title
# compose
t

Travis Griggs

02/27/2023, 5:18 PM
I'm looking for any feedback/guidance on a small demo piece I've been putting together for learning sake. It's mainly 2 files each about 100 lines. Some of those lines are comments like "is this really the right way to do this?" I'll post them in thread and would be grateful for any feedback/guidance.
MainActivity.kt
Copy code
class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContent {
         Pager4Theme {
            MainScreen()
         }
      }
   }
}

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MainScreen() {
   val navController = rememberAnimatedNavController()
   Scaffold(bottomBar = {
      PageTabBar(modifier = Modifier,
         onSelect = { index -> navController.navigate("page_$index") })
   }) { innerPadding ->
      PagesNavHost(
         navController = navController, modifier = Modifier.padding(innerPadding)
      )
   }
}


// This feels ugly, I had the same block of code for both transitions nearly, so I figured out how to
// extract it, but passing a "scope" feels odd
@OptIn(ExperimentalAnimationApi::class)
private fun slideDirection(scope: AnimatedContentScope<NavBackStackEntry>): AnimatedContentScope.SlideDirection {
   val initialRoute = scope.initialState.destination.route ?: ""
   val targetRoute = scope.targetState.destination.route ?: ""
   return if (initialRoute < targetRoute) {
      AnimatedContentScope.SlideDirection.Left
   } else {
      AnimatedContentScope.SlideDirection.Right
   }
}


// There's an open question (to me at least) of why I'm using the whole Nav thing here
// I did it because that seemed the way to have separate "screens" and get side to side animation
// But they're not really a stack? Maybe that doesn't matter. But could I just activate one of four
// child composables and accomplish the side-to-side animation a different way?
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun PagesNavHost(
   modifier: Modifier = Modifier,
   navController: NavHostController,
   startDestination: String = "page_1"
) {
   AnimatedNavHost(modifier = modifier.fillMaxSize(),
      navController = navController,
      startDestination = startDestination,
      enterTransition = { slideIntoContainer(slideDirection(this), animationSpec = tween(250)) },
      exitTransition = {
         slideOutOfContainer(
            slideDirection(this),
            animationSpec = tween(250)
         )
      }) {
      composable("page_1") { PlansPage() }
      composable("page_2") { MapsPage() }
      composable("page_3") { StatusPage() }
      composable("page_4") { SettingsPage() }
   }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
   Pager4Theme {
      MainScreen()
   }
PageTabs.kt
Copy code
// TODO: Figure out the Right/New Way(tm) to do the named colors
@Composable
fun PageTabButton(
   text: String,
   vector: Int,
   index: Int,
   selectedIndex: MutableState<Int>,
   modifier: Modifier = Modifier
) {
   val isSelected by remember { derivedStateOf { index == selectedIndex.value } }
   val tintColor by animateColorAsState(
      targetValue = when (isSelected) {
         true -> Color.White; false -> colorResource(id = R.color.label_tabs_inactive)
      }
   )
   Column(
      modifier = modifier
         .padding(6.dp)
         .clickable { selectedIndex.value = index },
      horizontalAlignment = Alignment.CenterHorizontally
   ) {
      Image(
         modifier = Modifier.size(24.dp),
         painter = painterResource(id = vector),
         contentDescription = text,
         colorFilter = ColorFilter.tint(tintColor)
      )
      Spacer(modifier = Modifier.height(4.dp))
      val weight = when (isSelected) {
         true -> FontWeight.Medium; false -> FontWeight.Normal
      }
      Text(
         text = text, color = tintColor, fontSize = 11.sp, fontWeight = weight
      )
   }
}

// My hunch is that I'm working too hard with this selectedIndex thing
// I see that Modifier has selectable stuff, I should of course add that for accessability,
// but the real question is, can it actually model the state of which button is selected for me as well?
@Composable
fun PageTabBar(
   modifier: Modifier = Modifier, onSelect: (Int) -> Unit = { _ -> }
) {
   Row(
      modifier = modifier
         .background(color = colorResource(id = R.color.background_tabs))
         .fillMaxWidth()
   ) {
      var selectedIndex = remember { mutableStateOf(1) }
      LaunchedEffect(selectedIndex.value) {
         onSelect(selectedIndex.value)
      }
      val fillEach = Modifier.weight(1f, true) // Is this REALLY how I get even distribution?
      // CAN I do the following with a loop?
      PageTabButton(
         text = "PLANS",
         vector = R.drawable.tab_plans_mask,
         index = 1,
         selectedIndex = selectedIndex,
         modifier = fillEach
      )
      PageTabButton(
         text = "MAP",
         vector = R.drawable.tab_map_mask,
         index = 2,
         selectedIndex = selectedIndex,
         modifier = fillEach
      )
      PageTabButton(
         text = "STATUS",
         vector = R.drawable.tab_status_mask,
         index = 3,
         selectedIndex = selectedIndex,
         modifier = fillEach
      )
      PageTabButton(
         text = "SETTINGS",
         vector = R.drawable.tab_settings_mask,
         index = 4,
         selectedIndex = selectedIndex,
         modifier = fillEach
      )
   }
}

@Preview(showBackground = true)
@Composable
fun PageTabBarPreview() {
   Pager4Theme {
      PageTabBar()
   }
}
What it looks like...
(I put the same thing up on codereview.stackexchcange, but there doesn't seem to be a lot of Compose activity there, since there's no tag for it -- https://codereview.stackexchange.com/questions/283564/learning-jetpack-compose-tabbar-with-paging-screens)
d

dorche

02/27/2023, 8:21 PM
For the Bottom Bar I'd suggest starting here https://developer.android.com/jetpack/compose/navigation#bottom-nav Do you really need the pager there? If all you're after is slide animation between your screens but no other pager-like behaviour. I'd also suggest reading the docs again about state and event management. There's a slight mind shift if you're coming from the View system, the one liner to remember is "state is passed down, events are bubbled up"
9 Views