@Field scope of a Map?

I have two @Field defines - a String and a Map and they work correctly within my app but within a child device driver they are out of scope. That's fine I expected that, although I hoped differently.

During execution my child driver makes parent calls back to my app and then the Map variable is out of scope (well empty) but the String variable is in scope and has previous content.

Why is this and is there anyway I can access my Map object within my app when a parent method is called from my child driver ?

I am doing this to remove some atomicState storage trying to eliminate it for any data that is not required to persist across restarts

An update - what is happening here is that my app finishes it's initialisation and then just waits for events. When my child driver calls a parent method the @Field statements are being executed again and re-initialising the values. Maybe that is expected as they got unloaded when the app completed initialisation ??

Is there anyway I can maintain a memory structure within my app in this scenario , or am I commited to having to use atomicState (or state) even though I build this data from scratch every time. I don't need it retained.

Kevin, could you please post a code sample? I'll trace it down.

That's very kind but it's 7000 lines of code but I will if you wish ? I think I know why it's happening (see my second post) as I can make it happen with the String now - I thought before it was in scope but I was initialising it with a value in @Field

@Field String testString="Hi Kevin"
//@Field Map topicLink = new LinkedHashMap()
//@Field Map topicLink = new HashMap()
// @Field Map topicLink = [:]
if (TopicLink==null) {
    @Field Map topicLink = [:]
}

I tried all the variations and even tried to stop it being reinitialised

I could probably create a small 20 line app and driver that illustrates this...

If this is inherent behaviour because the app completes awaiting events and purges its memory then @Field has very limited usage for an event driven app

First, the behavior of @Field-annotated variables hasn't been formally divulged to us by staff, as far as I can tell, so I'm not sure I'd recommend it for usage outside of their documented recommendations. However, it appears others, including at least nh.schottfam in this post (who I assume depends on this behavior for webCoRE and would notice if it were different), discovered it can persist between executions in the way that you describe.

I suspect the issue is indeed your initialization of the variable:

@Field String testString="Hi Kevin"

If you aren't familiar with Groovy, all top-level code in a script (which, without seeing Hubitat's exact implementation, I would assume apps and drivers count as) gets shoved inside a method (run(), to be precise), and then that method is (wait for it...) run whenever the script is run (which would be every time an app or driver is invoked on Hubitat since they don't stay constantly "running," just run when invoked somehow), so that would include your initialization code in this example. (The @Field annotation just ups their scope so they aren't restricted to just the run() method, which would not do you any good for pretty much any of your actual code.)

tl;dr can you just declare it, initialize it somewhere else if/when needed, and then use it in the way you're trying to do?

2 Likes

It needs to be static. Each event execution creates a new instance of the "class" that represents the device or app. If you don't make it static you're getting a brand new @Field on every execution.

I have this in my Kevo integration to do some synchronization across executions

@Field static java.util.concurrent.ConcurrentLinkedQueue commandQueue = new java.util.concurrent.ConcurrentLinkedQueue()

That works just fine and doesn't reinitialize.

1 Like

Ohh... of course ... let me try static...

I could be wrong since I've never tested this, just going by with what it seems like might be reasonable. Living dangerously, I know. :slight_smile: And I didn't mean to imply that it couldn't be static--I just copied and pasted a line from yours and didn't pay attention to how it was declared. Hope you find something that works!

1 Like

Thanks so much both of you ..

.. static works

( never had that in my Pet Commodore Basic ! )

3 Likes

A consequence of that though, if someone installs two instances of your app, it's going to share that variable. How I've resolved this is if I wanted, let's say a boolean in a @Field instead I make a Map of booleans where the key is the appid.

1 Like

That in itself is a nugget of information - but yes I see using a Map with the appID would solve that

There's no way I can share this Map (in memory) with my child device driver is there , or just for completeness a child app ?

Sure, make a method called getMap in the app that provides access to the @Field, then call parent.getMap() from within the children.

OK - makes sense .. so is a pointer passed to the memory construct and is there a possibility that an update made within the child could conflict with a simultaneous change made in the main app ?

I'm going to guess this overlaps with the mutex thread that's being discussed currently to be totally safe with concurrency as there's no locking on the variable (for example in an increment counter situation)

Great questions... A Map is not inherently thread safe so you will have race and concurrency issues. You can use the synchronized keyword to lock your critical sections.

1 Like