This is a hacky, not optimal solution, but I've given up trying alternatives.
Register a preference (better is to use an existing one, so your preferences aren't cluttered)
class Preferences(AddonPreferences):
example: StringProperty(
name="foo"
update=callbackfunc
)
In the callbackfunc, you can populate your CollectionProperty via .add.
Then just change this preference in order to trigger the callback:
bpy.context.preferences.addons[__name__].preferences.example = str(bpy.context.preferences.addons[__name__].preferences.example)
Just beware that draw() gets called an awful lot so have some sort of "do we need to update?" function so you don't hammer your CPU/disk.
skipUpdate = False
class MyPanel(Panel):
def draw(self, context):
global skipUpdate
if not skipUpdate:
bpy.context.preferences.addons[__name__].preferences.example = str(bpy.context.preferences.addons[__name__].preferences.example)
skipUpdate = True
I'm sure with more development time you could find a different callback that doesn't require registering a preference.
(By the way, this seems to be the method Material Library VX uses, which is where I shamefully borrowed it from.)