# (8.3) ProjectResourceListener Replacement

**URL:** <https://forum.inductiveautomation.com/t/8-3-projectresourcelistener-replacement/115673>\
**Category:** Module Development\
**Created:** [May 22, 2026, 8:23pm UTC](https://forum.inductiveautomation.com/t/8-3-projectresourcelistener-replacement/115673 "2026-05-22T20:23:20Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 22, 2026, 8:23pm UTC](https://forum.inductiveautomation.com/t/8-3-projectresourcelistener-replacement/115673/1 "2026-05-22T20:23:20Z")

</div>

In `8.1`, I'm using a [`ProjectResourceListener`](https://sdk.inductiveautomation.com/javadoc/ignition81/8.1.33/com/inductiveautomation/ignition/common/project/ProjectResourceListener.html) with a filter to listen for changes to my project resources.

How should I handle this in `8.3`?

I see that I can register a [`ResourceCollectionListener`](https://sdk.inductiveautomation.com/javadoc/ignition83/8.3.4/com/inductiveautomation/ignition/common/resourcecollection/ResourceCollectionListener.html) with [`ProjectManager::addListener`](https://sdk.inductiveautomation.com/javadoc/ignition83/8.3.4/com/inductiveautomation/ignition/gateway/resourcecollection/ResourceCollectionManager.html#addListener(com.inductiveautomation.ignition.common.resourcecollection.ResourceCollectionListener)), but I don't understand the [`ResourceCollectionChangeEvent`](https://sdk.inductiveautomation.com/javadoc/ignition83/8.3.4/com/inductiveautomation/ignition/common/resourcecollection/ResourceCollectionChangeEvent.html).

What is the `contextObject`?

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [May 22, 2026, 8:43pm UTC](https://forum.inductiveautomation.com/t/8-3-projectresourcelistener-replacement/115673/2 "2026-05-22T20:43:54Z")

</div>

First, depending on what you're doing, I'd highly recommend moving to a [`ResourceCollectionLifecycleFactory`](https://sdk.inductiveautomation.com/javadoc/ignition83/8.3.6/com/inductiveautomation/ignition/gateway/resourcecollection/ResourceCollectionLifecycleFactory.html) so we can do the boilerplate for you instead of you having to create listeners.

The Javadocs don't do a great job explaining WTF this is, but this is the common abstraction we use across all of our "create instances of something on the gateway when project resources change" pattern:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/e/e/eec8a025eebd7bce700f193e62acf66db3ffd6f4.png)

All that aside,  
The `contextObject` added to change events is just a generic escape hatch we use to smuggle some signals for internal usage across the generic configuration system. I would just ignore it (pass null).

Also, `ResourceCollectionListener` is "a whole project was added/removed/updated". You probably want a listener _within_ that project, which is a [`ResourceListener`](https://sdk.inductiveautomation.com/javadoc/ignition83/8.3.4/com/inductiveautomation/ignition/common/resourcecollection/ResourceListener.html).

---

<div class="post-metadata">

**Author:** ![bmusson](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/bmusson/32/87530_2.png) [@bmusson](https://forum.inductiveautomation.com/u/bmusson)\
**Post date:** [May 22, 2026, 9:01pm UTC](https://forum.inductiveautomation.com/t/8-3-projectresourcelistener-replacement/115673/3 "2026-05-22T21:01:50Z")

</div>

Awesome, thanks so much, this makes way more sense 😆

Here's what I've come up with:

```kotlin
class ClientResourceChangeListener(
    val sessionMonitor: PerspectiveSessionMonitor,
    projectManager: ProjectManager,
) : ResourceCollectionLifecycleFactory<ResourceCollectionLifecycle>(projectManager) {

    val filter = ResourceFilter(ApplicationScope.ALL, listOf(ClientResource.type))
    
    override fun getResourceFilter(): ResourceFilter {
        return filter
    }

    override fun createLifecycle(
        collection: RuntimeResourceCollection
    ): ResourceCollectionLifecycle {
        return ClientResourceSessionNotifier(sessionMonitor, collection)
    }
}

```

```kotlin
class ClientResourceSessionNotifier(
    val sessionMonitor: PerspectiveSessionMonitor,
    val collection: RuntimeResourceCollection,
) : ResourceCollectionLifecycle(collection) {

    companion object {
        const val PROTOCOL = "periscope-client-resource-notifier"
    }

    fun notify() {
        sessionMonitor.getSessionsForProject(collection.name).forEach {
            it.pages.forEach { page -> page.send(PROTOCOL, JsonObject()) }
        }
    }

    override fun onStartup(resources: List<Resource>) {}

    override fun onShutdown(resourceIds: List<ResourceId>) {}

    override fun onResourcesCreated(resources: List<Resource>) = notify()

    override fun onResourcesModified(resources: List<Resource>) = notify()

    override fun onResourcesDeleted(resources: List<ResourceId>) = notify()
}

```
