piano_composer 1.0.2 copy "piano_composer: ^1.0.2" to clipboard
piano_composer: ^1.0.2 copied to clipboard

Piano Composer Flutter SDK.

Piano Composer SDK for Flutter #

Piano Composer Flutter SDK delivers personalized experiences, paywalls, and A/B tests from Piano's experience management platform, and reports user interactions back for attribution.

Installation #

Add the dependency to your pubspec.yaml:

dependencies:
  piano_composer: ^1.0.2

Getting Started #

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

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

final piano = await Piano.init(
  endpoint: PianoEndpoint.production,
  aid: '<AID>',
);

Get a client via piano.composer(). Each call returns a new PianoComposerClient, so retain the reference when you need to share configuration or read back state (browserId, pageViewId) after a request:

final composer = piano.composer();

final request = PianoComposerRequest(
  url: 'https://piano.io/article',
  title: 'Article Title',
  tags: ['technology', 'news'],
);

final response = await composer.execute(request);

Configuration #

Configuration setters are chainable and return the same client:

final composer = piano.composer()
  ..userToken('<access_token>')        // token for personalized experiences
  ..gaClientId('<ga_client_id>')       // link experiences to Google Analytics
  ..browserIdProvider(() => '<browser_id>'); // supply a custom browser id

Experience Interceptors #

Extend PianoComposerInterceptor to hook into the request lifecycle. Both methods are optional:

class CustomInterceptor extends PianoComposerInterceptor {
  @override
  void beforeExecute(PianoComposerRequest request) {
    // Inspect the request before it is sent.
  }

  @override
  void afterExecute(
    PianoComposerRequest request,
    PianoComposerResponse response,
  ) {
    // Handle the response.
  }
}

final composer = piano.composer()
  ..addExperienceInterceptor(CustomInterceptor());

Building a Request #

PianoComposerRequest carries the content metadata and targeting context. All fields are optional:

Field Type Description
url String? Page URL
referer String? Referring URL
title String? Content title
description String? Content description
tags List<String> Content tags
keywords List<String> Content keywords
zone String? Targeting zone
contentId String? Content identifier
contentType String? Content type
contentCreated String? ISO 8601 timestamp (use PianoComposerRequest.formatDate)
contentAuthor String? Content author
contentSection String? Content section
contentIsNative bool? Whether the content is native
customVariables Map<String, List<String>?> Custom targeting variables
isDebug bool Enable verbose API responses
final request = PianoComposerRequest(
  url: 'https://piano.io/article/123',
  title: 'Breaking News Article',
  description: 'Latest updates on technology',
  contentId: '123',
  contentType: 'article',
  contentCreated: PianoComposerRequest.formatDate(DateTime.now()),
  contentAuthor: 'John Doe',
  contentSection: 'Technology',
  zone: 'homepage',
  customVariables: {
    'user_type': ['premium'],
    'region': ['us-west'],
  },
);

final response = await composer.execute(request);

Handling Events #

execute returns a PianoComposerResponse whose result.events contains the experience events. Handle them with typed listeners:

final response = await composer.execute(
  request,
  listeners: [
    PianoComposerEventTypeListener<PianoComposerShowTemplate>(
      onEvent: (event) => print('Show template: ${event.eventData.url}'),
    ),
    PianoComposerEventTypeListener<PianoComposerMeter>(
      onEvent: (event) => print('Meter event received'),
    ),
  ],
);

Or receive every event with a single callback:

final response = await composer.execute(
  request,
  onEvents: (events) {
    for (final event in events) {
      print('Event: ${event.eventData.runtimeType}');
    }
  },
);

Event types include PianoComposerExperienceExecute, PianoComposerMeter, PianoComposerShowTemplate, PianoComposerShowForm, PianoComposerShowLogin, PianoComposerShowRecommendations, PianoComposerUserSegment, PianoComposerSetResponseVariable, and PianoComposerNonSite.

Tracking #

Report user interactions back to Piano through composer.tracking so they are attributed to the originating experience:

final tracking = piano.composer().tracking;

// User dismissed a template or experience.
await tracking.trackCloseEvent('<tracking_id>');

// A recommendations widget was shown, then clicked.
await tracking.trackRecommendationsDisplay('<tracking_id>');
await tracking.trackRecommendationsClick('<tracking_id>', '<url>');

// A custom form was shown, then submitted.
await tracking.trackCustomFormImpression('<form_name>', '<tracking_id>');
await tracking.trackCustomFormSubmission('<form_name>', '<tracking_id>');

Reading Response Data #

// Retain the same instance to read back state after execute().
final composer = piano.composer();
final response = await composer.execute(request);

// Cookies
final tbCookie = response.tbCookie; // tracking cookie
final xbCookie = response.xbCookie; // experience cookie
final taCookie = response.taCookie; // analytics cookie

// Identifiers
final browserId = composer.browserId;
final pageViewId = composer.pageViewId;
final userId = response.userId;

// Edge cookies (if enabled)
final edgeCookies = composer.edgeCookies;

Clearing Stored Data #

Clear all locally stored data and cookies:

piano.composer().clearStoredData();

Error Handling #

execute throws a PianoException when the API returns errors:

try {
  final response = await piano.composer().execute(request);
} on PianoException catch (e) {
  print('Piano error: ${e.message}');
} catch (e) {
  print('Unexpected error: $e');
}