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.3
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
WebViewyou 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();
}