build.gradle.kts:dependencies {
implementation("com.meta.wearable:mwdat-core:<version>")
implementation("com.meta.wearable:mwdat-display:<version>")
}
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>
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.*
// 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)
}
}
// 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
}
}
}
session.removeDisplay() session.stop()
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 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
}
TextStyle | Description |
|---|---|
HEADING | Large, bold text for section titles |
BODY | Standard text for general content |
META | Small text for captions and metadata |
TextColor | Description |
|---|---|
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)
ImageSize | Description |
|---|---|
ICON | Small, inline-sized image |
FILL | Fills the available space |
CornerRadius | Description |
|---|---|
NONE | Sharp corners |
SMALL | Slightly rounded |
MEDIUM | Moderately rounded |
image(
uri = "https://example.com/photo.png",
sizePreset = ImageSize.FILL,
cornerRadius = CornerRadius.MEDIUM
)
ButtonStyle | Description |
|---|---|
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 */ }
)
IconStyle | Description |
|---|---|
FILLED | Solid icon |
OUTLINE | Hollow icon |
icon(name = IconName.CHECKMARK_CIRCLE, style = IconStyle.FILLED) icon(name = IconName.BELL, style = IconStyle.OUTLINE)
IconName enum values.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(paddingAll = 16, onClick = { /* Handle tap */ }) {
text("Tappable area", style = TextStyle.BODY)
}
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() }
)
}
}
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)
}
}
}
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)
}
}
}
}
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() },
)
}
}
}
onClick handler.button(label = "Accept", style = ButtonStyle.PRIMARY, onClick = { handleAccept() })
onClick handler.flexBox(direction = Direction.ROW, gap = 8, onClick = { openFavorites() }) {
icon(name = IconName.HEART, style = IconStyle.FILLED)
text("Favorites", style = TextStyle.BODY)
}
https onlyVideoPlayer.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()
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.addDisplay().fold(
onSuccess = { display -> /* Use the display */ },
onFailure = { error, _ -> showError(error.description) }
)
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 */ }
}
}