display-access

bởi facebook

Thiết lập khả năng hiển thị, chọn thiết bị hỗ trợ hiển thị, UI DSL, biểu tượng, nút bấm, hình ảnh và phát video

npx skills add https://github.com/facebook/meta-wearables-dat-android --skill display-access

Display Access (Android)

Use mwdat-display to render content on Meta Ray-Ban Display glasses.

Use this skill with getting-started and permissions-registration when creating a full app. A Display app still needs SDK initialization, app registration, Android permissions, and DAT manifest metadata before it can create a session.

Add the Display dependency

In libs.versions.toml:

[libraries]
mwdat-display = { group = "com.meta.wearable", name = "mwdat-display", version.ref = "mwdat" }

In app/build.gradle.kts:

dependencies {
    implementation(libs.mwdat.core)
    implementation(libs.mwdat.display)
}

Configure the app for Display

Display apps need the same core DAT setup as other apps:

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />

<application ...>
    <meta-data
        android:name="com.meta.wearable.mwdat.APPLICATION_ID"
        android:value="${mwdat_application_id}" />
    <meta-data
        android:name="com.meta.wearable.mwdat.CLIENT_TOKEN"
        android:value="${mwdat_client_token}" />
</application>

Set mwdat_application_id and mwdat_client_token from Gradle manifest placeholders or local.properties, as in the DisplayAccess sample. Developer Mode builds can use the developer registration flow, but production builds need real Wearables Developer Center credentials.

Request the runtime permissions before initializing DAT, then call Wearables.initialize(context) once and start observing SDK state. Start registration with Wearables.startRegistration(activity), collect Wearables.registrationState, collect Wearables.registrationErrorStream, and wait for RegistrationState.REGISTERED before creating a display session. Use Wearables.startUnregistration(activity) when the user disconnects the app.

Select a display-capable device

Display content only works on connected, compatible devices whose type supports display. Use the public device filter when you want automatic selection:

import com.meta.wearable.dat.core.Wearables
import com.meta.wearable.dat.core.selectors.AutoDeviceSelector

val selector = AutoDeviceSelector(filter = { device -> device.isDisplayCapable() })
val sessionResult = Wearables.createSession(selector)

Use SpecificDeviceSelector(selectedDeviceId) instead when your UI lets the user pick a specific DeviceIdentifier from Wearables.devices.

For device picker UI, mirror the DisplayAccess sample: collect Wearables.devices, start a metadata collection for each device ID from Wearables.devicesMetadata[id], remove metadata for devices that disappear, and show device name, device.deviceType.description, device.linkState, device.compatibility, and device.isDisplayCapable(). Keep selection disabled unless the device is LinkState.CONNECTED and display-capable, and surface DeviceCompatibility.DEVICE_UPDATE_REQUIRED with an openFirmwareUpdate action.

import com.meta.wearable.dat.core.types.Device
import com.meta.wearable.dat.core.types.DeviceIdentifier
import com.meta.wearable.dat.core.types.LinkState
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update

private val metadataJobs = mutableMapOf<DeviceIdentifier, Job>()
private val devicesMetadata = MutableStateFlow<Map<DeviceIdentifier, Device>>(emptyMap())

lifecycleScope.launch {
    Wearables.devices.collect { deviceIds ->
        (metadataJobs.keys - deviceIds).forEach { removedId ->
            metadataJobs.remove(removedId)?.cancel()
            devicesMetadata.update { current -> current - removedId }
        }

        deviceIds.forEach { id ->
            metadataJobs.getOrPut(id) {
                launch {
                    Wearables.devicesMetadata[id]?.collect { device ->
                        devicesMetadata.update { current -> current + (id to device) }
                        updateDeviceRow(
                            id = id,
                            name = device.name,
                            type = device.deviceType.description,
                            isConnected = device.linkState == LinkState.CONNECTED,
                            isDisplayCapable = device.isDisplayCapable(),
                            compatibility = device.compatibility,
                        )
                    }
                }
            }
        }
    }
}

If Wearables.createSession(...) fails with DeviceSessionError.DAT_APP_ON_THE_GLASSES_UPDATE_REQUIRED, show a DAT glasses app update action that calls Wearables.openDATGlassesAppUpdate(activity).

Attach Display after the session starts

Add the Display capability only after the DeviceSession reaches DeviceSessionState.STARTED. Keep the sample app pattern of showing a "preparing" state after session start and enabling user content only once the Display capability reports DisplayState.STARTED.

import androidx.lifecycle.lifecycleScope
import com.meta.wearable.dat.core.Wearables
import com.meta.wearable.dat.core.types.DeviceIdentifier
import com.meta.wearable.dat.core.types.DeviceSessionError
import com.meta.wearable.dat.core.types.RegistrationState
import com.meta.wearable.dat.core.session.DeviceSession
import com.meta.wearable.dat.core.session.DeviceSessionState
import com.meta.wearable.dat.core.selectors.SpecificDeviceSelector
import com.meta.wearable.dat.display.Display
import com.meta.wearable.dat.display.addDisplay
import com.meta.wearable.dat.display.removeDisplay
import com.meta.wearable.dat.display.types.DisplayState
import kotlinx.coroutines.launch

private var display: Display? = null

fun startDisplaySession(selectedDeviceId: DeviceIdentifier) {
    if (Wearables.registrationState.value != RegistrationState.REGISTERED) {
        showError("Register with Meta AI before starting Display")
        return
    }

    Wearables.createSession(SpecificDeviceSelector(selectedDeviceId))
        .fold(
            onSuccess = { session ->
                lifecycleScope.launch {
                    session.errors.collect { error ->
                        if (error == DeviceSessionError.DAT_APP_ON_THE_GLASSES_UPDATE_REQUIRED) {
                            showDatAppUpdateAction()
                        }
                        showError(error.description)
                    }
                }
                lifecycleScope.launch {
                    session.state.collect { state ->
                        if (state == DeviceSessionState.STARTED && display == null) {
                            attachDisplay(session)
                        }
                    }
                }
                session.start()
            },
            onFailure = { error, _ ->
                if (error == DeviceSessionError.DAT_APP_ON_THE_GLASSES_UPDATE_REQUIRED) {
                    showDatAppUpdateAction()
                }
                showError(error.description)
            },
        )
}

private fun attachDisplay(session: DeviceSession) {
    session.addDisplay()
        .fold(
            onSuccess = { newDisplay ->
                display = newDisplay
                lifecycleScope.launch {
                    newDisplay.state.collect { state ->
                        if (state == DisplayState.STARTED) {
                            setTryItEnabled(true)
                        } else {
                            setTryItEnabled(false)
                        }
                    }
                }
            },
            onFailure = { error, _ ->
                showError(error.description)
            },
        )
}

fun stopDisplaySession(session: DeviceSession) {
    session.removeDisplay().onFailure { error, _ -> showError(error.description) }
    session.stop()
    display = null
}

Send display UI

Build exactly one root view per sendContent call. Each call replaces the previous content on the glasses. Use a root flexBox { ... } for UI, or a root video(player = player) for video; do not put video(...) inside a flexBox.

Button and clickable flexBox callbacks are routed back to the phone app. Keep callbacks fast and delegate to your ViewModel to send the next content screen, as the DisplayAccess sample does for list items, Back, Start, Previous, Next, Done, and Watch video buttons.

import com.meta.wearable.dat.display.views.ButtonStyle
import com.meta.wearable.dat.display.views.FlexBoxBackground
import com.meta.wearable.dat.display.views.IconName
import com.meta.wearable.dat.display.views.TextColor
import com.meta.wearable.dat.display.views.TextStyle

private suspend fun sendStatusCard(display: Display) {
    display.sendContent {
        flexBox(
            gap = 12,
            padding = 24,
            background = FlexBoxBackground.CARD,
            onClick = { showDetailState() },
        ) {
            text("Bike ride", style = TextStyle.HEADING)
            text("Turn right in 200 ft", style = TextStyle.BODY, color = TextColor.SECONDARY)
            button(
                label = "Done",
                style = ButtonStyle.PRIMARY,
                iconName = IconName.CHECKMARK,
                onClick = { showDoneState() },
            )
        }
    }.onFailure { error, _ ->
        showError(error.description)
    }
}

Use images and built-in icons

Use HTTPS image URLs for image content, and use IconName enum values for built-in icons. Do not invent string icon names.

import com.meta.wearable.dat.display.views.CornerRadius
import com.meta.wearable.dat.display.views.IconStyle
import com.meta.wearable.dat.display.views.ImageSize

display.sendContent {
    flexBox(gap = 8, padding = 24) {
        image(
            uri = "https://example.com/thumbnail.png",
            sizePreset = ImageSize.FILL,
            cornerRadius = CornerRadius.MEDIUM,
        )
        icon(name = IconName.GEAR, style = IconStyle.FILLED)
        text("Device settings", style = TextStyle.BODY)
    }
}

Send video

For URL-based video, create a VideoPlayer, send it as the root content, then call play() after the send succeeds. Collect both player.state and player.error; on VideoPlayerState.ENDED, cancel the video observer and send the next display screen. Use an HTTP or HTTPS VideoSource.Url, VideoCodec.MP4 for MP4 assets, and a video sized within the public player limits.

import com.meta.wearable.dat.display.types.VideoCodec
import com.meta.wearable.dat.display.types.VideoPlayerState
import com.meta.wearable.dat.display.types.VideoSource
import com.meta.wearable.dat.display.views.VideoPlayer
import kotlinx.coroutines.Job

private var videoStateJob: Job? = null
private var videoErrorJob: Job? = null

lifecycleScope.launch {
    val currentDisplay = display ?: return@launch
    val player = VideoPlayer(
        source = VideoSource.Url("https://example.com/tutorial.mp4"),
        codec = VideoCodec.MP4,
    )

    videoStateJob?.cancel()
    videoErrorJob?.cancel()

    videoStateJob = launch {
        player.state.collect { state ->
            if (state == VideoPlayerState.ENDED) {
                videoStateJob?.cancel()
                videoStateJob = null
                sendStatusCard(currentDisplay)
            }
        }
    }
    videoErrorJob = launch {
        player.error.collect { error ->
            if (error != null) {
                showError(error.description)
            }
        }
    }

    currentDisplay.sendContent { video(player = player) }
        .onSuccess { player.play() }
        .onFailure { error, _ -> showError(error.description) }
}

Display rules

  • Initialize DAT and complete registration before creating the session.
  • Observe Wearables.registrationErrorStream, session.errors, Display.sendContent failures, and VideoPlayer.error; DeviceSession.start() returns Unit and reports async failures through session.errors.
  • Use SpecificDeviceSelector for a user-picked device and AutoDeviceSelector(filter = { it.isDisplayCapable() }) only when automatic selection is acceptable.
  • Wait for DeviceSessionState.STARTED before calling session.addDisplay().
  • Wait for DisplayState.STARTED before enabling or sending user-triggered content.
  • If device metadata reports DeviceCompatibility.DEVICE_UPDATE_REQUIRED, offer Wearables.openFirmwareUpdate(activity).
  • If session creation or session.errors reports DAT_APP_ON_THE_GLASSES_UPDATE_REQUIRED, offer Wearables.openDATGlassesAppUpdate(activity).
  • Keep display callbacks fast; move app state changes back onto your UI layer as needed.
  • Cancel state/error collection jobs, close or replace active VideoPlayer instances, detach with session.removeDisplay(), and then stop the session when the display experience ends.

Sample app

Use the Display Access sample app for a complete flow: registration, device selection, display attachment, interactive content, and video.

Links

Thêm skills từ facebook

gc-safe-coding
facebook
Để biết giải thích đầy đủ và lý do, hãy xem doc/GCSafeCoding.md.
app-review-prep
facebook
Chuẩn bị ứng dụng Meta cho App Review — kiểm tra trạng thái hiện tại, các yêu cầu còn tồn đọng, quyền đã được cấp và lịch sử gửi duyệt. Sử dụng trước khi gửi ứng dụng…
api-health
facebook
Theo dõi tình trạng API cho ứng dụng Meta — kiểm tra giới hạn tỷ lệ, khối lượng cuộc gọi và các API bị ngừng hỗ trợ. Dùng để chẩn đoán tình trạng hạn chế, lập kế hoạch dung lượng hoặc chuẩn bị cho phiên bản API…
debug-webhooks
facebook
Khắc phục sự cố webhook cho ứng dụng Meta — kiểm tra các đăng ký đang hoạt động, xác định cấu hình sai và gửi payload thử nghiệm để xác minh việc phân phối. Sử dụng khi…
api-integration
facebook
Hướng dẫn nhà phát triển thiết lập tích hợp Meta API từ đầu — khám phá các API phù hợp, tìm nạp hướng dẫn thiết lập, yêu cầu xác thực,…
webhook-setup
facebook
Thiết lập webhooks cho ứng dụng Meta từ đầu đến cuối — khám phá các chủ đề có sẵn, đăng ký nhận các trường và xác minh bằng payload kiểm thử. Sử dụng khi cấu hình webhooks cho…
test-ui
facebook
Kiểm thử hệ thống UI (PanelUI, ScreenSpace) với ví dụ poke bằng iwsdk CLI.
flags
facebook
Kiểm tra và so sánh trạng thái cờ tính năng trên các kênh phát hành React. Xem tất cả cờ trên các kênh (www, www-modern, canary, next, experimental, biến thể rn) hoặc so sánh các kênh cụ thể với --diff. Định dạng đầu ra bao gồm chế độ xem bảng mặc định, xuất CSV và nhóm trạng thái dọn dẹp. Trạng thái cờ được biểu thị bằng ký hiệu: bật (✅), tắt (❌), kiểm thử biến thể (🧪), chỉ lập hồ sơ (📊). Lỗi thường gặp: cờ __VARIANT__ được kiểm thử ở cả hai trạng thái trên www; sử dụng --diff để phát hiện sự khác biệt có ý nghĩa...