Onboarding Screen using Jetpack Compose

Manish Kaushik
5 min readJul 9, 2023

The onboarding screen is an essential part of many mobile applications, providing a user-friendly introduction to the app’s features and functionality. In this tutorial, we will explore how to build an onboarding screen using Jetpack Compose, the modern toolkit for building native Android UIs. We will leverage the power of Compose to create a visually appealing and interactive onboarding experience. So let’s get started!

For full code check the code on github: If its helpful for you, please don`t forget to like the repsoitory.

Prerequisites: Before diving into the implementation, make sure you have the following prerequisites set up:

  • Android Studio Arctic Fox (2020.3.1) or later.
  • Jetpack Compose version 1.0.0 or later.
Preview onBoarding Screen

As you can see in above image, we will now divide the Screen into small composable components to make it reusable and easy to test.

Data Class to hold the Pages:

data class OnboardPage(
val imageRes: Int, val title: String, val description: String
)

Sample Data to start with:

val onboardPagesList = listOf(
OnboardPage(
imageRes = R.drawable.image1,
title = "Welcome to Onboarding",
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
), OnboardPage(
imageRes = R.drawable.image2,
title = "Explore Exciting Features",
description = "Praesent at semper est, nec consectetur justo."
), OnboardPage(
imageRes = R.drawable.image3,
title = "Get Started Now",
description = "In auctor ultrices turpis at blandit."
)
)

Here’s the explanation for each composable function we will create:

OnboardScreen: This composable function represents the main onboarding screen. It takes no parameters and sets up the UI for the onboarding flow. It initializes the onboardPages list with the provided onboardPagesList. It also maintains the current page index using remember { mutableStateOf(0) }. The composable contains a column layout with the following children:

  • OnBoardImageView: Displays the image of the current onboarding page.
@Composable
fun OnBoardImageView(modifier: Modifier = Modifier, imageRes: Int) {
Box(modifier = modifier) {
Image(
painter = painterResource(id = imageRes),
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.FillWidth
)
Box(modifier = Modifier
.fillMaxSize()
.align(Alignment.BottomCenter)
.graphicsLayer {
// Apply alpha to create the fading effect
alpha = 0.6f
}
.background(
Brush.verticalGradient(
colorStops = arrayOf(
Pair(0.8f, Color.Transparent), Pair(1f, Color.White)
)
)
))
}
}
  1. Image: This composable displays the image using the provided imageRes. It uses the painterResource function to retrieve the image resource based on the imageRes ID. The contentDescription is set to null to indicate that it is decorative and does not require an accessibility description. The modifier is used to fill the available space (fillMaxSize()) within the Box.
  2. Box: This composable acts as a container for the image and the fading effect. It uses the modifier parameter to apply any additional modifiers passed to the OnBoardImageView composable.
  3. Fading Effect: The fading effect is created using another Box composable nested inside the main Box. The nested Box is aligned to the bottom center of the screen (align(Alignment.BottomCenter)) and fills the available space (fillMaxSize()).
  4. graphicsLayer: This modifier is used to apply transformations and effects to the nested Box. In this case, the alpha property is set to 0.6f to control the transparency of the fading effect.
  5. background: This modifier is applied to the nested Box and defines the background of the fading effect. It uses a Brush.verticalGradient to create a gradient that transitions from Color.Transparent at 80% of the height to Color.White at the bottom.
  • OnBoardDetails: Displays the title and description of the current onboarding page.
@Composable
fun OnBoardDetails(
modifier: Modifier = Modifier, currentPage: OnboardPage
) {
Column(
modifier = modifier
) {
Text(
text = currentPage.title,
style = MaterialTheme.typography.displaySmall,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = currentPage.description,
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
}
  • OnBoardNavButton: Provides navigation buttons i.e next button for moving to the next page or completing the onboarding.
@Composable
fun OnBoardNavButton(
modifier: Modifier = Modifier, currentPage: Int, noOfPages: Int, onNextClicked: () -> Unit
) {
Button(
onClick = {
if (currentPage < noOfPages - 1) {
onNextClicked()
} else {
// Handle onboarding completion
}
}, modifier = modifier
) {
Text(text = if (currentPage < noOfPages - 1) "Next" else "Get Started")
}
}
  • TabSelector: Displays tabs representing each onboarding page.
@Composable
fun TabSelector(onboardPages: List<OnboardPage>, currentPage: Int, onTabSelected: (Int) -> Unit) {
TabRow(
selectedTabIndex = currentPage,
modifier = Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.primary)
) {
onboardPages.forEachIndexed { index, _ ->
Tab(selected = index == currentPage, onClick = {
onTabSelected(index)
}, modifier = Modifier.padding(16.dp), content = {
Box(
modifier = Modifier
.size(8.dp)
.background(
color = if (index == currentPage) MaterialTheme.colorScheme.onPrimary
else Color.LightGray, shape = RoundedCornerShape(4.dp)
)
)
})
}
}
}

Explanation:

  1. TabRow: This is a built-in Jetpack Compose composable that represents a row of tabs. It takes the selectedTabIndex parameter, which indicates the index of the currently selected tab. We pass the currentPage parameter to selectedTabIndex so that the appropriate tab is selected based on the current page index. The modifier is used to apply additional modifiers to the TabRow. In this case, we set the width to fillMaxWidth() and apply a background color from the MaterialTheme.colorScheme.primary to the TabRow.
  2. onboardPages.forEachIndexed: This loops through each OnboardPage in the onboardPages list and creates a Tab composable for each page. The index parameter represents the current index of the loop, and the underscore _ is used to ignore the page object itself since it is not used inside the loop.
  3. Tab: This composable represents an individual tab in the TabRow. The selected parameter is set to true when the index of the current tab matches the currentPage. The onClick parameter is a callback function that is triggered when the tab is clicked. It calls onTabSelected with the index of the clicked tab.
  4. modifier.padding(16.dp): This applies padding to each individual tab, giving it some spacing from neighboring tabs.
  5. content: This is the content of each tab, defined as a Box composable. Inside the Box, we create a small box (size(8.dp)) with a background color. The background color is determined by the MaterialTheme.colorScheme.onPrimary if the tab is currently selected (index == currentPage), and Color.LightGray otherwise. We also apply a RoundedCornerShape(4.dp) to give the box a rounded appearance.

Now we have all the components needed to display our Onboarding Screen. We will now add them into the Column sequence in our OnboardScreen composable function.

@Composable
fun OnboardScreen() {

val onboardPages = onboardPagesList

val currentPage = remember { mutableStateOf(0) }

Column(
modifier = Modifier.fillMaxSize()
) {

OnBoardImageView(
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
imageRes = onboardPages[currentPage.value].imageRes
)

OnBoardDetails(
modifier = Modifier
.weight(1f)
.padding(16.dp),
currentPage = onboardPages[currentPage.value]
)

OnBoardNavButton(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(top = 16.dp),
currentPage = currentPage.value,
noOfPages = onboardPages.size
) {
currentPage.value++
}

TabSelector(
onboardPages = onboardPages,
currentPage = currentPage.value
) { index ->
currentPage.value = index
}
}
}

For full code check the code on github: If its helpful for you, please don`t forget to like the repsoitory and this blog 👏.

--

--