Product Updates

Updates to the Event Engine and Event Listeners

10 min read Darryn Campbell on Apr 18, 2024
feature.png

We are working hard to ensure that all our SDKs behave consistently and provide PubNub developers with the best possible experience.  We have recently introduced a number of changes, collectively known as updates to the “Event Engine,” as well as a new set of “Event Listener” APIs that offer greater control over how you receive PubNub Events.

This article seeks to explain what these different updates are as well as give an overview of our new “Event Listeners.”

SDK Support

At the time of writing, the latest releases of our Java, Swift, Javascript, Kotlin, and Rust SDKs support the new Event Engine and Event Listeners, with support in additional SDKs coming soon.

What is Changing?

At a high level, we are making the following changes to our SDKs:

  • Reworking our event listener APIs so you no longer have to pass a single, global PubNub object around your entire application.  Of the four bullets on this list, the new event listener APIs will deliver the greatest benefit to PubNub developers, so most of this article is dedicated to these.

  • Updating and harmonizing the logic that manages the communication with the PubNub backend, receiving events such as messages or presence.  This is the ‘Event Engine’.

  • Improved our ‘presence state’ management.  This has been made possible through improvements to the ‘Event Engine.’

  • Harmonizing our retry policy to make it consistent across all SDKs.  This has also been made possible through improvements to the ‘Event Engine.’

Change #1: Rework of the Event Listener APIs

We are improving our SDKs to provide more granular event listeners, allowing you to scope to the specific entity which event(s) you want to receive.  

What is an entity?  An entity can be one of ‘Channel,’ ‘Channel Group,’ ‘User Metadata,’ and ‘Channel metadata’.  

What is an event in this context?  That is one of ‘message,’ ‘presence events,’ ‘signals,’ ‘object event,’ ‘message actions,’ or ‘file.’

These improvements allow you, amongst other benefits, to specify separate message handlers for different channels, meaning you no longer have to keep track of the global state of your application and maintain a single listener for all PubNub events.

At the time of writing, we are still rolling out these updates across our range of SDKs.  I have used JavaScript for the code samples in the following sections, but you will find the equivalent functionality implemented in each of the SDKs I listed at the top of this article.

What is changing with Event Listeners?

Until recently, all of our SDKs contained a single monolithic object, usually called PubNub, at the center of the SDK, providing entry points to all possible interactions, e.g., subscribe, unsubscribe, publish, etc.  If you want to perform any of these actions, such as subscribing to a specific channel in a specific screen of your app, you need to pass a reference to this global PubNub object throughout your app.

In addition to initiating actions, the PubNub Object also allows the user to listen to status updates (related mostly to the subscribe connection state) and real-time events coming from the PN network, such as messages, signals, presence events, objects, files, and actions through the addListener() and removeListener() methods.  This addListener() code usually becomes bloated because of the large number of condition blocks to handle all the event types and sources - just look at the code for one of our demo apps as a case in point(!) 

Additionally, it is important to understand that everything happens in the global scope of the PubNub object, that is:

  • The channel/channel group subscription list is global (i.e., scoped to the PubNub object), and the global list is affected through subscribe/unsubscribe calls.

  • The status and event listeners are global and emit events related to all channels/channel groups currently subscribed to.

Consequently, if you have multiple screens in your application, each requiring a connection to different PubNub channels, you must track which connections are required manually as your user navigates around your app.  This process becomes prone to errors as you unsubscribe from channels that are no longer required. 

New Architecture for Event Listeners

The new architecture, currently rolling out across our range of SDKs, introduces new, more narrowly scoped ways of dealing with subscriptions and listening to events. These updated SDKs are currently backwardly compatible though deprecation is discussed later in this article. The new architecture offers additional methods to create what we call ‘entity’ objects: 

  • Channels

  • Channel Groups

  • User Metadata

  • Channel Metadata

These entities contain a subscription() method that returns a Subscription object.  For example, our JavaScript API can be called as follows:

You can think of the returned Subscription object as a more narrowly scoped PubNub client; it contains subscribe() and unsubscribe() methods to activate or stop the subscription to its parent entity. There are two ways to receive events on a Subscription:

1. The on[Event] handlers as shown in the above code snippet for onMessage and onPresence.  Other handlers exist for signals, objects, files, and message actions.

2. The addListener method will receive all events within a single function and you will find this syntax more familiar if you are upgrading from our previous APIs.  The equivalent code in JavaScript to listen for messages and presence events using the addListener method will look as follows:

What are the benefits of the new Event Listeners?

You can create multiple Subscription objects for any entity (e.g., channel), and all of these Subscription objects will be independent of each other, even ones created for the same entity.  This means that each Subscription can be activated (subscribe()) or stopped (unsubscribe()) without affecting other Subscriptions and allows for a much more flexible structuring of client applications, compared with managing the global state through the single PubNub object.  

Note: Creating multiple Subscription objects is cheap in terms of resources since the SDK will take care of multiplexing all these subscriptions over a single server connection for you.  Contrast this with the ‘old’ approach, where creating multiple PubNub objects would cause each to maintain a separate server connection.

Finally, SDKs will still support a global addListener() method on the PubNub object, but this should only be used to listen for status events, such as Connected or Disconnected.

Let’s consider a simple application that shows the values of various stocks.  When the user first launches the application they are shown the values of some popular stocks, but when the user logs in, they are able to select their favorite stock symbols and will also receive the current prices of these.

To implement this application in PubNub, you might choose to have two channels, a “commonStock” channel and a “favoriteStock” channel.

Now, let’s consider the user journey and how you would track which channels to listen for stock updates on:

With the previous architecture for channel subscriptions, which depended on a global object

1. The user launches the application.

The application subscribes to the “commonStock” channel

2. The user logs in

The application subscribes to the “favoriteStock” channel

The application is now subscribed to two channels: “commonStock” and “favoriteStock”

3. The user logs out

The application now has to determine which channels to unsubscribe from, in this case, “favoriteStock”.  The application is now only subscribed to a single channel: “commonStock”

Although trivial in this example, you can see how as the number of screens and channels increases, it becomes increasingly complicated to manage for large apps.  You need to have some component in your application that has a global view of your subscribed channel list and understands what should happen to that list as users navigate through your application. 

Now, let’s walk through that same scenario with the new Event Listeners

1. The user launches the application.

The application subscribes to the “commonStock” channel, as before

2. The user logs in

Since we want to receive common and favorite stock prices, we create two subscriptions for the logged-in user.  We do not need any previous knowledge that the user was subscribed to the ‘commonStock’ channel.

The application is now subscribed to two channels: “commonStock” and “favoriteStock.”  Note that even though the user is subscribed to both stocksSub1 and stocksSub2, this does not use any additional resources, and they will not receive duplicate messages.

3. The user logs out

As part of the user log-out logic, we unsubscribe from both the “commonStock” and “favoriteStock” channels.  We do not have to consider what channels a logged-out user has access to since that is outside of our scope.

The application is now subscribed to a single channel: “commonStockPrices,” because of the existing stocksSub1 subscription.

The key difference here is that the different screens of the application only had to take care of what happened within their own scope, meaning you do not have to maintain a global view of whether “commonStock” should still be subscribed to; the new event handlers that all for you

Combining Subscriptions into a Subscription Set

The examples so far have only considered individual channels, but the Subscription object is not limited to a single Channel or Channel Group, which can be combined together into a SubscriptionSet to handle the events on all these channels together.

To extend the previous example, we could have handled both logged-in channels with a single SubscriptionSet, with a single event handler.

Note that while the Subscription was created from the entity, the SubscriptionSet is called on the global PubNub object.

When will PubNub-Scoped Subscription be deprecated?

As we introduce the new architecture for event listeners, the previous APIs are being deprecated on a per-SDK basis.  Take the Javascript SDK, for example, and notice that the “old” pubnub.subscribe() and pubnub.unsubscribe() methods have been marked as deprecated.  We aim to make the upgrade process as friction free for our developers as possible, so each of our SDKs supports a long end-of-life (EOL) period.  Please see the documented EOL period for your specific SDK in the docs, found towards the bottom of the SDK feature list.

Change #2: Updates to our Event Engine

At the heart of our SDKs, there is a logic loop to handle the connection with our PubNub infrastructure to receive events such as messages or presence updates - this is what we are referring to as our event engine.  In general, this is not something you configure when using our SDKs and will ‘just work’ out of the box for 99.9% of our customers, but as we developed our SDK family over the years (as with any software engineering project), you accumulate technical debt.  While all of our SDKs provided a consistent experience to the user, behind the scenes, different SDKs had implementation quirks within the event engine that made it challenging for us to add new features and track down rare edge cases. 

We are updating the event engine in each of our SDKs so they all handle state transitions in a predictable and consistent manner; this will allow us to add enhancements that provide additional functionality while reducing the number of edge cases our customers will experience.

What code changes are required for the new Event Engine?

A new configuration option has been added to the SDK configuration, enableEventEngine which the documentation states will “use the standardized workflows for subscribe and presence.”  By ‘standardized,’ this reflects our efforts to standardize the implementation of the engine across all of our SDKs described in the previous section.

This is a non-breaking change, meaning that if you set this option to true, you will not have to make any changes to your application unless otherwise documented. The only documented example so far is the Kotlin status events for Subscribe have been updated to make them consistent with other SDKs (detailed in our matching our generic connection workflow).  These updated statuses are used in Kotlin by the connection status listener.  Please consult the SDK documentation for additional exceptions and required changes when enabling the new Event Engine.

Change #3: Updates to our Presence State Management

Presence State refers to the custom user state that can be set but will only persist as long as the user is subscribed to the channel.  Typical use cases for presence state include maintaining the player score, game state, or location.  If you are familiar with our collaboration (whiteboard) demo, that app uses ‘presence state’ to register and update the user’s cursor position.

We have identified some edge cases with specific customers who would benefit from handling presence state updates differently. However, this is on a case-by-case basis, and most customers can continue to use the existing presence state logic without change.  Specifically, the ‘existing’ presence state logic is setPresenceState, getPresenceState, and listening for the ‘state-change’ presence event.

What code changes are required for the new Presence State Management?

A new configuration option has been added to the SDK configuration, maintainPresenceState.  Most customers, unless otherwise advised, should retain the default value for this parameter, ‘true’.  Setting this value to ‘true’ will retain the current behavior, as described above.

Change #4: Harmonization of our SDK Retry policy 

In the event of client disconnection from PubNub, the automatic reconnection policy has been historically inconsistent between our different SDKs.  

Most of our SDKs would offer some way to configure how to recover from a connectivity issue, most commonly by exposing a ‘retry policy’ configuration and allowing the developer to select between ‘none,’ ‘linear,’ or ‘exponential.’ However, the timeout values associated with each policy would be hardcoded and specific to the SDK.

The retry policy is being improved and made more customizable, as documented on the Connection Management Reconnection Policy page.  As we continue to roll out these changes throughout our SDKs, the retry policy will become harmonized.  You can now configure:

  • Linear retry policy, with a customizable delay interval and configurable maximum retry count.

  • Exponential retry policy, with a customizable minimum and maximum delay, along with a maximum retry count.

  • Endpoints that should be excluded from the retry policy, for example, if the data is time-sensitive and does not make sense to retry, you can exclude it.  Consider a live event where a large audience is reacting to many messages; you might choose to exclude the ‘message_reaction’ event type.

Please consult the configuration section of your SDK for the new global retryConfiguration option and any SDK-specific limitations.

More Resources:

The changes described in this article, especially the changes to the Event Listeners, represent a fundamental change to how developers interact with our APIs. Still, we strongly believe that the long-term benefits outweigh the initial learning curve.  We want to make your transition to the new Event Listener APIs as easy as possible, so if you need help or support, feel free to contact our dedicated support team or email our developer relations team at devrel@pubnub.com.

We are also interested in your feedback. Is there something we could do or provide to make your upgrade easier?  Please let us know. 

0