From 4d4ed03c9555329d42e000212e4118cef6f3364e Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Mon, 4 Sep 2023 17:33:26 +0100 Subject: [PATCH] Add some initial Data Layer docs --- docs/datalayer.md | 140 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 4 deletions(-) diff --git a/docs/datalayer.md b/docs/datalayer.md index d118b9539a..e10f2044d7 100644 --- a/docs/datalayer.md +++ b/docs/datalayer.md @@ -8,11 +8,143 @@ DataStore documentation https://developer.android.com/topic/libraries/architectu Direct DataLayer sample code https://github.com/android/wear-os-samples -TODO +## DataLayer approach. -- Use CapabilityClient instead of NodeClient -- Consider switching local/remote to owner/reader -- Support WearableListenerService to handle changes to data. +The Horologist DataLayer libraries, provide common abstractions on top of the Wearable DataLayer. +These build upon a common assumption of Google Protobug and gRPC, which allows sharing data +definitions throughout your Wear and Mobile apps. + +See https://github.com/google/horologist/blob/main/auth/sample/shared/build.gradle.kts +for an example of configuring a build to use proto definitions. + +```protobuf +syntax = "proto3"; + +message CounterValue { + int64 value = 1; + .google.protobuf.Timestamp updated = 2; +} + +message CounterDelta { + int64 delta = 1; +} + +service CounterService { + rpc getCounter(.google.protobuf.Empty) returns (CounterValue); + rpc increment(CounterDelta) returns (CounterValue); +} +``` + +## Registering Serializers. + +The WearDataLayerRegistry is an application singleton to register the Serializers. + +```kotlin +object CounterValueSerializer : Serializer { + override val defaultValue: CounterValue + get() = CounterValue.getDefaultInstance() + + override suspend fun readFrom(input: InputStream): CounterValue = + try { + CounterValue.parseFrom(input) + } catch (exception: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", exception) + } + + override suspend fun writeTo(t: CounterValue, output: OutputStream) { + t.writeTo(output) + } +} + +val registry = WearDataLayerRegistry.fromContext( + application = sampleApplication, + coroutineScope = coroutineScope, +).apply { + registerSerializer(CounterValueSerializer) +} +``` + +## 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 +with a single owner and multiple readers. + +See https://developer.android.com/topic/libraries/architecture/datastore + +Publishing a DataStore. + +```kotlin + private val dataStore: DataStore by lazy { + registry.protoDataStore(lifecycleScope) + } +``` + +Reading a remote DataStore. + +```kotlin +val counterFlow = registry.protoFlow(TargetNodeId.PairedPhone) +``` + +## Using gRPC + +This library implements the gRPC transport over the Wearable MessageClient using the RPC request +feature. + +Implementing a service. + +```kotlin +class CounterService(val dataStore: DataStore) : + CounterServiceGrpcKt.CounterServiceCoroutineImplBase() { + override suspend fun getCounter(request: Empty): GrpcDemoProto.CounterValue { + return dataStore.data.first() + } + + override suspend fun increment(request: GrpcDemoProto.CounterDelta): GrpcDemoProto.CounterValue { + return dataStore.updateData { + it.copy { + this.value = this.value + request.delta + this.updated = System.currentTimeMillis().toProtoTimestamp() + } + } + } + } + +class WearCounterDataService : BaseGrpcDataService() { + + private val dataStore: DataStore by lazy { + registry.protoDataStore(lifecycleScope) + } + + override val registry: WearDataLayerRegistry by lazy { + WearDataLayerRegistry.fromContext( + application = applicationContext, + coroutineScope = lifecycleScope, + ).apply { + registerSerializer(CounterValueSerializer) + } + } + + override fun buildService(): CounterServiceGrpcKt.CounterServiceCoroutineImplBase { + return CounterService(dataStore) + } +} +``` + +Calling a remote service. + +```kotlin +val client = registry.grpcClient( + nodeId = TargetNodeId.PairedPhone, + coroutineScope = sampleApplication.servicesCoroutineScope, +) { + CounterServiceGrpcKt.CounterServiceCoroutineStub(it) +} + +// Call the increment method from the proto service definition +val newValue: CounterValue = + counterService.increment(counterDelta { delta = i.toLong() }) +``` ## Download