Piano Templates SDK for Flutter

Display Piano templates, forms, and recommendations returned by Piano Composer. Each type is shown in a WebView, either as a modal or inline.

Installation

Add the dependency to your pubspec.yaml:

dependencies:
  piano_templates: ^1.0.4

Getting Started

piano_templates builds on piano_common and piano_composer, so you need a Piano instance first:

import 'package:piano_common/piano_common.dart';
import 'package:piano_templates/piano_templates.dart';

final piano = await Piano.init(
  endpoint: PianoEndpoint.production,
  aid: '<AID>',
  language: 'en_US', // optional, defaults to 'en_US'
);

The language code you pass to Piano.init is forwarded to Piano as the lang URL parameter for every template and form shown by this package, so their content is localized accordingly.

Get a client via piano.templates():

final templates = piano.templates();

It creates three controllers, one for each kind of Composer show event:

Controller Type Handles event
templates.template() PianoShowTemplateController PianoComposerShowTemplate
templates.form() PianoShowFormController PianoComposerShowForm
templates.recommendations() PianoShowRecommendationsController PianoComposerShowRecommendations

The factory methods take no arguments. All per-show options are passed to show via a typed parameters object:

Future<R?> show(
  PianoComposerEvent<T> event,
  BuildContext context, [
  P? params,
]);

The params you pass are retained on controller.params, so callbacks (such as JS-bridge listeners or the form's signIn) keep working after show returns. A later show call that omits params reuses the last ones.

Templates

Create a controller, then call show with the event and a BuildContext:

final controller = templates.template();

// When a PianoComposerShowTemplate event arrives:
await controller.show(event, context);

Listening to template events

A template can send events over the JavaScript bridge (for example, when the user logs in or closes it). Pass a PianoTemplateListener via PianoShowTemplateParams to handle them:

final controller = templates.template();

await controller.show(
  event,
  context,
  PianoShowTemplateParams(
    listener: PianoTemplateListener(
      onClose: (data) => print('Closed'),
      onCloseAndRefresh: (data) => print('Closed, refresh the page'),
      onRegister: (data) => print('User registered'),
      onLogin: (data) => print('User logged in'),
      onLogout: (data) => print('User logged out'),
      onCustomEvent: (data) => print('Custom event: $data'),
      onTerminateSession: (session) {
        print('Terminate session: ${session.token}');
      },
    ),
  ),
);

onTerminateSession receives a PianoTerminateSession with token, sessionCount, and sessionLimit.

Reloading with a new token

show returns a PianoShowTemplateResult you can use to reload the template with a new user token, without reloading the whole page:

final result = await controller.show(event, context);
result?.reloadWithToken('<user_token>');

Forms

Show a Piano ID form. Pass a PianoShowFormParams with callbacks to react to its lifecycle:

final controller = templates.form();

await controller.show(
  event,
  context,
  PianoShowFormParams(
    accessToken: '<access_token>',
    onClose: () => print('Form closed'),
    signIn: (context) async {
      // Called when the form rejects the current token and needs a fresh one.
      // Return the new token, or null if the user cancelled.
      return await mySignIn(context);
    },
  ),
);

Recommendations

Show a Cxense recommendations widget via PianoShowRecommendationsParams:

final controller = templates.recommendations();

await controller.show(
  event,
  context,
  PianoShowRecommendationsParams(
    renderTemplateUrl: 'auto', // optional, defaults to 'auto'
    userId: '<user_id>',       // optional
  ),
);

Display Modes

show respects the display mode set on the event:

  • Modal — opens in a modal popup with an optional close button.
  • Inline — loads into a WebView you place in your own layout.

For inline mode, register a controller for the container selector before the event arrives, and use the returned WebViewController in your widget tree:

final controller = templates.template();

// Register the container selector ahead of time, so the WebViewController
// exists before the show event for it arrives.
late final _templateView = controller.inline('template');

// Use it in your layout.
Container(
  // ...
  child: WebViewWidget(controller: _templateView),
),

// Later, when the event arrives:
await controller.show(event, context);

The string passed to inline must exactly match the event's event.eventData.containerSelector — that's how show looks up which WebViewController to load the template into. If no controller was registered for that selector, show returns null and nothing is loaded.

Call inline once per container selector you expect to receive events for; each call returns (or creates) a distinct WebViewController, so a single controller instance can drive several inline placements at once:

final headerView = controller.inline('#header-template');
final sidebarView = controller.inline('#sidebar-template');

When the owning widget is disposed, release the inline controllers:

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Libraries

piano_templates