Integrate Display capability into your Android app

Updated: May 14, 2026

Overview

This guide explains how to integrate display functionality into your existing Android app. For a complete guide on adding Wearables Device Access Toolkit capabilities to an existing Android app, including registration, streaming, and photo capture, check out the Android integration guide.

Create a visual display experience for your app

Step 1: Add the Display dependency

In your app-level build.gradle.kts:
dependencies {
    implementation("com.meta.wearable:mwdat-core:<version>")
    implementation("com.meta.wearable:mwdat-display:<version>")
}

Step 2: Configure AndroidManifest.xml

Enable the DAT App Model (DAM) in your app’s AndroidManifest.xml file.
<manifest ...>
    <application ...>
        <!-- Meta Wearables Device Access Toolkit Setup -->
        <meta-data android:name="com.meta.wearable.mwdat.DAM_ENABLED" android:value="true" />
    </application>
</manifest>

Step 3: Import packages

Import the required display packages.
import com.meta.wearable.dat.core.*
import com.meta.wearable.dat.display.*
import com.meta.wearable.dat.display.types.*
import com.meta.wearable.dat.display.views.*

Step 4: Connect to the display

Connect to the display by initializing the SDK, creating a session, and attaching the display capability.
// Initialize the SDK
Wearables.initialize(context)

// Create and start a session. Then observe session.state for DeviceSessionState.STARTED
val session = Wearables.createSession(AutoDeviceSelector()).getOrThrow()
session.start()
session.state.first { it == DeviceSessionState.STARTED }

// Attach the display capability. Then observe display.state for DisplayState.STARTED.
val displayResult = session.addDisplay(DisplayConfiguration())
val display = displayResult.getOrThrow()

// Send content once the display reaches STARTED
display.sendContent {
    flexBox(direction = Direction.COLUMN, gap = 12) {
        text("Hello, glasses!", style = TextStyle.HEADING)
    }
}

Step 5: Observe state changes

// Using Flow
display.state.collect { state ->
    when (state) {
        DisplayState.STARTED -> {
            // Display is ready, send content
        }
        DisplayState.STOPPED -> {
            // Display disconnected or stopped
        }
        DisplayState.STARTING,
        DisplayState.STOPPING -> {
            // Transitional states
        }
        DisplayState.CLOSED -> {
            // Terminal: capability has been removed from the session
        }
    }
}

Step 6: Stop display

When your app is done using the display capabilities, clean up resources.
session.removeDisplay()
session.stop()
Note: The glasses display dims after 20 seconds of inactivity and enters sleep mode at 25 seconds. Display sleep does not end a Wearables Device Access Toolkit session. When the display wakes, your app can either show the previously displayed content or send a new view.

Build content

Wearables Device Access Toolkit uses a declarative component system to build UI layouts for display, so you compose views using a result builder in Kotlin.
Each call to sendContent() replaces the entire display, so there are no incremental updates. Views are presented one at a time with vertical scrolling. Horizontal scrolling is not supported.

FlexBox

FlexBox is the primary layout container for Meta Ray-Ban Display apps. It helps you build declarative UI trees with text, images, icons, and buttons. This means that every layout starts with a root FlexBox. Inside, you arrange either these child components or nested FlexBoxes using specific layout properties.
flexBox arranges children along a main axis (row or column) with control over spacing, alignment, and wrapping.
flexBox(
    direction = Direction.COLUMN,      // COLUMN, ROW, COLUMN_REVERSE, ROW_REVERSE
    gap = 12,                          // Gap between children in dp
    alignment = Alignment.START,       // Main axis: START, CENTER, END, STRETCH
    crossAlignment = Alignment.CENTER, // Cross axis: START, CENTER, END, STRETCH
    wrap = true,                       // Whether children wrap
    paddingTop = 16,                   // Per-edge padding in dp
    paddingBottom = 16,
    paddingStart = 16,
    paddingEnd = 16
) {
    // Child components go here
}

Text

Display styled text. Three style presets match the glasses design system.
TextStyleDescription
HEADING
Large, bold text for section titles
BODY
Standard text for general content
META
Small text for captions and metadata
TextColorDescription
PRIMARY
Default high-contrast text
SECONDARY
Lower-contrast supporting text
text("Welcome", style = TextStyle.HEADING)
text("Tap a button to continue", style = TextStyle.BODY, color = TextColor.SECONDARY)
text("Step 1 of 5", style = TextStyle.META, color = TextColor.SECONDARY)

Images

Display images loaded from URLs.
ImageSizeDescription
ICON
Small, inline-sized image
FILL
Fills the available space
CornerRadiusDescription
NONE
Sharp corners
SMALL
Slightly rounded
MEDIUM
Moderately rounded
image(
    uri = "https://example.com/photo.png",
    sizePreset = ImageSize.FILL,
    cornerRadius = CornerRadius.MEDIUM
)
Note: The Meta Ray-Ban Display glasses render at 600x600 resolution. Use appropriately sized images — there is no benefit to sending larger images, and oversized assets introduce lag due to Bluetooth bandwidth constraints.

Buttons

Implement tappable buttons with corresponding labels, optional icons, and click handlers.
ButtonStyleDescription
PRIMARY
High-emphasis filled button
SECONDARY
Medium-emphasis filled button
OUTLINE
Low-emphasis outlined button
button(
    label = "Continue",
    style = ButtonStyle.PRIMARY,
    iconName = IconName.ARROW_RIGHT,
    onClick = { /* Handle tap */ }
)

button(
    label = "Go back",
    style = ButtonStyle.OUTLINE,
    iconName = IconName.ARROW_LEFT,
    onClick = { /* Handle tap */ }
)

Icons

Display a system icon from a built-in catalog of 100+ glyphs, covering navigation, media, weather, social, and more.
IconStyleDescription
FILLED
Solid icon
OUTLINE
Hollow icon
icon(name = IconName.CHECKMARK_CIRCLE, style = IconStyle.FILLED)
icon(name = IconName.BELL, style = IconStyle.OUTLINE)
See the icon catalog for all available IconName enum values.
If you need an icon outside the built-in catalog, you can also provide your own custom image.

Flex layout properties

Flex properties (flexGrow, flexShrink) are constructor parameters on flexBox. To apply flex behavior to a non-flexBox child, wrap it in a flexBox.
flexBox(direction = Direction.ROW) {
    flexBox(flexGrow = 1f) {
        text("Label", style = TextStyle.BODY)
    }
    flexBox(flexShrink = 0f) {
        button("Action", style = ButtonStyle.PRIMARY)
    }
}
FlexBox containers also support click handling and per-edge padding.
flexBox(paddingAll = 16, onClick = { /* Handle tap */ }) {
    text("Tappable area", style = TextStyle.BODY)
}

Layouts

Column layout

Use Direction to determine the main axis along which children are arranged. This code sample illustrates a vertical layout with a header, body text, and action button.
display.sendContent {
    flexBox(direction = Direction.COLUMN, gap = 12, paddingAll = 16) {
        text("Oil Change Guide", style = TextStyle.HEADING)
        text("Learn how to change your car's oil in 6 easy steps.", style = TextStyle.BODY)
        text("Duration: 30 min", style = TextStyle.META, color = TextColor.SECONDARY)
        button(
            label = "Start",
            style = ButtonStyle.PRIMARY,
            iconName = IconName.ARROW_RIGHT,
            onClick = { showFirstStep() }
        )
    }
}

Row layout

This code sample illustrates a horizontal layout with an image thumbnail and text.
display.sendContent {
    flexBox(
        direction = Direction.ROW,
        gap = 12,
        crossAlignment = Alignment.CENTER,
        paddingAll = 16,
    ) {
        flexBox(direction = Direction.COLUMN, flexGrow = 1f) {
            image(
                uri = thumbnailUrl,
                sizePreset = ImageSize.FILL,
                cornerRadius = CornerRadius.MEDIUM,
            )
        }
        flexBox(direction = Direction.COLUMN, gap = 4, flexGrow = 3f) {
            text("Brake Pad Replacement", style = TextStyle.BODY)
            text("45 min", style = TextStyle.META, color = TextColor.SECONDARY)
        }
    }
}

Clickable list items

Create a list of tappable items, each with its own icon and label.
display.sendContent {
    flexBox(direction = Direction.COLUMN, gap = 8, paddingAll = 16) {
        text("Tutorials", style = TextStyle.HEADING)
        for (tutorial in tutorials) {
            flexBox(
                direction = Direction.ROW,
                gap = 12,
                crossAlignment = Alignment.CENTER,
                paddingAll = 12,
                onClick = { selectTutorial(tutorial) },
            ) {
                icon(name = IconName.GEAR, style = IconStyle.FILLED)
                text(tutorial.title, style = TextStyle.BODY)
            }
        }
    }
}
Create a step-by-step view with navigation buttons located at the bottom of the viewport.
display.sendContent {
    flexBox(direction = Direction.COLUMN, gap = 12, paddingAll = 16) {
        text(
            "Step ${stepIndex + 1} of $totalSteps",
            style = TextStyle.META,
            color = TextColor.SECONDARY,
        )
        text(step.title, style = TextStyle.HEADING)
        text(step.description, style = TextStyle.BODY)
        image(
            uri = step.imageUrl,
            sizePreset = ImageSize.FILL,
            cornerRadius = CornerRadius.MEDIUM,
        )
        flexBox(direction = Direction.ROW, gap = 8, alignment = Alignment.CENTER) {
            button(
                label = "Back",
                style = ButtonStyle.OUTLINE,
                iconName = IconName.ARROW_LEFT,
                onClick = { showPreviousStep() },
            )
            button(
                label = "Next",
                style = ButtonStyle.PRIMARY,
                iconName = IconName.ARROW_RIGHT,
                onClick = { showNextStep() },
            )
        }
    }
}

User inputs

Users can interact with display content through captouch. These interactions are abstracted as tap events and delivered to your app through callbacks.

Button clicks

Buttons receive tap events through their onClick handler.
button(label = "Accept", style = ButtonStyle.PRIMARY, onClick = { handleAccept() })

Tappable areas

Make any FlexBox tappable by passing an onClick handler.
flexBox(direction = Direction.ROW, gap = 8, onClick = { openFavorites() }) {
    icon(name = IconName.HEART, style = IconStyle.FILLED)
    text("Favorites", style = TextStyle.BODY)
}

Back gesture

The back gesture (a two-finger tap on the glasses temple) ends the display session.

Video

Video playback allows you to play video content from a URL on the full-screen video player of Meta Ray-Ban Display glasses.

Constraints

  • Format: MP4 only
  • Maximum dimensions: 400 pixels per side, 70,000 total pixels
  • URL scheme:https only
  • Concurrency: One video at a time per display session

Play video

Play video using VideoPlayer.
val player = VideoPlayer(
    source = VideoSource.Url("https://example.com/tutorial-clip.mp4"),
    codec = VideoCodec.MP4
)

// Send the video to the display
display.sendContent {
    video(player = player)
}

// Start video playback
player.play()

// Observe playback state
player.state.collect { state ->
    when (state) {
        VideoPlayerState.PLAYING -> { /* Video is playing */ }
        VideoPlayerState.ENDED -> { /* Video finished */ }
        VideoPlayerState.IDLE -> { /* Not started */ }
        VideoPlayerState.STARTING -> { /* Buffering */ }
        VideoPlayerState.PAUSE -> { /* Paused */ }
    }
}

// Stop playback
player.close()

Error handling

Display operations may fail. Handle errors gracefully to provide a better experience for your users.

Display errors

Display operations return DatResult, which will contain a DisplayError if an error occurred during that operation.
display.sendContent {
    flexBox { text("Hello") }
}.fold(
    onSuccess = { /* Content sent successfully */ },
    onFailure = { error, _ ->
        when (error) {
            DisplayError.CAPABILITY_DENIED ->
                // Device denied display capability — check Wearables Developer Center config
                checkDeveloperCenterSetup()
            DisplayError.DEVICE_DISCONNECTED ->
                // Glasses disconnected — prompt user to reconnect
                showReconnectPrompt()
            DisplayError.INVALID_SESSION_STATE ->
                // Display not in .STARTED state — wait for state change
                waitForDisplayReady()
            DisplayError.RENDERING_FAILED ->
                // Content failed to render on the glasses
                showRenderError()
            DisplayError.UNEXPECTED_ERROR ->
                // Unknown error
                showGenericError()
        }
    }
)

Session-level errors when adding a display

session.addDisplay().fold(
    onSuccess = { display -> /* Use the display */ },
    onFailure = { error, _ -> showError(error.description) }
)

Video errors

VideoPlayerError outlines errors for video playback on MRBD.
player.error.collect { error ->
    when (error) {
        VideoPlayerError.NOT_BOUND ->
            // play() called before player was bound — send content first
        VideoPlayerError.INVALID_URL ->
            // URL is blank or uses unsupported scheme (only https)
        VideoPlayerError.INVALID_DIMENSIONS ->
            // Video exceeds max dimensions (400px per side, 70,000 total pixels)
        VideoPlayerError.ALREADY_PLAYING ->
            // Another video is active on the device
        VideoPlayerError.STREAM_REJECTED ->
            // Device rejected the video stream
        VideoPlayerError.PLAYBACK_FAILED ->
            // Codec error or surface unavailable on glasses
        VideoPlayerError.UNEXPECTED_ERROR ->
            // Unknown error
        null -> { /* No error */ }
    }
}