Jetpack Compose: Tooltip Implementation

In modern Android app development using Jetpack Compose, providing informative and contextual help to users is crucial for a good user experience. Tooltips are a common UI element that displays information when a user hovers over or long-presses an item. This guide will show you how to implement tooltips effectively in Jetpack Compose.

What is a Tooltip?

A tooltip is a small, informative popup that appears when a user interacts with a UI element, typically by hovering over it with a mouse or performing a long press on a touch screen. It provides contextual help or supplementary information about the element.

Why Use Tooltips?

  • Enhance User Experience: Provides immediate and contextual help.
  • Improve Accessibility: Helps users understand the purpose of UI elements.
  • Reduce Clutter: Keeps the main UI clean by hiding less frequently needed information.

Implementing Tooltips in Jetpack Compose

Jetpack Compose doesn’t have a built-in tooltip component. However, you can create a custom tooltip using Compose’s composables and modifiers.

Method 1: Custom Tooltip with PopupWindow

One approach is to use a PopupWindow to display the tooltip. This method offers flexibility in positioning and styling the tooltip.

Step 1: Create a Tooltip Composable

Define a composable that displays the tooltip content inside a PopupWindow.


import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties

@Composable
fun Tooltip(
    text: String,
    content: @Composable () -> Unit
) {
    var isTooltipVisible by remember { mutableStateOf(false) }
    val view = LocalView.current

    Box(
        modifier = Modifier.wrapContentSize()
    ) {
        content()

        if (isTooltipVisible) {
            Popup(
                alignment = Alignment.BottomCenter,
                properties = PopupProperties(
                    dismissOnOutsideClick = true,
                    focusable = true
                ),
                onDismissRequest = { isTooltipVisible = false }
            ) {
                Surface(
                    modifier = Modifier
                        .padding(8.dp)
                        .background(Color.Gray)
                ) {
                    Text(
                        text = text,
                        color = Color.White,
                        modifier = Modifier.padding(8.dp)
                    )
                }
            }
        }
    }
}


@Preview(showBackground = true)
@Composable
fun TooltipPreview() {
    Tooltip(text = "This is a tooltip!") {
        Button(onClick = {}) {
            Text("Hover Me")
        }
    }
}

In this implementation:

  • The Tooltip composable takes a text parameter for the tooltip message and a content lambda for the UI element to which the tooltip applies.
  • mutableStateOf is used to track the visibility of the tooltip.
  • Popup is used to create the tooltip window. It’s styled with a gray background and white text for visibility.
  • dismissOnOutsideClick is set to true, so the tooltip closes when the user clicks outside of it.
Step 2: Use the Tooltip Composable

Wrap the UI element you want to add a tooltip to with the Tooltip composable and control the tooltip’s visibility with a state variable.


import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.*
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalView

@Composable
fun MyComposableWithTooltip() {
    var isTooltipVisible by remember { mutableStateOf(false) }

    Tooltip(text = "Click Me for More Info!") {
        Button(
            onClick = { },
            modifier = Modifier.pointerInput(Unit) {
                detectTapGestures(
                    onLongPress = {
                        isTooltipVisible = true
                    },
                    onTap = {
                        isTooltipVisible = false // Optional: Hide on tap
                    }
                )
            }
        ) {
            Text("Clickable Button")
        }
    }
}

Key improvements and explanations:

  • detectTapGestures from androidx.compose.foundation.gestures.* is used to listen for both onLongPress to show the tooltip and onTap to potentially hide it.
  • Using Modifier.pointerInput(Unit) ensures that the gesture detection works correctly by providing a stable key.
  • The Tooltip composable encapsulates the logic for showing and hiding the tooltip using a Popup.

Method 2: Custom Tooltip with CompositionLocal and Overlay

This method creates a more modular tooltip using CompositionLocal and an overlay to manage the visibility and positioning.

Step 1: Define a Tooltip State

Create a data class to hold the tooltip state.


data class TooltipState(
    var isVisible: Boolean = false,
    var tooltipText: String = ""
)
Step 2: Create a CompositionLocal for the Tooltip State

Provide a CompositionLocal that holds the TooltipState.


import androidx.compose.runtime.compositionLocalOf

val LocalTooltipState = compositionLocalOf { TooltipState() }
Step 3: Create the Tooltip Composable

Use an overlay to display the tooltip conditionally based on the state.


import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun TooltipArea(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    val tooltipState = remember { TooltipState() }
    CompositionLocalProvider(LocalTooltipState provides tooltipState) {
        Box(modifier = modifier) {
            content()
            if (tooltipState.isVisible) {
                TooltipOverlay(tooltipState.tooltipText)
            }
        }
    }
}

@Composable
fun TooltipOverlay(text: String) {
    Surface(
        modifier = Modifier
            .padding(8.dp)
            .background(Color.Gray)
    ) {
        Text(
            text = text,
            color = Color.White,
            modifier = Modifier.padding(8.dp)
        )
    }
}
Step 4: Use the Tooltip Area

Wrap the UI element with TooltipArea and trigger the tooltip.


import androidx.compose.foundation.clickable
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier

@Composable
fun MyComposableWithTooltip() {
    val tooltipState = LocalTooltipState.current
    
    Button(
        onClick = { },
        modifier = Modifier.clickable {
            tooltipState.tooltipText = "More details here!"
            tooltipState.isVisible = true
        }
    ) {
        Text("Clickable Button")
    }
}

Best Practices for Tooltips

  • Keep Tooltips Concise: Tooltips should provide short, helpful information.
  • Use Proper Placement: Position tooltips so they don’t obscure the UI element.
  • Provide Clear Visual Cues: Ensure tooltips are visually distinct from the rest of the UI.

Conclusion

Implementing tooltips in Jetpack Compose involves creating custom composables and managing the visibility of these elements based on user interactions. Tooltips greatly enhance the user experience by providing contextual help. Understanding these implementation methods allows developers to add informative tooltips, thereby improving user satisfaction and overall app usability.