-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* beacon * check-path * coroutines + usage for check-path * expression * external-action * metrics * fix a bunch of formatting things * fix formatting problems * settimeout * async-node * update package paths * async node ios docs * address some comments --------- Co-authored-by: brocollie08 <[email protected]> Co-authored-by: nancywu1 <[email protected]>
- Loading branch information
1 parent
87d1ac3
commit e759140
Showing
14 changed files
with
426 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
--- | ||
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 tree 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 UI 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 asset node or an array of asset 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 UI asset | ||
}); | ||
|
||
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: | ||
|
||
```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> | ||
|
||
### CocoaPods | ||
|
||
Add the subspec to your `Podfile` | ||
|
||
```ruby | ||
pod 'PlayerUI/AsyncNodePlugin' | ||
``` | ||
|
||
### Swift Usage | ||
|
||
In integration code | ||
|
||
```swift | ||
var body: some View { | ||
SwiftUIPlayer( | ||
flow: flow, | ||
plugins: [ | ||
AsyncNodePlugin { node in | ||
// Determine what to return either using the singleNode or multiNode case | ||
// Then JSON can be provided using the concrete case, see below for using the encodable case | ||
return .singleNode(.concrete(jsContext?.evaluateScript(""" | ||
({"asset": {"id": "text", "type": "text", "value":"new node from the hook"}}) | ||
""") ?? JSValue())) | ||
|
||
// OR | ||
return .multiNode([ | ||
.concrete(jsContext?.evaluateScript(""" | ||
({"asset": {"id": "text", "type": "text", "value":"1st value in the multinode"}}) | ||
""") ?? JSValue()), | ||
.concrete(jsContext?.evaluateScript(""" | ||
({"asset": {"id": "async-node-2", "async": "true" }}) | ||
""") ?? JSValue()) | ||
]) | ||
|
||
} | ||
], | ||
result: $resultBinding | ||
) | ||
} | ||
``` | ||
|
||
The plugin also provides a default asset placeholder struct that is encodable, instead of passing in the JSON string users can use | ||
`AssetPlaceholderNode` which includes an `asset` key that takes any user defined Encodable struct as the value. Assuming the following encodable struct is defined: | ||
|
||
```swift | ||
struct PlaceholderNode: Codable, Equatable, AssetData { | ||
public var id: String | ||
public var type: String | ||
var value: String? | ||
|
||
public init(id: String, type: String, value: String? = nil) { | ||
self.id = id | ||
self.type = type | ||
self.value = value | ||
} | ||
} | ||
``` | ||
|
||
Instead of using the JSON string above, the following can be used: | ||
|
||
```swift | ||
return .singleNode(.encodable(PlaceholderNode(id: "text", type: "text", value: "new node from the hook"))) | ||
|
||
// OR | ||
|
||
return .multiNode([ | ||
ReplacementNode.encodable(PlaceholderNode(id: "text", type: "text", value: "1st value in the multinode")), | ||
ReplacementNode.encodable(AsyncNode(id: "id"))]) | ||
``` | ||
|
||
Note: the AsyncNode struct is already defined in the plugin with the `async` property defaulted to true so only `id` needs to be passed in | ||
|
||
As a convenience to the user, the AsyncNodePlugin just takes a callback which has the content to be returned, this is provided to the plugin which calls the the `onAsyncNode` hook tap method | ||
|
||
</ios> | ||
<android> | ||
|
||
In build.gradle | ||
```kotlin | ||
implementation "com.intuit.player.plugins:async-node:$PLAYER_VERSION" | ||
``` | ||
|
||
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 UI asset to be parsed | ||
// e.g. | ||
// listOf( | ||
// mapOf( | ||
// "asset" to mapOf( | ||
// "id" to "asset-1", | ||
// "type" to "text", | ||
// "value" to "new asset!" | ||
// ) | ||
// ) | ||
// ) | ||
} | ||
|
||
AndroidPlayer(asyncNodePlugin) | ||
``` | ||
</android> | ||
</PlatformTabs> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.