Links

Interaction SDK v2

How to build Experiences into your products using the new Monterosa / Interaction SDK v2
Monterosa / Interaction SDK v2 is available in early access for existing customers. Please contact sales if you would like to register your interest
If you want to move quickly and integrate our pre-existing Experiences into your existing app or site, we recommend starting with Embed Experiences using the SDK.
If you want to take more control of the Experience, for example by building your own native UI for a poll within your app, then the SDK offers you this, while leveraging the scalability, speed and content management capabilities of the Monterosa / Interaction Cloud.
Use Cases include:
  1. 1.
    Customise an existing Experience with your own UI create you own look and feel for votes, quizzes and predictions and more and add these to your existing native or web apps. For example creating a custom layout for a Player Rater
  2. 2.
    Create you own Experience from the start. If you have a voting, quiz or prediction concept that isn't an existing Fast Track Experience you can create it using the SDK to create a new application. For example creating a new predictor for a sport we don't support yet,
  3. 3.
    Create your own new experience and use it as a template If you want to build a web experience and provide this experience to all users within you organisation, you can build it in the Javascript SDK and store this in your Experiences tab for all your teams to re -use whenever they wish. Monterosa internal teams and development partners use this to help them support their customers at scale.
This guide primarily focusses on customising existing experiences, with further guides coming soon that help you create a fresh Experience in the platform.
This guide assumes you are familiar with the Core Concepts of the platform and have access to Studio.

Getting started

You should first follow the steps outlined in the "Getting started" section of the "Embedding Experiences using the SDK" page.
Additionally you will require InteractKit which allows access to bi-directional data flows. Add InteractKit as follows:
iOS
Android
Javascript
If you use Swift Package Manager, add the following GIT repository URL as a new package dependency in Xcode:
https://<package-username>:<package-token>@gitlab.com/monterosa-sdk/ios.git
Finally, select MonterosaSDKInteractKit from the list of available Package Products.
If you use CocoaPods, the GIT repository source URL should already be in your Podfile, so you'll just need to add the following pod dependency:
pod 'MonterosaSDKInteractKit'
Add the following Interaction SDK packages as dependencies to the app-level build.gradle:
dependencies {
...
implementation "co.monterosa.sdk:interactkit"
...
}
NPM should already have been setup to locate the Monterosa / Interaction SDK packages, so we just need to install the InteractKit package:
npm install @monterosa-sdk/interact-kit
Important: You still need to include all Stable polyfills for environments that don't support the features required by Monterosa / Interaction SDK.
Your IDE should help with importing InteractKit, here are some examples:
JavaScript
iOS
Android
import {
getProject,
getEvents,
...
} from '@monterosa-sdk/interact-kit';
import MonterosaSDKInteractKit
import co.monterosa.sdk.interactkit.*

Core SDK concepts

Before starting, we recommend reviewing Core Concepts to understand how the platform works in more detail.
From a development perspective, there are a series of container concepts with a logical hierarchy as follows:
  • Project: a Project is a container and an entry point into a given configuration and management environment for an App. It contains project-level data about how the app as a whole should behave
    • Event: an Event is a representation of a live event in the real world, such as a football match, a concert, the airing of a TV-show, or even the month of June. It is characterised by its start and end times, and acts as a collection of Elements on the Timeline. It is "owned" by the Project it belongs to. Events are part of a Schedule, and can be manually started, scheduled or manipulated using the Control API for example via sports data feeds.
      • Element: an Element is a bi-directional interactive component that is triggered to appear at a certain time and process both the appearance of the content, the way it behaves, and the data which is sent back from the audience. Elements are also used for non-interactive content like Videos or Images.
  • App: the platform serves up interactive, real-time web and native applications known in the platform as simply Apps. Using Build you can create your own App, associating it with one or more Projects for management and deployment. Apps are defined using a collection of configuration files, known collectively as the App Spec. You can create and customise your own App whilst building an Experience for it.
  • Experience: Experiences are Apps which have been made available within Studio for self-service deployment via the Experiences tab. If you are building a Javascript App then it can become an Experience available to your teams to deploy in the self-service Experiences workflow from Studio. Native mobile apps are not yet supported in the Experiences workflow, but you can still develop your own native mobile apps.
  • Fields: Projects, Events and Elements each have an associated set of fields. These are key-value pairs where the set of keys available to customise and the type of data they'll contain is configured in the App Spec, whilst the value is configured via Studio. Thanks to this configurability, you can create infinite number of distinct Projects, Events and Elements.
An example of a customisable set of fields for a project

Retrieving data from the platform

Retrieving a Project

The first point of entry for your App is via the Project. The following snippet showcases how to retrieve the Project and read its associated fields. These fields are dynamic, changed easily within Studio, and can be used within your application to configure behaviour or other settings.
JavaScript
iOS
Android
async function displayProject() {
try {
const project = await getProject();
const {
id,
fields: { my_field }
} = project;
} catch (e) {
console.error('Something went wrong!', e);
}
}
func loadProject() {
// Interact will already be initialised at this stage
Interact.defaultCore.getProject(completion: { [weak self] result in
guard let self = self else { return }
do {
self.display(project: try result.get())
} catch {
// Treat the error
}
})
}
func display(project: Project) {
// You can use project fields in your UI by fetching them like so:
let id = project.id
let myField = project.fields["my_field"]
}
fun loadProject() {
Core.default!!.interact.getProject {
it.onSuccess {
display(project = it)
}
it.onFailure {
// Treat the error, `it` is a throwable
}
}
}
fun display(project: Project) {
// You can use project fields in your UI by fetching them like so:
val id = project.id
val myField = project.fields["my_field"]
}

Find Events

An Event in the platform can represent a window of time, such as a sports game, TV show or other performance. They can be of any duration. They can also be tagged with finished, active or upcoming states depending on where we are in relation to start and end time of an Event.
While you may not always want to process the currently active Event, this is the most common approach to selecting which Event is the focal point. The snippet below illustrates how to retrieve the list of all available Events and identify one that is currently active.
JavaScript
iOS
Android
async function displayActiveEvent() {
try {
// If left unspecified, the `getEvents` function would use
// the project you setup in the SDK by calling `configure()`
const events = await getEvents();
const firstActiveEvent =
events.find(({ state }) => state === EventState.Active);
const {
id,
name,
endAt,
fields: {
my_field: eventCustomField,
},
} = firstActiveEvent;
console.log(firstActiveEvent, eventCustomField);
} catch (e) {
console.error('Something went wrong!', e);
}
}
func displayActiveEvent(in project: Project) {
// If left unspecified, the `getEvents` function would use
// the project you setup in the SDK by calling `configure()`
project.getEvents { result in
do {
let events = try result.get()
// You can find an active event by checking it's state
guard let firstActiveEvent = events.first(where: { $0.state == .active }) else {
return
}
// You can fetch some of its properties
// And display them in your UI as you see fit
let id = firstActiveEvent.id
let myField = firstActiveEvent.fields["my field"]
let eventName = firstActiveEvent.name
let eventFinishDate = firstActiveEvent.endAt
} catch {
// Treat the error
}
}
}
fun displayActiveEvent(project: Project) {
// If left unspecified, the `getEvents` function would use
// the project you setup in the SDK by calling `configure()`
project.getEvents {
it.onFailure {
// Treat the error, `it` is a throwable
}
it.onSuccess {
val firstActiveEvent = it.firstOrNull { it.state == EventState.ACTIVE }
// You can fetch some of its properties
// And display them in your UI as you see fit
val id = firstActiveEvent?.id
val myField = firstActiveEvent?.fields?.get("my field")
val eventName = firstActiveEvent?.name
val eventFinishDate = firstActiveEvent?.endAt
}
}
}

Finding Elements within Events

Within an Event, content creators will be publishing Elements - bi-directional content units that have certain properties and behaviours built in, as defined in the App Spec. For example, you can define an Element to behave like a poll, containing a question, a number of possible answer options and a duration.
Since Elements are two-way in nature, there is built-in backend processing included. This is distinctly different to other WebSocket services which typically abstract mass processing of data with sending and receiving. InteractionSDK Elements can collect answers from users and send aggregated results back to your application, allowing you to show percentages in real-time.
An Event may include multiple Elements. For example if you create an Event for a soccer game, an Element might be a result prediction appearing before the game starts and another that triggers when a goal is scored.
The snippet below retrieves the array of Elements already existing within the Event and illustrates how you can use its contentType to differentiate each Element type. In the example, we determine two types of Element being received - poll and goalScored.
Note that we'll show you how you can respond to new InteractionSDK Elements being published in real-time.
JavaScript
iOS
Android
async function displayElements(event) {
try {
const elements = await getElements(event);
for (const element of elements) {
switch (element.contentType) {
case 'poll':
displayPoll(element);
break;
case 'goalScored':
displayGoalScored(element);
break;
}
}
} catch (e) {
console.error('Something went wrong!', e);
}
}
function displayGoalScored(element) {
// In this function we know the element is a goal scored, so we can equally make some assumptions
// about what data will be available. As you can see below, given the control
// you have over the App Spec, you can provide via Studio as much data as you want to support,
// in this example the goal scorer name, number, and the amount of goals they scored today,
// so you can add some extra fanfare in your UI when they score a hat trick!
const {
fields: {
goalScorerName,
goalScorerNumber,
goalScorerGoalsToday,
},
} = element
}
func displayElements(in event: Event) {
event.getElements { [weak self] result in
guard let self = self else { return }
do {
let elements = try result.get()
elements.forEach { element in
switch element.contentType {
case "poll":
self.displayPoll(element)
case "goalScored":
self.displayGoalScored(element)
default:
// Element type not recognised.
return
}
}
} catch {
// Treat the error
}
}
}
func displayGoalScored(_ element: Element) {
// In this function we know the element is a goal scored, so we can equally make some assumptions
// about what data will be available.
// As you can see below, given the control you have over the App Spec, you can provide via the studio
// as much data as you want to support, in this example the goal scorer name, number, and the amount of goals
// they scored today, so you can add some extra fanfare in your UI when they score a hat trick!
let goalScorerName = element.fields["goalScorerName"]
let goalScorerNumber = element.fields["goalScorerNumber"]
let goalScorerGoalsToday = element.fields["goalScorerGoalsToday"]
}
fun displayElements(event: Event) {
event.getElements {
it.onFailure {
// Treat the error, `it` is a throwable
}
it.onSuccess {
it.forEach { element ->
when (element.contentType) {
"poll" -> displayPoll(element)
"goalScored" -> displayGoalScored(element)
else -> {
// element type not recognised
}
}
}
}
}
}
fun displayGoalScored(element: Element) {
// In this function we know the element is a goal scored, so we can equally make some assumptions
// about what data will be available.
// As you can see below, given the control you have over the App Spec, you can provide via the studio
// as much data as you want to support, in this example the goal scorer name, number, and the amount of goals
// they scored today, so you can add some extra fanfare in your UI when they score a hat trick!
val goalScorerName = element.fields["goalScorerName"]
val goalScorerNumber = element.fields["goalScorerNumber"]
val goalScorerGoalsToday = element.fields["goalScorerGoalsToday"]
}

Displaying Elements

Once you've identified your chosen Element, we will retrieve the associated data needed to render a meaningful UI object. In this case we'll pull out the questions and options associated with a Poll.
Note: In a similar manner to how Projects, Events, and Elements are configurable sets of data, the answer options of an Element are also configurable within your App Spec and Studio, letting you add to them as much metadata as you need.
JavaScript
iOS
Android
function displayPoll(element) {
const {
question: {
text: questionText,
imageURL: questionImageURL,
},
answerOptions,
fields: {
poll_custom_field: pollCustomField,
}
} = element;
const answers = answerOptions.map(({ text }) => text);
}
func displayPoll(_ element: Element) {
let question = element.question!["text"]
let questionImageURL = element.question!["imageURL"]
let answers = element.answerOptions!.map { answerOption in
return answerOption["text"] as! String
}
let pollCustomField = element.fields["poll custom field"]
}
fun displayPoll(element: Element) {
// Note, Android doesn't support yet arbitrary
// parameters in the question. It will be added
// in future versions
val question = element.question!!.text
val questionImageURL = element.question!!.imageURL
val answers = element.answerOptions!!.map {
it.text
}
val pollCustomField = element.fields["poll custom field"]
}

Sending user responses back

If retrieving data and rendering UI is the first step, the second is to send the user's choices back to the platform.

Answering an Interactive Element

Once you have displayed the answer options on the screen, you'll want to give users a way to select their choice using the answer()method.
This lets you send your response back to the platform. Here we assume that the variable userAnswer contains the 0-based index of the answer options selected by the user. Additionally, when calling the answer() method, you'll receive an Error object that lets you identify exactly what went wrong so you can correct it.
JavaScript
iOS
Android
function sendAnswer(element, index) {
try {
// index should be e.g. 0 to the count of options available - 1
answer(element, index);
// Your answer was sent successfully
} catch {
if (e instanceof MonterosaError) {
switch (e.code) {
case AnswerError.OptionIndexOutOfRange:
// The index is outside the range of possible options
break;
case AnswerError.AboveMaxVoteOptions:
// You voted for more indexes than is allowed
break;
case AnswerError.BelowMinVoteOptions:
// You voted for fewer indexes than is allowed
break;
case AnswerError.AboveMaxVotesPerUser:
// You voted with a higher total value than is allowed
break;
case AnswerError.AboveMaxVotesPerOption:
// You voted with a higher value than is allowed in a single option
break;
case AnswerError.VotedOnNonInteractiveElement:
// You tried to vote on a non interactive element
break;
case AnswerError.VotedOnClosedElement:
// You voted on closed element
break;
default:
// Shouldn't occur, but be ready for it as more cases could be added in the future.
break;
}
} else {
// Shouldn't occur
}
}
}
func answer(element: Element, with index: Int) {
do {
try element.answer(with: index)
// Your answer was sent successfully
}
catch let err as Element.AnswerError where err == .optionIndexOutOfRange {
// The index is outside the range of possible options
}
catch let err as Element.AnswerError where err == .aboveMaxVoteOptions {
// You voted for more indexes than is allowed
}
catch let err as Element.AnswerError where err == .belowMinVoteOptions {
// You voted for fewer indexes than is allowed
}
catch let err as Element.AnswerError where err == .aboveMaxVotesPerUser {
// You voted with a higher total value than is allowed
}
catch let err as Element.AnswerError where err == .aboveMaxVotesPerOption {
// You voted with a higher value than is allowed in a single option
}
catch let err as Element.AnswerError where err == .votedOnNonInteractiveElement {
// You tried to vote on a non interactive element
}
catch let err as Element.AnswerError where err == .votedOnClosedElement {
// You voted on closed element
}
catch {
// Shouldn't occur
}
}
fun answer(element: Element, index: Int) {
try {
element.answer(index)
// Your answer was sent successfully
} catch (e: ElementVoteError){
when (e.errorType) {
ErrorType.OPTION_INDEX_OUT_OF_RANGE -> // The index is outside the range of possible options
ErrorType.ABOVE_MAX_VOTE_OPTIONS -> // You voted for more indexes than is allowed
ErrorType.BELOW_MIN_VOTE_OPTIONS -> // You voted for fewer indexes than is allowed
ErrorType.ABOVE_MAX_VOTES_PER_USER -> // You voted with a higher total value than is allowed
ErrorType.ABOVE_MAX_VOTES_PER_OPTION -> // You voted with a higher value than is allowed in a single option
ErrorType.VOTED_ON_NON_INTERACTIVE_ELEMENT -> // You tried to vote on a non interactive element
ErrorType.VOTED_ON_CLOSED_ELEMENT -> // You voted on closed element
else -> {
// Shouldn't happen but in case new errors
// are added in the future.
}
}
}
}

Validating the answer to an Interactive Element

On some scenarios, you may want to validate that a given answer is correct before attempting to submit it. For instance, this could be helpful if you want to highlight in red that the user's choice is not a valid response.
To that effect, we offer you a method called validateAnswer() that lets you perform the same validations the answer() method performs, and highlights any errors in the same manner.
JavaScript
iOS
Android
// You can validate your answer at any point, so as to provide
// feedback to your user about what went wrong:
try {
validateAnswer(element, index);
} catch (e) {
// The same error handling as was done when calling `answer`
}
// You can validate your answer at any point, so as to provide
// feedback to your user about what went wrong:
do {
try element.validateAnswer(index)
}
catch {
// The same error handling as was done when calling `answer`
}
// Not yet available on Android

Receive the results of an Interactive Element

After the user selects their response to the Element, you may want show them what other people are saying. The platform collects responses and periodically sends aggregated stats back to all connected clients. Here's how you receive those results:
JavaScript
iOS
Android
function displayPoll(element) {
// ...
const { results } = element;
if (results === null) {
// We don't have yet results, so reflect the case on the UI
return;
}
// We are able to show results to the user
const voteCount = results.map((result) => result.votes);
const votePercentage = results.map((result) => result.percentage);
}
func displayPoll(_ element: Element) {
// ...
let results = element.results
if let results = results {
// We are able to show results to the user
let voteCount = results.map { $0.voteCount }
let votePercentage = results.map { $0.votePercentage }
} else {
// We don't have yet results, so reflect the case on the UI
}
}
fun displayPoll(element: Element) {
val results = element.results
if (results != null) {
val voteCount = results.map { it.votes }
val votePercentage = results.map { it.percentage }
}
}

Responding to real-time updates

As users of your application interact with the platform, data can change at any point in time. To ensure your app displays constantly relevant data we offer the possibility to register listeners to be notified when any data you are displaying changes.

Project updates

The following snippet demonstrates how you can be easily notified about changes in the Project, be it a change in the project fields, or in the Events that are currently published. For example, when a new Event is created.
JavaScript
iOS
Android
// Called whe the project fields are updated
const unsubscribeOnProjectFieldsUpdated = onProjectFieldsUpdated(
project,
() => { console.log(project) }
);
// Called when the project listings are updated
const unsubscribeOnProjectListingsUpdated = onProjectListingsUpdated(
project,
() => { console.log(project) }
);
// Called when an event is published
const unsubscribeOnEventPublished = onEventPublished(
project,
(event) => { console.log(event) }
);
class MyProjectUpdateDelegate: ProjectUpdateDelegate {
func didPublishEvent(project: Project, event: Event) {
// Code when an event is published
}
func didRemoveEvent(project: Project, event: Event) {
// Called when an event is removed
}
func didUpdateProjectFields(project: Project) {
// Called when the project fields change
}
}
let myDelegate = MyProjectUpdateDelegate()
project.add(listener: myDelegate)
// When we no longer need to be notified about project changes.
project.remove(listener: myDelegate)
class MyProjectUpdateListener : ProjectUpdateListener {
override fun onEventPublished(project: Project, event: Event) {
// Code when an event is published
}
override fun onEventRemoved(project: Project, event: Event) {
// Called when an event is removed
}
override fun onProjectFieldsUpdated(project: Project) {
// Called when the project fields change
}
}
val myListener = MyProjectUpdateListener()
project.add(myListener)
// When we no longer need to be notified about project changes.
project.remove(myListener)

Event updates

The following snippet demonstrates how you can be notified about changes in the Event, be it a change in the Event fields, state, or Elements being added or removed from the event. For example, a new Poll being published during a live event.
JavaScript
iOS
Android
// Called when an element is published to an event
const unsubscribeOnElementPublished = onElementPublished(event,
(element) => { console.log(element) }
);
// Called when an element is revoked from the event
const unsubscribeOnElementRevoked = onElementRevoked(
event,
element => { console.log(element) }
);
// Called when the event is updated
const unsubscribeOnEventUpdated = onEventUpdated(
event,
() => { console.log(event) }
);
// Called when the event state changes
const unsubscribeOnEventState = onEventState(
event,
(state) => { console.log(state) }
);
class MyEventUpdateDelegate: EventUpdateDelegate {
func didReceiveUpdate(event: Event) {
// Called when the data in the event changes
}
func didChangeState(event: Event) {
// Called when the state of the event changes
}
func didPublishElement(event: Event, element: Element) {
// Called when an element is published
}
func didRevokeElement(event: Event, element: Element) {
// Called when an element is revoked
}
}
let myDelegate = MyEventUpdateDelegate()
event.add(listener: myDelegate)
// When we no longer need to be notified about event changes.
event.remove(listener: myDelegate)
class MyEventUpdateListener : EventUpdateListener {
override fun onEventUpdated(event: Event) {
// Called when the data in the event changes
}
override fun onEventStateChanged(event: Event) {
// Called when the state of the event changes
}
override fun onElementPublished(event: Event, element: Element) {
// Called when an element is published
}
override fun onElementRevoked(event: Event, element: Element) {
// Called when an element is revoked
}
}
val myListener = MyEventUpdateListener()
event.add(myListener)
// When we no longer need to be notified about event changes.
event.remove(myListener)

Element updates

The following snippet demonstrates how you can be notified about changes in an Element. For example if the content creator changes a field after making a mistake.
JavaScript
iOS
Android
// Called when the results of an interactive element change
const unsubscribeOnElementResults = onElementResults(
element,
(results) => { console.log(results) }
);
// Called when the element is updated
const unsubscribeOnElementUpdated = onElementUpdated(
element,
() => { console.log(element) }
);
// Called when the state of the element is updated
const unsubscribeOnElementStateChanged = onElementStateChanged(
element,
() => { console.log(element) }
);
class MyElementUpdateDelegate: ElementUpdateDelegate {
func didChangeState(element: Element) {
// Called when the element state is updated
}
func didReceiveResults(element: Element) {
// Called when the results of the element are updated
}
func didReceiveUpdate(element: Element) {
// Called when the element is updated
}
}
let myElementUpdateDelegate = MyElementUpdateDelegate()
element.add(listener: myElementUpdateDelegate)
// When we no longer need to be notified about element changes.
element.remove(listener: myElementUpdateDelegate)
class MyElementUpdateListener : ElementUpdateListener {
override fun onElementStateChanged(element: Element) {
// Called when the element state is updated
}
override fun onElementResults(element: Element) {
// Called when the results of the element are updated
}
override fun onElementUpdated(element: Element) {
// Called when the element is updated
}
}
val myListener = MyElementUpdateListener()
element.add(myListener)
// When we no longer need to be notified about element changes.
element.remove(myListener)

SDK Development status

v2 of the Interaction SDK is in development and ready for testing by existing or trial customers or those who wish to evaluate it for prototyping.