PubSub is a very simple module to facilitate the Pub/Sub pattern in Xojo: GitHub - stam66/PubSubXojo: Module that implements a simple publish/subscribe model in Xojo..
The publish/subscribe model is a direct way to avoid raising convoluted events and event handlers, especially where unrelated objects need to react to the same message.
I ported this from library implementing Pub/Sub model for LiveCode, as I couldn’t find anything similar to this for Xojo - or maybe I missed it (worth noting that the LiveCode library is only 33 lines, this is about 150, which probably says something about perceived verbosity in LiveCode… or maybe more about my Xojo skills - happy for any feedback).
The module includes 4 methods:
-
Subscribe(eventName As String, callbackMethod As String, target As Object) to register an object to receive a specific message when broadcast.
-
Broadcast(eventName As String, data As Variant = Nil) to broadcast a message with optional parameters.
-
Unsubscribe(eventName As String, callbackMethod As String = “”, target As Object)
-
RemoveAllSubscriptions()
The module has a private property to store subscriptions:
Private mSubscriptions As Dictionary
Dictionary structure: Dictionary[EventName:String] → Dictionary[Target:WeakRef] → String() of callback method names
Example usage
// Add a method to your WebPage
Public Sub HandleDataChanged(data As Variant)
MessageBox("Event received: " + data.StringValue)
End Sub
// In Opening event or a button:
PubSub.Subscribe("TestEvent", "HandleDataChanged", Self)
PubSub.Broadcast("TestEvent", "Hello World!")
// Test unsubscribe
PubSub.Unsubscribe("TestEvent", "HandleDataChanged", Self)
PubSub.Broadcast("TestEvent", "Should not appear") // Won't trigger
// Or test removing all
PubSub.RemoveAllSubscriptions()
A practical use case could be if you need multiple changes to happen in your app anywhere when for example logging in: A message such as “LoginSuccessful” can be broadcast from the login dialog to any object, instead of having to raise an event(s). I needed this because I needed changes to happen both in webpage, controls and webcontainers and this made it simpler.
Posting it here as some may have use of this (Apache 2.0 licence).
Stam
2 Likes
Ah, the Introspection system rises again.
I would like to suggest you take a look at Xojo’s Delegate functionality, as it would drastically simplify your code (and speed it up – Introspection can be sluggish). It also gives you built-in checking for “does this object that I’m trying to subscribe actually have the method to receive the events”. Your code gracefully handles the case where this is false, but there are considerable benefits to letting the compiler catch this for you as it almost certainly is not what the developer intends.
1 Like
Personally, I prefer an interface. But introspection breaks sometimes, so it’s best just to avoid it for mission critical features.
As I learned from Greg-O recently, a Delegate can be used with a Method inside a Module, unlike a class interface. A minor thing but could be an advantage in the right situation.
Thanks all - I’m all for learning new stuff and have made a stab at using delegate.
I’ll be honest, it was working perfectly for me before, but always open to feedback and for suggestions for improvement, especially if I’ve misunderstood the “delegate functionality”.
Current module structure:
Delegate: EventCallback(data As Variant) (all callbacks need to have param as variant even if it’s nil).
Property: Private mSubscriptions As Dictionary Dictionary structure: Dictionary[EventName:String] → Dictionary[Target:Object] → EventCallback()
I’ve added a couple of methods to the existing ones:
Subscribe(eventName As String, callback As EventCallback, target As Object)
Broadcast(eventName As String, data As Variant = Nil)
Unsubscribe(eventName As String, callback As EventCallback, target As Object)
UnsubscribeAll(eventName As String, target As Object) Unsubscribe all callbacks for a target from an event
UnsubscribeTarget(target As Object) Remove all subscriptions for a target across all events
RemoveAllSubscriptions()
I’ve tested this in my web app and it works - I really can’t convince myself its faster or better, but I guess type-safety may be important in the future…
My GitHub repo above has been updated - grateful for feedback…
Stam
Pretty good. I’d suggest that Unsubscribe does not need the object parameter - this requirement will prevent the use of a Module method, as you can’t refer to a Module as an object. This may also affect your Subscribe method as well.
My big complaint with delegates is they can be nil (easily testable) or non-nil and uncallable (not testable). So if passed a weak version, the target could now be nil and attempting to invoke will cause an exception. You can try to catch the exception, but that handler will also catch any unhandled exceptions inside the invoked delegate. And since Xojo’s exceptions provide effectively zero information, determining if you should forward the exception or not is difficult.
MBS has a method GetDelegateTargetMBS method which can help, but it’s also not bulletproof. Christian has had to adjust it in the past as Xojo changes things. And for a distributable library, requiring MBS isn’t a popular option.
I get the benefit of modules, but for my implementations of a notification system, I always use an interface and weak refs.
I have an open feature request for Xojo to include such a framework and build it into the IDE. I don’t expect much traction on it though.
One tip for @Stam_Kapetanakis: this topic is more complicated than it seems at first glance. Threads are a good way to confuse things. If the broadcast is made from within a thread, you’ll want to not immediately push the notifications to subscribers, as those will all fire inside that thread. Instead a queue and timer is usually the best option to ensure that notifications are always delivered on the main thread. You should also take care that an exception in one handler does not interrupt delivery to other handlers.
Edit: Happy to share some inspiration. My Beacon project has the notification kit I have crafted. You can view the files in a browser at Beacon/Project/Modules/NotificationKit.xojo_code at master · thommcgrath/Beacon · GitHub for the core module code and Beacon/Project/Modules/NotificationKit at master · thommcgrath/Beacon · GitHub for the classes it uses. One link would be nice, but the Xojo project format doesn’t work that way.
1 Like