From e7a3c57566f17f1031c15aa4a3d172fdf810ab92 Mon Sep 17 00:00:00 2001 From: Chiara Chiappini Date: Wed, 28 Feb 2024 12:56:22 +0000 Subject: [PATCH] Add docs for phone ui prompts and some improvements --- .../datalayer/watch/WearDataLayerAppHelper.kt | 3 +- docs/datalayer-helpers-guide.md | 216 +++++++++--------- docs/datalayer-phone-ui.md | 23 ++ docs/datalayer-sample.md | 20 ++ docs/datalayer.md | 20 +- mkdocs.yml | 2 + 6 files changed, 168 insertions(+), 116 deletions(-) create mode 100644 docs/datalayer-phone-ui.md create mode 100644 docs/datalayer-sample.md diff --git a/datalayer/watch/src/main/java/com/google/android/horologist/datalayer/watch/WearDataLayerAppHelper.kt b/datalayer/watch/src/main/java/com/google/android/horologist/datalayer/watch/WearDataLayerAppHelper.kt index 5516bf7580..a7200a05c0 100644 --- a/datalayer/watch/src/main/java/com/google/android/horologist/datalayer/watch/WearDataLayerAppHelper.kt +++ b/datalayer/watch/src/main/java/com/google/android/horologist/datalayer/watch/WearDataLayerAppHelper.kt @@ -189,7 +189,8 @@ public class WearDataLayerAppHelper( /** * Marks that the necessary setup steps have been completed in the app such that it is ready for - * use. Typically this should be called when any pairing/login has been completed. + * use. Typically this should be called when any pairing/login has been completed. If used for + * prompting login, it should also be called during startup if login happened before */ public suspend fun markSetupComplete() { surfacesInfoDataStore.updateData { info -> diff --git a/docs/datalayer-helpers-guide.md b/docs/datalayer-helpers-guide.md index 4ff17bd9a8..26b55c9459 100644 --- a/docs/datalayer-helpers-guide.md +++ b/docs/datalayer-helpers-guide.md @@ -1,6 +1,7 @@ # DataLayer helpers libraries -These libraries provides an easy means to detect and install your app across both watch and phone. +The DataLayer helpers libraries help tracking the connection between phone and watch and +start flows from the such as installing the app However, they are not intended to cover complex use cases, or complex interactions between watch and phone. @@ -25,57 +26,64 @@ phone. For your watch and phone projects respectively. -1. Add the capability +2. Add the capabilities Add a `wear.xml` file in the `res/values` folder with the following content: - ```xml + ``` + data_layer_app_helper_device_watch + + horologist_watch ``` - + and ```xml - + + data_layer_app_helper_device_phone - + + horologist_phone + ``` - On your watch and phone projects respectively. + on your wear and phone projects respectively. For more details, see [Specify capability names for detecting your apps](https://developer.android.com/training/wearables/apps/standalone-apps#capability-names). -1. Initialize the client, including passing a `WearDataLayerRegistry`. +3. Initialize the client including passing a `WearDataLayerRegistry`. ```kotlin + // on your watch project val appHelper = WearDataLayerAppHelper(context, wearDataLayerRegistry, scope) - // or + // on your phone project val appHelper = PhoneDataLayerAppHelper(context, wearDataLayerRegistry) ``` + -## Typical use cases: - -1. **Connection and installation status** +## Connection and installation status - This is something that your app may do from time to time, or on start up. +The `DataLayerAppHelper.connectedNodes()` returns information about connected devices. You could +invoke this method at startup or while the app is running. - ```kotlin +```kotlin val connectedNodes = appHelper.connectedNodes() - ``` +``` - The resulting list might will contain entries such as: +The resulting list might will contain entries such as: - ``` +``` AppHelperNodeStatus( id=7cd1c38a, displayName=Google Pixel Watch, @@ -106,131 +114,131 @@ phone. usage_status_value: 1 } ) - ``` +``` -1. **Responding to availability change** +## Responding to availability change - Once you've established the app on both devices, you may wish to respond to when the partner - device connects or disconnects. For example, you may only want to show a "launch workout" button - on the phone when the watch is connected. +Once you've established the app on both devices, you may wish to respond to when the partner +device connects or disconnects. For example, you may only want to show a "launch workout" button +on the phone when the watch is connected. - ```kotlin - val nodes by appHelper.connectedAndInstalledNodes - .collectAsStateWithLifecycle() - ``` +```kotlin +val nodes by appHelper.connectedAndInstalledNodes + .collectAsStateWithLifecycle() +``` -1. **Installing the app on the other device** +## Installing the app on the other device - Where the app isn't installed on the other device - be that phone or watch - then the library offers - a one step option to launch installation: +Where the app isn't installed on the other device - be that phone or watch - then the library offers +a one step option to launch installation: - ```kotlin - appHelper.installOnNode(node.id) - ``` +```kotlin +appHelper.installOnNode(node.id) +``` -1. **Launching the app on the other device** +## Launching the app on the other device - If the app is installed on the other device, you can launch it remotely: +If the app is installed on the other device, you can launch it remotely: - ```kotlin - val result = appHelper.startRemoteOwnApp(node.id) - ``` +```kotlin +val result = appHelper.startRemoteOwnApp(node.id) +``` -1. **Launching a specific activity on the other device** +## Launching a specific activity on the other device - In addition to launching your own app, you may wish to launch a different - activity as part of the user journey: +In addition to launching your own app, you may wish to launch a different +activity as part of the user journey: - ```kotlin - val config = activityConfig { - classFullName = "com.example.myapp.MyActivity" - } - appHelper.startRemoteActivity(node.id, config) - ``` +```kotlin +val config = activityConfig { + classFullName = "com.example.myapp.MyActivity" +} +appHelper.startRemoteActivity(node.id, config) +``` -1. **Launching the companion app** +## Launching the companion app - In some cases, it can be useful to launch the companion app, either from the watch or the phone. +In some cases, it can be useful to launch the companion app, either from the watch or the phone. - For example, if the connected device does not have your Tile installed, you may wish to offer the - user the option to navigate to the companion app to install it: +For example, if the connected device does not have your Tile installed, you may wish to offer the +user the option to navigate to the companion app to install it: - ```kotlin - if (node.surfacesInfo.tilesList.isEmpty() && askUserAttempts < MAX_ATTEMPTS) { - // Show guidance to the user and then launch companion - // to allow the to install the Tile. - val result = appHelper.startCompanion(node.id) - } - ``` +```kotlin +if (node.surfacesInfo.tilesList.isEmpty() && askUserAttempts < MAX_ATTEMPTS) { + // Show guidance to the user and then launch companion + // to allow the to install the Tile. + val result = appHelper.startCompanion(node.id) +} +``` -1. **Tracking Tile installation** (Wear-only) +## Tracking Tile installation (Wear-only) - To determine whether your Tile(s) are installed, add the following to your `TileService`: +To determine whether your Tile(s) are installed, add the following to your `TileService`: - In `onTileAddEvent`: +In `onTileAddEvent`: - ```kotlin - wearAppHelper.markTileAsInstalled("SummaryTile") - ``` +```kotlin +wearAppHelper.markTileAsInstalled("SummaryTile") +``` - In `onTileRemoveEvent`: +In `onTileRemoveEvent`: - ```kotlin - wearAppHelper.markTileAsRemoved("SummaryTile") - ``` +```kotlin +wearAppHelper.markTileAsRemoved("SummaryTile") +``` -1. **Tracking Complication installation** (Wear-only) +## Tracking Complication installation (Wear-only) - To determine whether your Complication(s) are in-use, add the following to your `ComplicationDataSourceService`: +To determine whether your Complication(s) are in-use, add the following to your `ComplicationDataSourceService`: - In `onComplicationActivated`: +In `onComplicationActivated`: - ```kotlin - wearAppHelper.markComplicationAsActivated("GoalsComplication") - ``` +```kotlin +wearAppHelper.markComplicationAsActivated("GoalsComplication") +``` - In `onComplicationDeactivated`: +In `onComplicationDeactivated`: - ```kotlin - wearAppHelper.markComplicationAsDeactivated("GoalsComplication") - ``` +```kotlin +wearAppHelper.markComplicationAsDeactivated("GoalsComplication") +``` -1. **Tracking the main activity has been launched at least once** (Wear-only) +## Tracking the main activity has been launched at least once (Wear-only) To mark that your main activity on the watch app has been launched once, use: - ```kotlin - wearAppHelper.markActivityLaunchedOnce() - ``` +```kotlin +wearAppHelper.markActivityLaunchedOnce() +``` - To check it on the phone side, use: +To check it on the phone side, use: - ```kotlin - val connectedNodes = appHelper.connectedNodes() - // after picking a node, check if value is USAGE_STATUS_LAUNCHED_ONCE - node.surfacesInfo.usageInfo.usageStatus - ``` +```kotlin +val connectedNodes = appHelper.connectedNodes() +// after picking a node, check if value is USAGE_STATUS_LAUNCHED_ONCE +node.surfacesInfo.usageInfo.usageStatus +``` -1. **Tracking the app has been set up** (Wear-only) +## Tracking the app has been set up (Wear-only) - To mark that the user has completed in the app the necessary setup steps such that it is ready - for use, use the following: +To mark that the user has completed in the app the necessary setup steps such that it is ready +for use, use the following: - ```kotlin - wearAppHelper.markSetupComplete() - ``` +```kotlin +wearAppHelper.markSetupComplete() +``` - And when the app is no longer considered in a fully setup state, use the following: +And when the app is no longer considered in a fully setup state, use the following: - ```kotlin - wearAppHelper.markSetupNoLongerComplete() - ``` +```kotlin +wearAppHelper.markSetupNoLongerComplete() +``` - To check it on the phone side, use: +To check it on the phone side, use: - ```kotlin - val connectedNodes = appHelper.connectedNodes() - // after picking a node, check if value is either USAGE_STATUS_LAUNCHED_ONCE - // or USAGE_STATUS_SETUP_COMPLETE - node.surfacesInfo.usageInfo.usageStatus - ``` +```kotlin +val connectedNodes = appHelper.connectedNodes() +// after picking a node, check if value is either USAGE_STATUS_LAUNCHED_ONCE +// or USAGE_STATUS_SETUP_COMPLETE +node.surfacesInfo.usageInfo.usageStatus +``` diff --git a/docs/datalayer-phone-ui.md b/docs/datalayer-phone-ui.md new file mode 100644 index 0000000000..63cb85c558 --- /dev/null +++ b/docs/datalayer-phone-ui.md @@ -0,0 +1,23 @@ +# DataLayer phone UI + +The DataLayer phone UI library provides an implementation for phone prompts UI that can be used to +bring more users to the Wear app. + +## Install app +Use the `InstallAppPrompt` to build the UI of a prompt to ask the user to install the app on their watch. + +The `shouldDisplayPrompt` method allows to check if the prompt should be displayed, based on the following conditions: + +- there is a watch connected + +- the app is not installed + +The `shouldDisplayPrompt` method is relying on the capability defined in the wear.xml of the Wear app, if the Wear app is installed +but hasn't been updated with the capability definition, this method may still return a `AppHelperNodeStatus` +indicating that the Wear app is not installed. + +## ReEngage prompt +Use the `ReEngagePrompt` to build the UI of a prompt to ask the user to install the app on their watch. + +## SignIn prompt +Use the `SignInPrompt` to build the UI of a prompt to ask the user to finish the sign in on the watch. diff --git a/docs/datalayer-sample.md b/docs/datalayer-sample.md new file mode 100644 index 0000000000..ac18ebe2a6 --- /dev/null +++ b/docs/datalayer-sample.md @@ -0,0 +1,20 @@ +# DataLayer sample app + +The goal of this sample is to show how to use the DataLayer helpers both on phone and watch. +In order to be able to demo all the features, you need to install the sample both on the watch and +on the phone + +## DataLayer phone sample app + +### In-app prompts +This sample shows how to build in app prompts by using the +[Phone UI library](https://github.com/google/horologist/tree/main/datalayer/phone-ui) and the +[PhoneDataLayerAppHelper](https://github.com/google/horologist/blob/main/datalayer/phone/src/main/java/com/google/android/horologist/datalayer/phone/PhoneDataLayerAppHelper.kt). + + + + + + + + diff --git a/docs/datalayer.md b/docs/datalayer.md index b290b8a066..b7c2288de9 100644 --- a/docs/datalayer.md +++ b/docs/datalayer.md @@ -1,13 +1,9 @@ -# DataLayer library +# Horologist DataLayer library. -DataStore documentation https://developer.android.com/topic/libraries/architecture/datastore - -Direct DataLayer sample code https://github.com/android/wear-os-samples - -## DataLayer approach. - -The Horologist DataLayer libraries, provide common abstractions on top of the Wearable DataLayer. -These are built upon a common assumption of Google Protobuf and gRPC, which allows sharing data +The Horologist DataLayer library, provide common abstractions on top of the +[Wearable DataLayer](https://developer.android.com/training/wearables/data/data-layer). +These are built using [Google Protobuf](https://protobuf.dev/) and +[gRPC](https://grpc.io/docs/what-is-grpc/introduction/), which allows sharing data definitions throughout your Wear and Mobile apps. See this @@ -34,7 +30,8 @@ service CounterService { ## Registering Serializers. -The WearDataLayerRegistry is an application singleton to register the Serializers. +The [WearDataLayerRegistry](https://google.github.io/horologist/api/datalayer/core/com.google.android.horologist.data/-wear-data-layer-registry/index.html) +is an application singleton to register the Serializers. ```kotlin object CounterValueSerializer : Serializer { @@ -64,7 +61,8 @@ val registry = WearDataLayerRegistry.fromContext( ## Use Androidx DataStore This library provides a new implementation of Androidx DataStore, in addition to the local -Proto and Preferences implementations. The implementation uses the Wearable DataClient +Proto and Preferences implementations. The implementation uses the +[WearableDataClient](https://developers.google.com/android/reference/com/google/android/gms/wearable/DataClient) with a single owner and multiple readers. See [DataStore](https://developer.android.com/topic/libraries/architecture/datastore). diff --git a/mkdocs.yml b/mkdocs.yml index 8cb4f555f3..e5b0aa6267 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,8 @@ nav: - 'Data Layer': - 'Guide': datalayer.md - 'App Helpers': datalayer-helpers-guide.md + - 'Phone UI': datalayer-phone-ui.md + - 'Sample': datalayer-sample.md - 'Media': - 'Overview': media-toolkit.md - 'Simple app guide': simple-media-app-guide.md