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.
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:
{ "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
This means there must be a constant renewal of new
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
Add the plugin to Player:
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 ] })
The
react
version of the AsyncNodePlugin is identical to using the core plugin. Refer to core usage for handler configuration:import { ReactPlayer } from '@player-ui/react'; import { MetricsPlugin } from '@player-ui/async-node-plugin'; const asyncNodePlugin = new AsyncNodePlugin(); const player = new ReactPlayer({ plugins: [ asyncNodePlugin ] })
CocoaPods
Add the subspec to your
Podfile
pod 'PlayerUI/AsyncNodePlugin'
Swift Usage
In integration code
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: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:
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 inAs 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 methodIn build.gradle
implementation "com.intuit.player.plugins:async-node:$PLAYER_VERSION"
In integration code
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)