So as part of expanding the capabilities of some of my apps and drivers, I need to add new preference options. But it seems that they don't get 'fully added' until I manually click on "Save Preferences" on each app or driver.
For example, I added a new preference, with a default value:
Clicking on Save Preferences resolved it. But that means clicking on that on each and every driver that uses the new preference.
It has a default specified. The user doesn't need to select/enter anything, it should just get the default value, but it doesn't and instead it gets null until "Save Preferences" is clicked.
Is there some way to trigger this to actually save via code?
This is the expected behavior. There is no way to "trigger" this manually. However, you can either set the value of a preference in your installed() method or use your default value if the value is null (often via the ternary conditional operator), among other ways of addressing the issue.
Thanks, I'll do that. When you explain it, it's extremely obvious I could do that... but I totally did not think of doing that.
I've literally got a few other places I'm already using updateSetting()... still didn't think of just checking for null and setting defaults in my code.
I think I'll just write a few 'getter' methods to wrap up accessing the settings values, and if they're accessed and null, then set them to the default and return the default. That should work just fine.
The "Speak" command that's auto-added via the SpeechSynthesis capability seems to have a way to get the default, so it must exist, but I didn't see it documented anywhere.
I am not aware of any way this is exposed (including intentionally undocumented location properties I did a quick look at to verify ).
Assuming you mean on the device detail page as the pre-selected value, I assume that's just coming from the platform itself (which does, of course, know -- and at some point needs to know -- the value) when displaying the UI.
Initialize runs on reboot, yes. But initialize doesn't do whatever "Save Preferences" does to actually write the preferences out to the actual device object state.
From what I can tell Hubitat does something before it does "updated()" when you click on that button.
I just wrote the above 'getters' (now a bit modified from that screenshot to avoid naming conflicts). It works well, and it allows for more flexibility like setting things to Integer on 'get' as well.
Adding to the above, initialize() is only called on system start if the driver implements capability "Initialize". I'm guessing this one does (this is pretty much what you'd need to maintain a webscoket connection), but some authors write an initialize() method that they call from both installed() and updated() to more easily share code between the two (even though there is no requirement to use this name for that case alone). There is nothing special about that case.
Look again at the code I sent you the link for. Several of the Migration items address that specific issue. It's designed for on-the-fly migration of settings.
FWIW, I would do the migrations in-line rather than with initialize(). In-line will handle deploy of new code in a running hub and does not require a reboot.
Like I said above, I just replaced ALL references in my code to a preference value to instead use a "getter" for them. In that getter it checks for null, and returns a default if it's null. I don't bother setting the preference if it's null since it's not really needed. Once the user selects a preference or clicks on "Save Preferences" it'll no longer be null.
The getter method has the added benefit of being able to do casting, etc. No need to litter my code with "as Integer" because the preference comes in as a String or BigDecimal or whatever and I need Integer. The getter can handle that as well.
And it makes it easy to get the preference from one device in another, since I can just call the getter on the other device.
I don't bother setting the preference if it's null since it's not really needed.
No need to litter my code with "as Integer"
But, if you have these sorts of validations for some values, then at some point it becomes easier to do a wrapper for all of them, even if many/most literally just returns the value. The upside is having the same syntax for every variable - as per the example, "get_settingName". You don't need to keep track of if it has been checked, or where. Every single settings variable reference goes through the wrapper. And "get_settingName()" isn't much worse than "settingName" (and I prefer using "settings['settingName'] over just "settingName"). edit: Or _settingsName works too, just more easily confused with non-settings variables. /edit
But yes, overkill to solve just the one null issue, even tho one null issue suggests you'll have another sooner or later
Where it's checking for Groovy "truth" instead then it will always return true. If disableTrackDataEvents is null, then it returns the 'else' on the ternary, which in this case is true. If it's not null and is set to false, well, that's also a Groovy truth false, so it returns true. If I want to have it as "if this is null, return the else, otherwise return its value" then I need to explicitly check for null.
Or to use your example, if settings['brightnessStartLevel'] = false or null, then it would return settings['device'].currentLevel(). If it's 0 then it'll also return settings['device'].currentLevel() since 0 is 'false' and you're checking for 'not false' in that case.
And since you're using a chain of plain if rather than if-else if, you'll never get down to if(settings['brightnessStartLevel'] < 1) return 1 in the case of a zero.
And since you're using a chain of plain if rather than if-else if, you'll never get down to if(settings['brightnessStartLevel'] < 1) return 1 in the case of a zero.
Yes, that was by design. My typical assumption is zero is not user-entered (tho not always true) , therefore I'd treat it the same as a null. For the <1 check, I was thinking decimals and/or negative, even not plausible (as a made-up example) since input type would prevent. However, if zero might be either valid, or user-entered, just add a check for zero prior to null, and/or explicitly check "== null". No nesting required, since they're all mutually exclusive.
Personally, I hate inline if's, but to each there own. It's on me for not noticing they're "return".