Guiding Users with Subtlety: Implementing Rich Tool Tips for Onboarding in Jetpack Compose

Kerry Bisset
9 min readAug 12, 2024

When users open your app for the first time, how do you ensure they feel welcomed and know what to do? The initial experience can set the tone for their journey, and getting it right is a delicate balance between what your team thinks is helpful and what the user wants. Whether it’s guiding them through essential features or helping them understand the value of your app, the way you introduce users to your app can make all the difference. With Jetpack Compose, there are many ways to create a smooth onboarding process, but one approach stands out for its subtlety and effectiveness: using Rich Tooltips in Material 3. Let’s explore how this can enhance the first launch experience and why it might be the right choice for your app.

Traditional Onboarding Methods

Splash Screens and Welcome Slides

Splash screens and welcome slides have long been the app onboarding process staples. These methods provide a simple and visually appealing way to introduce users to your brand and highlight key features of the app. Typically, splash screens are the first thing users see when they open the app, often displaying its logo or a brief loading animation. Following this, welcome slides offer a series of screens that walk users through the app’s core functionality, often with illustrations or short descriptions.

While these methods can effectively set the stage for what users can expect, they have limitations. Splash screens sometimes feel like unnecessary delays, especially if they linger too long. Welcome slides, though informative, can overwhelm users with too much information upfront. In today’s fast-paced digital environment, users might skip through them without absorbing the content. In addition, they may require a UI freeze to ensure they are not out of date by the time users install the application.

Example of Welcome Slides

Walkthrough Screens

Another common approach is using walkthrough screens, which guide users through the app’s interface and features after signing in or completing an initial setup. These screens often highlight essential areas of the app, using arrows or overlays to direct attention to specific buttons or sections.

Walkthroughs are more interactive than welcome slides and can be a great way to ensure users don’t miss critical features. However, they can also be intrusive, especially if users are eager to start using the app. For some, walkthroughs might interrupt the natural flow of exploration, leading to frustration rather than engagement.

The Power of Rich Tool Tips in Material 3

While I enhanced a view in one of my application’s screens, I needed a subtle yet effective way to guide the user. A traditional dialog felt too intrusive, interrupting the flow of the app. That’s when I turned to rich tooltips — small, contextual pop-ups that offer guidance without overwhelming the user. Unlike dialogs, which demand immediate attention and action, tooltips can remain on the screen until the user decides to dismiss them, making them an ideal choice for a more flexible and user-friendly onboarding experience.

Rich Tool Tips in Material 3

Material 3, focusing on adaptability and user experience, provides excellent support for implementing rich tooltips. These tooltips are not just simple text boxes; they can be styled and customized to align with your app’s design language, offering integration with your overall UI.

Rich tooltips are especially powerful in the context of onboarding. They can highlight key features or provide instructions as users explore the app without forcing them to follow a predetermined path. This makes them less intrusive and more aligned with a user-driven exploration process. For example, a rich tooltip can appear if a user pauses over a new button or section, providing helpful information that enhances understanding without disrupting the user’s flow.

Benefits of Using Rich Tool Tips for Onboarding

Rich tooltips' flexibility allows for a smoother onboarding process. They stay on the screen as long as needed, allowing users to fully absorb the information before moving on. This approach respects the user’s pace, offering assistance when necessary without being overbearing.

Moreover, rich tool tips can be context-aware. They can appear based on user interactions, such as hovering over a new feature or accessing a specific part of the app for the first time. This targeted guidance ensures that users receive help exactly when needed, improving their initial experience and long-term engagement with the app.

Implementing Rich Tool Tips in Jetpack Compose

Creating RichTooltips in Jetpack Compose is a powerful way to provide users with contextual guidance without interrupting their experience. Below, we’ll walk through the key components needed to set up a RichTooltip, using a practical example to illustrate how they come together.

Basic Components of a Rich Tool Tip

Consider the following example, where a RichTooltip guides users through different screen interactions.

Setting Up the ExplanationArea

The ExplanationArea composable is the core component for displaying the tooltip. It consists of the tooltip's title, details, and a customizable action button. This composable leverages the TooltipBox provided by Material 3, which allows for flexible positioning and styling of the tooltip.

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ExplanationArea(
title: String,
details: String,
onNext: () -> Unit,
tooltipState: TooltipState,
content: @Composable () -> Unit
) {
TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = {
RichTooltip(title = { Text(title) }, action = {
TextButton(onClick = onNext) {
Text("Next")
}
}) {
Text(details)
}
},
state = tooltipState,
) {
content()
}
}
  • title and details: These parameters define the tooltip's content, guiding the user with concise and relevant information.
  • onNext: A callback function triggered when the user clicks the action button within the tooltip, allowing the tooltip to proceed to the next step or close.
  • tooltipState: Manages the tooltip's visibility and lifecycle, ensuring it remains on the screen until dismissed by the user.
  • content: The main UI element that the tooltip is associated with, such as a button or text field.

Integrating Tool Tips into the UI

The tooltip is integrated into the UI by wrapping key components with the ExplanationArea. Here’s how it looks when used with different UI elements like a floating action button, a bottom bar, and a text field:

Scaffold(
floatingActionButtonPosition = FabPosition.EndOverlay,
floatingActionButton = {
ExplanationArea(
title = "Create a New Entry",
details = "Click here to add a new form. You'll be able to fill in all necessary details and submit once you're ready.",
onNext = { viewModel.onInteraction(TestSecondScreenInteractions.GoToNextTip) },
tooltipState = viewModel.mapOfToolTipState[TestSecondScreenViewModel.GuidanceStep.STEP_TWO]!!,
) {
FloatingActionButton({ viewModel.onInteraction(TestSecondScreenInteractions.GoToBack) }) {
Icon(Icons.Default.Add, contentDescription = "Back")
}
}
},
bottomBar = {
BottomAppBar {
ExplanationArea(
title = "Navigate Back",
details = "Press this button to return to the previous screen. Your progress will be saved automatically.",
onNext = { viewModel.onInteraction(TestSecondScreenInteractions.GoToNextTip) },
tooltipState = viewModel.mapOfToolTipState[TestSecondScreenViewModel.GuidanceStep.STEP_FIVE]!!,
) {
IconButton({ viewModel.onInteraction(TestSecondScreenInteractions.GoToBack) }) {
Icon(Icons.Default.ArrowBackIosNew, contentDescription = "Back")
}
}
}
},
content = {
Column(modifier = Modifier.padding(it)) {
ExplanationArea(
title = "Fill in the Details",
details = "Enter the required information in this field. Accurate data helps ensure the best results.",
onNext = { viewModel.onInteraction(TestSecondScreenInteractions.GoToNextTip) },
tooltipState = viewModel.mapOfToolTipState[TestSecondScreenViewModel.GuidanceStep.STEP_FOUR]!!,
) {
OutlinedTextField(
viewModel.showText,
onValueChange = { t ->
viewModel.onInteraction(TestSecondScreenInteractions.ShowText(t))
},
label = { Text("Example Field") }
)
}
}
}
)

Managing User Interaction with Tool Tips

To maintain a user-friendly experience, it’s necessary to manage the state and progression of tooltips effectively. In this example, the viewModel.mapOfToolTipState controls the visibility of each tooltip, ensuring they appear at the right time based on user actions.

viewModel.onInteraction(TestSecondScreenInteractions.GoToNextTip)

Managing Tool Tip Logic in the ViewModel

The underlying logic must be well-structured to guide users through your application. The ViewModel manages the state and interactions of these tooltips. Let’s walk through how the TestSecondScreenViewModel is designed to handle the logic for displaying the steps in the example.

Initializing Tool Tip States

The ViewModel starts by setting up the initial states for each tooltip using the TooltipState class. Each step in the onboarding process is associated with a GuidanceStep, which defines the order and flow of the tooltips.

internal val mapOfToolTipState = mapOf(
GuidanceStep.STEP_ONE to TooltipState(initialIsVisible = false, isPersistent = true),
GuidanceStep.STEP_TWO to TooltipState(initialIsVisible = false, isPersistent = true),
GuidanceStep.STEP_THREE to TooltipState(initialIsVisible = false, isPersistent = true),
GuidanceStep.STEP_FOUR to TooltipState(initialIsVisible = false, isPersistent = true),
GuidanceStep.STEP_FIVE to TooltipState(initialIsVisible = false, isPersistent = true)
)
  • initialIsVisible = false: Ensures the tooltip is not visible when the screen loads. It will only appear when triggered by user interaction.
  • isPersistent = true: Keeps the tooltip on the screen until the user manually dismisses it, giving them control over the pacing of the onboarding process.

Handling User Interactions

The onInteraction method in the ViewModel processes different user actions, such as navigating back, proceeding to the next tooltip, or skipping the guidance altogether. This method ensures that the correct tooltip is displayed based on the current step in the onboarding process.

internal fun onInteraction(interactions: TestSecondScreenInteractions) {
when (interactions) {
TestSecondScreenInteractions.GoToBack -> {
goBackAvailable = false
navController.popBackStack()
}

TestSecondScreenInteractions.GoToNextTip -> {
if (currentGuidanceStep == null) {
showGuidanceAvailable = false
}
val previousGuidanceStep = currentGuidanceStep
currentGuidanceStep = if (previousGuidanceStep == null) {
GuidanceStep.STEP_ONE
} else {
GuidanceStep.entries.getOrNull(currentGuidanceStep!!.ordinal + 1)
}

mapOfToolTipState[currentGuidanceStep]?.let {
MainScope().launch(Dispatchers.Main.immediate) {
previousGuidanceStep?.let {
mapOfToolTipState[it]?.dismiss()
}
it.show()
}
}
}
TestSecondScreenInteractions.SkipGuidance -> {
showGuidanceAvailable = false
}

is TestSecondScreenInteractions.ShowText -> {
showText = interactions.text
}
}
}
  • GoToNextTip: Advances the user to the next step in the onboarding sequence. The currentGuidanceStep is updated, and the corresponding tooltip is displayed using the show() method. The previous tooltip, if any, is dismissed to ensure that only one tooltip is visible at a time.
  • SkipGuidance: This option allows users to skip the entire guidance process if they prefer, hiding all tooltips and letting them explore the app on their own.
  • GoToBack: Handles the back navigation, ensuring that any in-progress tool tip sequences are appropriately managed.

Managing State Persistence and Transitions

The ViewModel maintains the state of each tooltip and ensures a smooth transition between them. Using coroutines within the MainScope ensures these transitions are handled asynchronously, providing a responsive user experience.

MainScope().launch(Dispatchers.Main.immediate) {
previousGuidanceStep?.let {
mapOfToolTipState[it]?.dismiss()
}
it.show()
}

Outcome

Challenges and Considerations with Rich Tool Tips

1. Disruption of Flow

One of the primary challenges with rich tooltips is that the flow of guidance can get disrupted if the user doesn’t interact with the tooltip as intended. Unlike dialogs, which typically require user action to proceed (such as dismissing the dialog), tooltips can remain on the screen indefinitely. If the user doesn’t hit the “Next” button, the onboarding process can stall, potentially confusing the user about what to do next.

This issue can make the onboarding process feel less cohesive, as it relies heavily on the user’s initiative to continue. Without a fallback mechanism like an onDismiss event, there’s a risk that users might miss out on important guidance.

2. Code Complexity

Implementing rich tooltips can lead to a more code-heavy solution than other onboarding methods. Each tooltip requires careful management of its state and transitions, particularly when multiple tooltips are involved. The ViewModel logic, as we’ve seen, can become quite detailed and intricate to ensure that the right tooltip is shown at the right time.

This complexity might not be necessary for all applications. A less complex onboarding method might be more appropriate for simpler apps or those with more intuitive features, such as a simple dialog or walkthrough. It’s important to weigh the benefits of rich tooltips against the additional development effort required.

3. Highlighting the UI Element

One of the strengths of rich tooltips is that they highlight the UI element they are associated with, drawing the user’s attention directly to the relevant part of the interface. However, this also means that the highlighted element might remain emphasized if the user doesn’t engage with the tooltip, potentially causing visual clutter or confusion.

A potential solution to this issue is introducing a small indicator pointing from the tooltip to the UI element, like a caret or an arrow. This can help reinforce the connection between the guidance provided by the tooltip and the action the user is expected to take, even if they don’t immediately interact with the tooltip.

4. Balancing Guidance and Autonomy

Another consideration is finding the right balance between guiding the user and allowing them to explore the app independently. Overusing rich tool tips can make the app feel overly guided, potentially frustrating users who prefer to learn through exploration. It’s important to ensure that tooltips are used strategically, providing help only where it’s most needed.

--

--