Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugins android clean up #290

Merged
merged 15 commits into from
Feb 2, 2024
122 changes: 122 additions & 0 deletions docs/site/pages/plugins/async-node.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
title: AsyncNode Plugin
platform: core,react,ios,android
---

# Async Node Plugin

The AsyncNode Plugin is used to enable streaming additional content into a flow that has already been loaded and rendered.
A common use case for this plugin is conversational UI, as the users input more dialogue, new content must be streamed into Player in order to keep the UI up to date.

The pillar that makes this possible is the concept of an `AsyncNode`. An `AsyncNode` is any node with the property `async: true`, it represents a placeholder node that will be replaced by a concrete node in the future.

In the example below, node with the id "some-async-node" will not be rendered on first render, but will be replaced with a concrete asset node at a later time:

```json
{
"id": "flow-1",
"views": [
{
"id": 'action',
"actions": [
{
"id": "some-async-node",
"async": true,
},
],
},
],
...
}
```

The `AsyncNodePlugin` exposes an `onAsyncNode` hook on all platforms. The `onAsyncNode` hook will be invoked with the current node when the plugin is available and an `AsyncNode` is detected during the resolve process. The node used to call the hook with could contain metadata according to content spec.

User should tap into the `onAsyncNode` hook to examine the node's metadata before making a decision on what to replace the async node with. The return could be a single node or an array of nodes.


### Continuous Streaming

In order to keep streaming in new content, there must be at least 1 or more `AsyncNode`s in the view tree at all times.
This means there must be a constant renewal of new `AsyncNode`s after the previous ones are resolved by the user.

## Usage

<PlatformTabs>
<core>

Add the plugin to Player:

```js
import { Player } from '@player-ui/player';
import { AsyncNodePlugin } from '@player-ui/async-node-plugin';

const asyncNodePlugin = new AsyncNodePlugin();

// Configuring async node behaviour
asyncNodePlugin.hooks.onAsyncNode.tap('handleAsync', async (node: Node.Node) => {
...
// Determine what to return to be parsed into a concrete node
});

const player = new Player({
plugins: [
asyncNodePlugin
]
})
```
</core>
<react>

The `react` version of the AsyncNodePlugin is identical to using the core plugin. Refer to core usage for handler configuration:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both core and react are the same, can we consolidate the tabs? Like just have react or just have core? This kinda goes back to the difference between headless and UI/platform plugins. Like the AsyncNodePlugin doesn't have a react specific version in source either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered about this as well, idk what the tab would be. core without react may not be clear to web consumers that they can use it, but react without core is incorrect cuz it's a core plugin


```js
import { ReactPlayer } from '@player-ui/react';
import { MetricsPlugin } from '@player-ui/async-node-plugin';

const asyncNodePlugin = new AsyncNodePlugin();

const player = new ReactPlayer({
plugins: [
asyncNodePlugin
]
})
```

</react>
<ios>

</ios>
<android>

In build.gradle
```kotlin
implementation "com.intuit.player.plugins:async-node:$JVM_PLAYER_VERSION"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just PLAYER_VERSION since divergent JVM player version was an internal concept

```

In integration code
```kotlin
import com.intuit.player.plugins.asyncnode.AsyncNodePlugin

val asyncNodePlugin = AsyncNodePlugin()

// Configuring async node behaviour
asyncNodePlugin.hooks.onAsyncNode.tap("handleAsync") { hookContext, node ->
...
// Determine what to return in the form of a list of maps representing nodes that can be parsed
// e.g.
// listOf(
// mapOf(
// "asset" to mapOf(
// "id" to "asset-1",
// "type" to "text",
// "value" to "new asset!"
// )
// )
// )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't put this in terms of nodes -- that means little to consumers. Really, what they're interested in is UI, so asset representation. This goes for the above docs too. I'd just put it in terms of content, which is really what you have here, but it's worth calling out.

Maybe there can be a sentence or two calling out that this'd work (?) for replacing non-asset nodes (like metadata or something), if that's applicable. Not sure there is a great use case for that though.

}

AndroidPlayer(asyncNodePlugin)
```
</android>
</PlatformTabs>
59 changes: 54 additions & 5 deletions docs/site/pages/plugins/beacon.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,24 +186,37 @@ For convenience, there are several extension methods for utilizing a pre-install

```kotlin
// registering handler
player.onBeacon { beacon: String ->
androidPlayer.onBeacon { beacon: String ->
// Process beacon and send to analytics platform
}

// fire beacon
player.beacon(
androidPlayer.beacon(
"clicked",
"button",
assetThatIsBeaconing,
someData,
)
```

The base `RenderableAsset` class provides an additional helper to make beaconing less verbose from an asset perspective.
The base `RenderableAsset` class provides an additional helper as well as extension to make beaconing less verbose from an asset perspective.

```kotlin
// within RenderableAsset implementation
beacon(BeaconAction.clicked, BeaconElement.button)
// helper in RenderableAsset
public fun beacon(
action: String,
element: String,
asset: Asset = this.asset,
data: Any? = null
): Unit

// extension on RenderableAsset
fun RenderableAsset.beacon(
action: BeaconAction,
element: BeaconElement,
asset: Asset = this.asset,
data: Any? = null
): Unit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just link to the source here to ensure it stays up-to-date?

```

</android>
Expand Down Expand Up @@ -288,4 +301,40 @@ struct ActionAssetView: View {
```

</ios>

<android>

Using the base `RenderableAsset`'s helper function in this example fires off a basic beacon with no adjustment to format as well as empty data upon button click

**Example**

```kotlin
class MyAsset(assetContext: AssetContext) :
DecodableAsset<>(assetContext, Data.serializer()) {

@Serializable
data class Data(
...
) : AssetData()

override fun createView(): View {
...
}

override fun View.hydrate() {
val binding = MyCustomViewBinding.bind(this)
binding.button.setOnClickListener {
[email protected](
action = BeaconAction.clicked,
element = BeaconElement.button,
asset = this@MyAsset,
data = null
)
}
...
}
}
```

</android>
</PlatformTabs>
37 changes: 35 additions & 2 deletions docs/site/pages/plugins/check-path.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Check Path Plugin
platform: react,core
platform: react,core,android
---

# Check Path Plugin
Expand Down Expand Up @@ -48,7 +48,7 @@ Install the plugin:
```bash
yarn add @player-ui/check-path-plugin-react
```

6474792156
Add it to Player:

```js
Expand Down Expand Up @@ -91,6 +91,23 @@ var body: some View {
```
</ios>

<android>
In build.gradle

```kotlin
implementation "com.intuit.player.plugins:check-path:$JVM_PLAYER_VERSION"
```

In Player constructor

```kotlin
import com.intuit.player.plugins.checkpath.CheckPathPlugin

val plugins = listOf(CheckPathPlugin())
AndroidPlayer(plugins)
```
</android>

</PlatformTabs>


Expand Down Expand Up @@ -201,6 +218,22 @@ struct MyAsset: View {
```
</ios>

<android>

The JVM CheckPathPlugin API contains partial functionality in comparison to the core plugin, ideally this should not be used, and all logic where pathing is concerned should be handled in transforms
If the situation arises and this plugin is absolutely necessary, the following API are available at the moment. This is subject to change in future releases:

```kotlin
// Plugin API
public fun getAsset(id: String): Asset?

// Player extensions
public val Player.checkPathPlugin: CheckPathPlugin?
public fun Player.getAsset(id: String): Asset?
```

</android>

</PlatformTabs>


112 changes: 112 additions & 0 deletions docs/site/pages/plugins/coroutines.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
title: Coroutines
platform: android
---

# Kotlin Coroutines

Kotlin coroutines is a popular Kotlin library to handle asynchronous programming and concurrency in the JVM. More information on Coroutines can be found [here](https://kotlinlang.org/docs/coroutines-overview.html)
This module contains several JVM exclusive plugins to aid various tooling needs.

## UpdatesPlugin

Plugin using a `ReceiveChannel` to provide an updated asset whenever the Player View is updated.

### Usage

<PlatformTabs>
sugarmanz marked this conversation as resolved.
Show resolved Hide resolved
<android>
In build.gradle

```kotlin
implementation "com.intuit.player.plugins:coroutines:$JVM_PLAYER_VERSION"
```

In Player constructor
```kotlin
import com.intuit.player.plugins.coroutines.UpdatesPlugin

val plugins = listOf(UpdatesPlugin())
AndroidPlayer(plugins)
```
</android>
</PlatformTabs>


### API

<PlatformTabs>
<android>
The API for this plugin revolves around the `ReceiveChannel` for asset updates.

```kotlin
// Plugin API

/** Public property that is used by the [UpdatesPlugin] to send [Asset] updates, can be used in a CoroutineContext to receive [Asset] updates. */
public val updates: ReceiveChannel&lt;Asset?&gt;

/** This blocks the current thread until a new update is received or the timeout specified has passed. */
public fun waitForUpdates(timeout: Long = 500): Asset?

// Experimental

/** This cleans out the [updates] channel to prepare it to receive new updates */
public fun flush(): Unit

// Player extensions
public val Player.updatesPlugin: UpdatesPlugin?
public fun Player.waitForUpdates(timeout: Long = 500): Asset?
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment around just linking to source - if we want separate API docs, we should just generate them


</android>
</PlatformTabs>

## FlowScopePlugin

Plugin that provides a CoroutineScope that can be used to perform various async operations with the context of the current flow.
All operations launched using the scope will be cancelled when player changes state.

### Usage

<PlatformTabs>
<android>
In build.gradle

```kotlin
implementation "com.intuit.player.plugins:coroutines:$JVM_PLAYER_VERSION"
```

In Player constructor
```kotlin
import com.intuit.player.plugins.coroutines.FlowScopePlugin

val plugins = listOf(FlowScopePlugin())
AndroidPlayer(plugins)
```
</android>
</PlatformTabs>


### API

<PlatformTabs>
<android>

```kotlin
// Plugin API

/** The CoroutineScope reflective of the current Player flow */
public var flowScope: CoroutineScope?
/** Build a child scope of the current flowScope to ensure that these scopes will be structured according to the current Flow */
public fun subScope(coroutineContext: CoroutineContext = Dispatchers.Default): CoroutineScope?

// Extension functions

/** Retrieves the current Player flow bound to the scope created by this plugin */
public val CoroutineScope.flow: Flow?
public val Player.flowScopePlugin: FlowScopePlugin?
public val Player.flowScope: CoroutineScope?
public fun Player.subScope(coroutineContext: CoroutineContext = EmptyCoroutineContext): CoroutineScope?
```
</android>
</PlatformTabs>
Loading