Android XR を使用すると、画面スペースを超えて、アプリを現実世界に固定できます。このセッションでは、Jetpack Compose Glimmer と ARCore の Geospatial API を使用して、地理空間を認識する Android XR エクスペリエンスを構築する方法を学びます。まず、ARCore でデバイスの地理空間ポーズを取得する方法について説明します。次に、Glimmer の最小限の UI コンポーネントを、ビジュアル ポジショニング システム(VPS)を介して現実世界の場所に固定する方法を説明します。これらを組み合わせて、位置情報を認識するシンプルな空間エクスペリエンスを構築します。
文字起こし
皆さん、こんにちは。Android Developer Summit の Android XR トラックの最初のセッションへようこそ。
イェー!
Android XR について多くの方に学んでいただき、大変嬉しく思います。
私は Yasmine Quintana Evjen です。Paul Hoover と Dereck Bridie も参加しています。2 人は、会話が進むにつれて参加します。
このセッションのトピックは Android XR です。Jetpack Compose Glimmer と ARCore for Jetpack XR を使用して、空間に固定された現実世界のエクスペリエンスを構築する方法について説明します。
その前に、Android XR について簡単に紹介します。
Android XR は、ユーザーがアプリを操作する方法を再定義する新しいプラットフォームです。画面を超えて、周囲の空間にまで広がります。
このプラットフォームは、デジタル エクスペリエンスを物理的な環境に直接もたらすように設計されています。
現実世界の存在感を高める一目でわかるオーバーレイや、無限の没入型環境など、さまざまな方法があります。
これは、私たちが長年にわたって一緒に構築してきたオープンな Android エコシステムと同じです。そして今、キャンバスは部屋と同じくらい大きく、想像力と同じくらい広大です。
Google は、XR をデバイスのスペクトルとして捉えています。このスペクトルは、オーディオ グラスやディスプレイ グラスで軽量な拡張エクスペリエンスを実現するスマートグラスから、集中力、生産性、没入型エンターテイメント機能を提供する没入型ヘッドセットや有線 XR グラスまで、多岐にわたります。
Android XR ファミリーの最新デバイスは XREAL AURA で、本日よりご購入いただけます。
このデバイスは軽量かつパワフルで、Android の使いやすさを備えた XR エクスペリエンスを実現します。
このプラットフォームのビジョンは、すでに Android 向けに開発しているデベロッパーが、XR 向けの開発にもすぐに取り組めるようにすることです。
Google は、使い慣れた同じアプリモデル、ライブラリ、ツールを使用して、Android XR 向けの開発を簡単に行えるようにしたいと考えています。
既存の 2D Android 互換アプリは、ほとんど変更を加えることなく、Android XR ヘッドセットや有線 XR グラスで動作します。
また、XR プラットフォームに最適な素晴らしい XR エクスペリエンスを作成できるように、XR プラットフォーム専用に構築された一連のライブラリを提供しています。
他の没入型プラットフォームでエクスペリエンスを構築することに慣れているデベロッパーは、OpenXR と、Unity、Godot、Unreal などの 3D エンジン サポートを使用して構築できます。
Android ライブラリを使用したビルドについて説明します。
Google は、没入型エクスペリエンスと拡張エクスペリエンス全体でアプリを XR に拡張するのに役立つ Kotlin ライブラリのセットである Jetpack XR SDK を提供しています。
没入型ライブラリを見てみましょう。
Jetpack XR ライブラリは今年、ベータ版と安定版がリリースされ、没入型 XR エクスペリエンスの構築を始めるのに必要な信頼性が確保されました。
知覚レイヤは Jetpack XR 向けの ARCore です。
このライブラリは、テーブルなどの平らな面がどこにあるか、ユーザーの手がどのように配置されているかなど、現実世界に関する情報を提供します。
3D 空間内のポイントを追跡し、適切な権限が付与されている場合は、その情報をアプリに提供します。
次に、Jetpack XR SceneCore があります。これは、空間コンテンツを配置するためのシーングラフを提供します。
このライブラリは、3D 空間でエンティティを表現でき、正確なシーングラフ配置のための空間動作を提供します。
また、Jetpack Compose for XR には、3D 専用に構築された新しいサブスペース コンポーザブルが用意されています。
行と列を使用した構築など、Compose レイアウトにすでに精通している Android デベロッパーは、空間 UI のレイアウトを直感的に行えると感じるでしょう。
空間行を作成すると、複数の空間パネルを並べて宣言的に配置できます。位置やサイズを手動で計算する必要はありません。
空間パネルやオービターなどのサブスペース コンポーザブルを使用すると、2D コンポーネントをこの新しい 3D 空間に簡単に拡張できます。
もちろん、サブスペース コンポーザブルは 2D コンテンツを表示するだけではありません。
Compose for XR は、空間外部サーフェスと空間 glTF モデルを介して、空間動画と 3D モデルをサポートしています。
次に、拡張アプリについて説明します。
Jetpack Projected ライブラリを使用すると、既存のモバイルアプリを拡張して、センサーやディスプレイなどのスマートグラス ハードウェアを利用できます。
最後に、Jetpack Compose Glimmer は、グラス向けに最適化された Android XR エクスペリエンスを構築するための UI ツールキットです。
Jetpack Compose Glimmer は、XR エクスペリエンスを構築するためのスタイルとすぐに使えるコンポーネントで構成されています。
昨年リリースした Glimmer は、その後も機能の拡張を続けており、Glimmer を使用してディスプレイ グラスアプリを構築するのに役立つエージェント スキルなどが追加されています。
android skills add display-glasses-with-jetpack-compose-glimmerリリース以来、Google は皆様からのフィードバックに耳を傾けてきました。
Jetpack Compose Glimmer をスマートグラス以外にも拡張してほしいというご要望をいただいていました。
このたび、Glimmer を使用した空間アンカー エクスペリエンスをより適切にサポートするための変更を加えました。これについては、この講演の後半で詳しく説明します。
Glimmer を最適化する取り組みは、ディスプレイから有線 XR グラスまで、さまざまな XR フォーム ファクタで表示するための新しいサーフェスを作成することから始まりました。
この新しいサーフェスの追加により、UI をカスタマイズする方法の柔軟性が向上します。
また、アプリのブランドの独自性を高めるために、Glimmer コンポーネントにカスタムカラーを追加しました。
これらを使用して、XR アプリ全体でブランド スタイルを表現します。
Jetpack Compose Glimmer ライブラリには、ターゲット カラーやブランドカラーをメガネ対応の色に変換するヘルパー関数が用意されています。
これは、光学シースルー ディスプレイでの視認性を最適化するためです。
では、実際に見てみましょう。
このメガネアプリでフォーカスされたボタンの色を変更したいので、私のお気に入りの色を 1 つ選択します。
val brandColor = Color(0xFFFFAABB)
val focusedColor = ButtonDefaults.focusedColor(brandColor)
Column(…) {
LeadingImageCard(
focusable = false,
…
) {
…
}
ButtonGroup(...) {
Button(
onClick = {},
leadingIcon = { … },
focusedColor = focusedColor,
) { Text("Add to cart") }
/* Rest of the buttons */
}
}コードに色が追加されると、Glimmer のヘルパー関数がその色をメガネに適した色に調整し、視認性を最適化します。
これらはすべて Jetpack Compose Glimmer ライブラリに組み込まれているため、さまざまな環境で優れたエクスペリエンスを構築できます。
Android Studio では、エミュレータを使用して、明るい環境、暗い環境、にぎやかな環境など、さまざまな背景でグラス UI がどのように表示されるかを可視化できます。
これらのアップデートはほんの一部です。
「いいえ」ですね。
チームからは、このジョークはウケないと言われましたが、私はまだ諦めていません。
ここからは、Paul にバトンタッチして、Jetpack Compose Glimmer について詳しく説明してもらいます。
テスト、よし!
[次へ] ボタンをクリックします。
Yasmine さん、ありがとうございました。
それでは
ヤスミンが述べたように、Jetpack Compose Glimmer は、ディスプレイ グラス向けに最適化されたアプリケーションを開発するための基本的な構成要素を提供します。
当社のコンポーネントは、光学シースルー ディスプレイ向けに構築されたベスト プラクティスを統合しており、開発者の皆様が美しく機能的な光学シースルー エクスペリエンスを構築できるように、API に直接組み込まれています。
このような光学シースルー デバイス向けのアプリを設計、構築する際には、いくつかの点に注意する必要があります。
まず、シースルー ディスプレイであるため、黒色はありません。
世界が見えるとき、黒は透明です。
そのため、UI、テキスト、コンテンツのコントラストを維持するために、暗い背景に頼ることはできません。
黒は不可視ですが、他のデジタル コンテンツを隠すために使用できます。
Glimmer UI フレームワークには、UI 要素を簡単に分離して階層感を提供できる Depth API があります。
このスタックのカードの奥行きにより、カードが互いに分離された状態を維持できます。
Google のビジョン サイエンティストは、ほとんどの照明条件で視認できるテキストとアイコンのターゲット コントラスト比を提示しています。
ヤスミンが示したように、この機能はフレームワークに組み込まれているため、サーフェスの色をカスタマイズする際に、インターフェースがユーザーにとって読みやすいものになることを確信できます。
Google は、これらのベスト プラクティスやその他のベスト プラクティスを、拡大を続けるコンポーネント ライブラリに組み込んでいます。
最近、多くの新しいコンポーネントがリリースされました。
最新のエディションの 1 つは、スムーズなモーション遷移とジェスチャーが組み込まれたページャー コンポーネントです。
val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}Compose の水平ページャーに慣れている場合は、このコンポーネントは非常に馴染みのあるものになるでしょう。
また、ページャー、スタック、その他の Glimmer UI とシームレスに統合できるように設計された 4 つの新しいカード バリエーションも導入しました。
先頭画像カードと末尾画像カードでは、テキストと画像が並んで表示されます。
テキストとビジュアル情報の量が同じ場合は、これらのカード バリエーションを使用します。
画像カードを独自のカード バリアントに分割しました。
この動画選択ツールのように、大きな画像と 1 行のテキストまたはキャプションがある場合に最適です。
アクション カード バリアントは、1 つのボタンをカードに埋め込みます。
このデスティネーション ピッカーのように、カードを有効にしたときに何が起こるかをユーザーに正確に知らせたい場合に使用します。
ボタングループを使用すると、多くのボタンを小さなスペースにまとめることができます。
ButtonGroup {
Button(onClick = {}) { Text("Button 1") }
Button(onClick = {}) { Text("Button 2") }
Button(onClick = {}) { Text("Button 3") }
Button(onClick = {}) { Text("Button 4") }
Button(onClick = {}) { Text("Button 5") }
}このシンプルなコードだけで、美しい遷移、スクロール動作、端を隠すスクリム(必要なときにのみ表示)、メガネからコントロールに自動的にルーティングされる入力が実現します。
ボタングループには、新しいアイコンボタンや切り替えボタンを含めることもできます。
var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}このメディア コントロールを作成するために使用されているすべてのボタンタイプをここで確認できます。
また、さまざまな背景で UI を簡単にテストできるようになりました。
このグラスエミュレータでは、360°写真を挿入してパンできるため、さまざまな背景、複雑な背景、明るい背景、暗い背景でテストできます。
ボタン グループは、このような詳細ビューでコンテキスト アクションを表示するのに適しています。
タップすると、共有要素遷移を使用して詳細なビューアの状態に移動し、システムの「戻る」ジェスチャーを使用して前のビューに戻ることができます。
とてもいいですね。
また、音声入力インジケーターとアラート ダイアログも追加しました。
このように、多くの新機能が追加されています。
新しい機能についていくつか確認したところで、実際に何かを作成してみましょう。
Glimmer は、スマートグラスの構築だけでなく、有線 XR グラス向けのエクスペリエンスの構築にも使用できます。
そして、現実世界のロケーションベースのアンカーから始めて、周囲の世界を拡張するエクスペリエンスを構築していただきたいと考えています。
たとえば、デベロッパー カンファレンスに参加するために初めて訪れた街で、食事をする場所を探しているとします。
お気に入りのナビアプリに、近くのレストランとその評価を尋ねることができます。
情報を場所に直接重ねて表示することで、コンテキスト、インテリジェンス、ガイダンスを提供できます。
ここでは、Icon Marker という最新の Glimmer コンポーネントを確認できます。
物理的な世界の重要な要素を強調表示し、このカードのような他の Glimmer コンポーネントと組み合わせることができます。
この例では、Google マップ、Places API、Geospatial API を使用してスポットにアノテーションを付けています。
デレック、設定方法を説明しよう。
いいですね。
それでは
それでは
ポールが先ほど言ったように、さっそく構築を始めましょう。
そこで、developer.android.com/xr のデベロッパー ドキュメントを実際に使用し、Jetpack Compose Glimmer コンポーネント ライブラリを確認します。
Glimmer で利用可能なすべてのコンポーネントの視覚的な概要を確認できます。
まず、Paul が先ほど紹介した UI の部分から始めましょう。
そこで、Glimmer UI を使用して空間インジケーターと情報ボックスを作成します。
まず、使用するコンポーネントを特定しましょう。
左側にはアイコン マーカーが表示され、右側には場所を説明するカードが表示されます。
Android Studio に移動すると、他の Compose コンポーネントと同様にこのペインをプレビューでき、変更をリアルタイムで確認できます。
前述のように、さまざまな種類の背景環境で UI をプレビューし、暗い環境や明るい環境など、複数の種類のシナリオで UI のコントラストが十分であることを確認できます。
キャンバスが設定されたので、行を作成し、その中にアイコン マーカーを配置して、スペースを空けてサンプル テキストを含むカードを配置します。
これで完了です。
2D プレビューに問題がなければ、現実世界にマークを付けることができます。これを行うには、UI を空間化します。
つまり、2D ではなく 3D でレイアウトします。
空間 UI は、ユーザーの目の前にある 3D 空間であるサブスペースに含まれます。
Subspace {
SpatialPanel {
VideoPlayer()
}
}デフォルトでは、サブスペースはユーザーから快適な距離にコンテンツを配置します。
Geospatial API の使用方法を説明する前に、まず近くに何かを配置してみましょう。
先ほど説明した Subspace コンポーザブルには、追従ターゲットを指定できるオプションのパラメータがあります。
val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}現実世界に何かをアタッチするには、まず、Jetpack XR 向け ARCore から取得したアンカーを作成します。
この例では、QR コード認識機能を使用しています。
これにより、QR コードを読み取って追跡できるようになります。コンテンツは空間内でその QR コードを追跡し、先ほど見た UI をアタッチします。
違いますね。
ここまで、デジタル コンテンツを近くの現実世界に配置する方法を説明しました。
ポールさんの例に戻りましょう。
私はオランダ出身です。
マウンテンビューにはあまり詳しくない。
このイベントの後、楽しいコーヒーショップを見つけるのに役立つアプリを作成するお手伝いをしたいと思います。
そこで、ARCore for Jetpack XR に搭載された Geospatial API を使用して、ユーザーが近くのレストランを見つけられるようにする方法を見てみましょう。
そのため、当初はスマートフォン向けにリリースされた Geospatial API が、Android XR でも利用できるようになりました。
Google ストリートビューのデータと GPS 測定値を組み合わせて、サポートされているエリアでデバイスの位置を特定します。
そのため、スマートグラスで Geospatial API を使用すると、ユーザーの位置と進行方向を正確に特定し、高精度のターンバイターン方式のナビゲーション ユースケースを実現できます。
また、有線 XR グラスなどの没入型デバイスでは、Geospatial API を使用して、現実世界に正確に配置されたエクスペリエンスを構築できます。
たとえば、建物に空間コンテンツを固定したり、特別な市内ツアーを作成したりできます。
それでは、次の 3 つのステップで Geospatial API を使い始めましょう。
1 つ目は、Google Cloud Platform プロジェクトを作成し、アプリで認証を構成することです。
次に、アプリで Geospatial API を構成して有効にしたら、ユーザーに位置情報の取得を許可するよう求めます。このとき、位置情報の精度は「正確」に設定します。
その後、Geospatial API を使用するようにセッションを構成すれば、準備は完了です。
Geospatial API に関するデベロッパー向けドキュメントの詳細は、新しくなった Android XR デベロッパー ページで確認できます。
まず、ユーザーの位置情報を使用して、Google Maps Platform の Places API にリクエストを送信し、ユーザーの近くにあるおすすめのカフェを検索します。
まず、お客様はどこにいますか?
Geospatial API に、ユーザーの位置情報を継続的に更新するようリクエストします。
produceState<AppStatus>(Initializing) {
val geospatial = Geospatial.getInstance(session)
val wait = geospatial.state.onEach {
value = WaitingForGeospatial(it.geospatialTrackingState)
}.first {
it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
}
val accuracyWait = geospatial.state.onEach {
value = WaitingForGeospatialAccuracy(it)
}.first { it.horizontalAccuracy < horizontalAccuracyRequirement }
value = WaitingForPlacesResults
val result = client.findNearby(context, accWait.geospatialPose)
value = Done(result)
}そこで、XR セッションを介して Geospatial オブジェクトにアクセスします。
その後、Geospatial が必要なセンサーを初期化し、Cloud API に接続して起動するまで待ちます。
そして、一定の精度が得られるまで待ちます。
そのため、GPS 測定値が取得されたときや、ビジュアル ポジショニング システムがユーザーの現在地で一致する場所を見つけたときに、精度レベルが向上することがあります。
また、高精度レベルを待つことは、位置情報に基づくエクスペリエンスが適切な場所に固定され、結果がユーザーの近くのエリアから取得されるようにするために重要です。
これで、Geospatial API から取得した位置情報を使用して、Places API で検索を実行する準備が整いました。
val bounds = CircularBounds.newInstance(
LatLng(pose.latitude, pose.longitude),
100.0, // radius, in meters
)
val placeFields = listOf(
Place.Field.ID, Place.Field.DISPLAY_NAME,
Place.Field.RATING, Place.Field.LOCATION,
)
return placesClient.searchNearby(
SearchNearbyRequest.builder(bounds, placeFields)
.setIncludedTypes(listOf(PlaceTypes.CAFE))
.setMaxResultCount(10)
.build()
).await()そこで、Places API で SearchNearbyRequest を使用して、先ほど取得したユーザーの位置周辺の場所をクエリし、100 m 以内の結果を見つけます。
場所の ID、表示名、評価、そしてもちろん位置情報を取得します。
最後に、場所タイプ フィルタを含む指定されたパラメータを使用して検索を実行します。
データについては以上です。UI に戻りましょう。
そこで、先ほどのマーカーと情報ボックスを配置する方法を考える必要があります。そのために、地理空間アンカーを使用します。
コードに戻り、XR セッションを使用して Geospatial API にアクセスし、先ほどの検索結果から取得した特定の緯度と経度を使用して地理空間アンカーを作成します。
val anchorResult by produceState<AnchorResult?>(null) {
val geospatial = Geospatial.getInstance(session)
value = geospatial.createAnchorOnSurface(
place.location.latitude, place.location.longitude,
surface = GeospatialSurface.TERRAIN,
altitudeAboveSurface = 3.0,
eastUpSouthQuaternion = Quaternion()
)
}この例では、指定した座標の地形から 3 m の高度を指定していますが、サポートされている屋上にアタッチすることもできます。その場合、高度の値は、その場所にある建物の屋上を基準に決定されます。
そのため、アンカーの回転値を指定するクォータニオンを使用してアンカーを回転させることもできます。
ただし、この例では、UI を回転させてユーザーに向けるため、実際にはデフォルトを使用します。
そして、先ほどの Glimmer UI をこのアンカーにアタッチします。これで、空間アンカー エクスペリエンスの構成要素が揃いました。
when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}そのため、Geospatial API は優れたウェイファインディング エクスペリエンスを実現できると考えています。また、Glimmer を使用して設計することで、シースルー ディスプレイ向けに特別に作られた、一目でわかるユーザー エクスペリエンスを作成できます。
Geospatial API の詳細については、最近更新された developer.android.com/xr をご覧ください。
それでは、Yasmine に引き継ぎます。
このセッションでは、Android XR のいくつかの機能(Jetpack XR 向け ARCore と Jetpack Compose Glimmer)について説明しました。
Android XR が有線 XR グラスにもたらす新機能に期待しています。
すでに多くのデベロッパーが Android XR 向けの開発に取り組んでいます。皆様のクリエイティブな作品が Google Play ストアに登場することを楽しみにしています。
まずは、デベロッパー向けドキュメント、XR の試験運用版、Codelab をご覧ください。
XR に関するセッション、デモ、ワークショップを多数ご用意しています。
ぜひご参加いただき、没入型エクスペリエンスと拡張エクスペリエンスにわたる Android XR 向けの開発について学んでください。
Android XR チームを代表して、皆様のご参加に心より感謝申し上げます。Android Developer Summit の残りの時間もぜひお楽しみください。
ありがとうございました。
android skills add display-glasses-with-jetpack-compose-glimmerval brandColor = Color(0xFFFFAABB)
val focusedColor = ButtonDefaults.focusedColor(brandColor)
Column(…) {
LeadingImageCard(
focusable = false,
…
) {
…
}
ButtonGroup(...) {
Button(
onClick = {},
leadingIcon = { … },
focusedColor = focusedColor,
) { Text("Add to cart") }
/* Rest of the buttons */
}
}val pagerState =
rememberGlimmerPagerState(pageCount = { 10 })
GlimmerHorizontalPager(pagerState) { page ->
/* Card Content */
}ButtonGroup {
Button(onClick = {}) { Text("Button 1") }
Button(onClick = {}) { Text("Button 2") }
Button(onClick = {}) { Text("Button 3") }
Button(onClick = {}) { Text("Button 4") }
Button(onClick = {}) { Text("Button 5") }
}var buttonChecked by remember { mutableStateOf(false) }
ToggleButton(
checked = buttonChecked,
leadingIcon = { Icon(FavoriteIcon) },
onCheckedChange = { buttonChecked = it }
) {
Text(if (buttonChecked) "On" else "Off")
}Subspace {
SpatialPanel {
VideoPlayer()
}
}val state by
qrCode.state.collectAsState()
Subspace(FollowTarget.anchor(anchor)) {
SpatialPanel {
Row {
IndicatorDot()
Spacer()
QrCodeCard(state)
}
}
}produceState<AppStatus>(Initializing) {
val geospatial = Geospatial.getInstance(session)
val wait = geospatial.state.onEach {
value = WaitingForGeospatial(it.geospatialTrackingState)
}.first {
it.geospatialTrackingState == Geospatial.GeospatialTrackingState.RUNNING
}
val accuracyWait = geospatial.state.onEach {
value = WaitingForGeospatialAccuracy(it)
}.first { it.horizontalAccuracy < horizontalAccuracyRequirement }
value = WaitingForPlacesResults
val result = client.findNearby(context, accWait.geospatialPose)
value = Done(result)
}val bounds = CircularBounds.newInstance(
LatLng(pose.latitude, pose.longitude),
100.0, // radius, in meters
)
val placeFields = listOf(
Place.Field.ID, Place.Field.DISPLAY_NAME,
Place.Field.RATING, Place.Field.LOCATION,
)
return placesClient.searchNearby(
SearchNearbyRequest.builder(bounds, placeFields)
.setIncludedTypes(listOf(PlaceTypes.CAFE))
.setMaxResultCount(10)
.build()
).await()val anchorResult by produceState<AnchorResult?>(null) {
val geospatial = Geospatial.getInstance(session)
value = geospatial.createAnchorOnSurface(
place.location.latitude, place.location.longitude,
surface = GeospatialSurface.TERRAIN,
altitudeAboveSurface = 3.0,
eastUpSouthQuaternion = Quaternion()
)
}when (anchorResult) {
is AnchorCreateSuccess ->
Subspace(FollowTarget.anchor(anchorResult.anchor)) {
SpatialPanel(SubspaceModifier.rotateToLookAtUser()) {
PlaceIndicator(place)
}
}
else ->
Log.e("Demo", "Failed to create anchor $anchorResult")
}