Issues with deselection of settings

I'm kind of late to the game, read some of the stuff above here.

here is my quick take. Yea, there is code that can be bad, and causes trouble. But like Windows for example, there are some checkes in the system that in most cases (not all) will negate a bad batch of code. This is built into the system, because the average user, should just NOT have to worry about slow downs. right now, I reboot mine at 4am, just because, the last update helped where I could get away with 3 days, but 1 day is just good.

long story short, the Dev's of HE need to put a little more research into what is causing the issue and just help getting it work.

sorry, a little rant..

1 Like

I don't think you're following what I am saying.. Dynamic preferences cannot be tracked in a manifest because they are generated with evaluated, dynamic names at runtime and cannot be defined in a manifest of any kind.

i.e.

input "attr_"+(it+2), "string", title: "Attribute ${it+2}:", required: false, multiple: false, defaultValue: null, submitOnChange: true

That's what I was getting at and why a manifest won't work. But I am glad that you have things figured out with removal of settings. :slight_smile:

Correct, they can't be tracked from a static manifest, which is why I was suggesting it be a parameter of the input itself. E.g.

input "attr_"+(it+2), "string", title: "Attribute ${it+2}:", required: false, multiple: false, defaultValue: null, submitOnChange: true, garbageCollect: true, dependentOn: ["someOtherInput"]

Also, I'm playing around with app.removeSettings and found one slight issue to using it. Before discovering it, I was forcing a user to unset a child setting before being able to unset a parent setting to prevent orphaned child settings, like so:

input "parentSetting", "text", submitOnChange: true,
    title: "Parent Setting${!parentSetting && childSetting ? " (required because dependent setting configured)" : ""}",
    required: !parentSetting && childSetting
if (parentSetting || childSetting) {
    input "childSetting", "text", submitOnChange: true, title: "Child Setting"
}

With app.removeSettings, I can simplify this to:

input "parentSetting", "text", submitOnChange: true, title: "Parent Setting"
if (parentSetting) {
    input "childSetting", "text", submitOnChange: true, title: "Child Setting"
} else {
    app.removeSetting("childSetting")
}

The only problem I'm seeing is that if another input on the same page references childSetting and displays something based off of it, then the UI for it won't get updated when app.removeSetting("childSetting") is called. Is there a way to force the UI to refresh?

Just to clarify, since it's easier to demonstrate coding ideas with examples rather than long winded explanations, here's the kind of thing I think would be cool and helpful.

From the developer's perspective, they would write something like this:

def mainPage() {
    dynamicPage("mainPage") {
        section {
            input "parentInput", "text", submitOnChange: true
            if (parentInput) {
                input "childInput_1", "text", 
                    garbageCollect: true, dependentOn: ["parentInput"]
                href name: "toSomeOtherPage", page: "someOtherPage"
            }
        }
    }
}

def someOtherPage() {
    dynamicPage("someOtherPage") {
        section {
            input "childInput_2", "text", submitOnChange: true,
                garbageCollect: true, dependentOn: ["parentInput"]
            if (childInput_2) {
                input "childOfChildInput_2", "text", 
                    garbageCollect: true, dependentOn: ["childInput_2"]
            }
        }
    }
}

And behind the scenes, the platform code would handle that kind of like this:

List inputNamesForDisplayedPage = []

def renderUI() {
    /*
    Code that calls the page's method and renders it
    can keep a list of rendered input names...
    List newInputNamesForDisplayedPage = ...
    */
    List removedInputNamesForDisplayedPage = inputNamesForDisplayedPage - newInputNamesForDisplayedPage
    garbageCollectSettings(removedInputNamesForDisplayedPage)
    inputNamesForDisplayedPage = newInputNamesForDisplayedPage
}

def garbageCollectSettings(List settingNames) {
    settingNames.each { settingName ->
        if (getSetting(settingName)?.garbageCollect) {
            app.removeSetting(settingName)
        }
        List allDependentSettingNames = getAllSettings().findAll { it.dependentOn?.contains(settingName) }.collect { it.name }
        garbageCollectSettings(allDependentSettingNames)
    }
}

And finally, from an end user's perspective, if they unset the "parentInput" setting, then all of the other three inputs that were dependent on it, even those that are dynamic and on a different page, would be removed instead of orphaned.

I think one thing you are forgetting is the required: true parameter. If you have a setting that is required you won't be able to remove it from the app and therefore this "garbage collection" is a moot point.

But what you are proposing is already possible if you don't have required:true for parentInput. If you have gone in and removed the setting for parentInput, in your updated() method, you can use the following code:

if(!parentInput){
    app.removeSetting(childInput_1)
    app.removeSetting(childInput_2)
    app.removeSetting(childOfChildInput_2)
}

Then those settings would be removed from the app. But what I don't understand is why you would have a setting in an app that you don't actually need. Usually those settings in the preferences section are always required.

1 Like

I fail to see how the "required: true" parameter is relevant to garbage collection. Give me an example. If an input is marked as required and it is not dynamic (always shown, not shown on the condition of another parameter (in which case it is no longer required)), then it doesn't need to be garbage collected anyways because a user won't be able to hit save until they've configured it. Garbage collection would be for dynamic inputs.

Huh? Aren't most settings in Hubitat apps optional... aka not needed... aka the user can set them if they want, but they don't have to?

The problem with this solution is that it doesn't help when rendering the UI. Consider the following situation and code:

I want a user to select a motion sensor and / or a virtual motion sensor, and then select modes in which each will be active, but I don't want both to be active in the same mode, so I have this code:

def modeNames = location.modes.collect { it.name }

input "motionSensor", "capability.motionSensor", title: "Motion Sensor", submitOnChange: true
if (motionSensor) {
    input "motionSensorModeNames", "enum", title: "> Motion sensor modes",
        options: modeNames - virtualMotionSensorModeNames,
        multiple: true, submitOnChange: true, required: true
}

input "virtualMotionSensor", "capability.switch", title: "Virtual Motion Sensor", submitOnChange: true
if (virtualMotionSensor) {
    input "virtualMotionSensorModeNames", "enum", title: "> Virtual motion sensor modes",
        options: modeNames - motionSensorModeNames,
        multiple: true, submitOnChange: true, required: true
}

Now, let's say the user selects a motion sensor and a "Day" mode for it, and then they select a virtual motion sensor and a "Night" mode for it. But then, they decide they don't want to use the motion sensor at all, and want the virtual motion sensor to handle both both modes, so they remove the selected motion sensor. At that point, since they didn't first deselect "Day" from the motionSensorModeNames input, that setting is now no longer visible to the user, but still configured, so when they go to add the "Day" mode to the virtualMotionSensorModeNames input, it's not going to appear in the list of options, because it's still in motionSensorModeNames.

Now, there are ways to work around this issue (see the first block of code in post 126 for one solution), which I have done in my actual code, and that plus calling app.removeSettings in the updated method can completely eliminate orphaned settings and the issues they cause, but the UI workarounds and adding a bunch of removeSettings calls in the updated method are messy and tedious! Which is why most Hubitat developers don't do it and end up with orphaned settings related bugs. Adding a couple extra parameters to the input method and implementing optional setting garbage collection would greatly clean things up, and it would be easy enough that developers might actually use it and we would see less bugs related to this.

All i did was suggest a possible workaround to help clean up your settings. You're not going to get what you have asked for from Hubitat, so I was merely offering a suggestion. Clearly, you want only what you want and nothing else so I apologize for saying anything.

No need to apologize. I do appreciate your suggested solution, and in some cases, that would work, but I was merely pointing out that it doesn't help in every case.

I realize that Hubitat probably isn't going to add this functionality, but like you, I was just making a suggestion. A non-developer in this thread originally pointed out the issue of having devices linked to apps in which those devices didn't appear to be configured from the user's point of view, and I experienced quite a few headaches with that as well, as a user, when I first got my Hubitat, before I ever tried to develop anything for it. And now that I have developed for it and understand what's going on, I thought I would chime in and suggest a potential solution. ¯_(ツ)_/¯

No....settings in complex apps like Rule Machine are created dynamically. You won't see the same settings in two different rules. Here are the lists of settings for two of my Rules.

The whole point of your "cleanup" is the option becomes deselected. But the issue with persistent options is that they are not deselected. The option still has a selection in it. That's why it shows up in the in-use-by in the edit devcice page. So, how would your function know that it needs to be removed when it is still populated?

I know! Settings in one of MY apps are created dynamically. If my settings were all static, I would have no desire for the cleanup functionality I'm asking for.

No, the point of my cleanup is for options that are NOT deselected! Just like you said, those are the ones that are a problem. The garbageCollect function would know which dynamic settings need to be removed because they would be linked to some parent setting further up the chain via the dependentOn parameter in my example. I gave the code for it. I'm not sure how to be any more clear about how it would work than that. If you can come up with some specific code that you don't think would work with that model, you can post it and we can test it.

So, how would you know to remove the parent setting? This is what I am not understanding . If you knew enough to remove the parent setting, why not just remove the child setting.

This would also mean that the whole way that rule machine creates settings would have to be re-worked.

1 Like

The same way Hubitat (the app executor) knows how to render the UI for an app. It calls one of your page methods, and your app then calls the input method for each input it wants rendered in the UI. If the app executor kept track of what was last displayed in the UI, it could compare each time that page was refreshed / rendered, and it would then know which inputs (on that page only) had gone away. And that's all it needs to know. As long as other inputs not on that page are linked via a dependentOn parameter, they can be cleaned up too. But as it is currently, without the developer passing that information in a dependentOn parameter when creating an input, there is no way to know which child settings on other pages should be cleaned up.

Absolutely nothing would have to be reworked. The garbageCollect parameter of inputs would default to false, and all existing apps would continue to function exactly as they always have. This is extending an API, not changing it.

Okay, I'm about argued out, so I'll leave it at that. We may not all agree on my implementation for a fix, and we don't have to, because it's not my responsibility to fix it, but several of us have agreed that there IS a problem here with how a number of apps handle (or more accurately, don't handle) their settings.

1 Like

On the surface it's not a bad concept, as a distant future wishlist item... However there are so many other issues/improvements needed on the platform right now that putting effort into further enabling developers to write bad code would to be a misdirection of valuable development resources.

2 Likes

Aside from the In Use By references of a device, what "orphaned settings related bugs" are you referring to?

Cant think of specific examples off the top of my head, but when I first got my HE last year, it was happening in either Rule Machine or Motion Lighting. There were multiple occasions when I would change a setting, and some unexpected thing would start happening, and then when I would set up the app again from scratch with the exact same user visible settings, it would be totally fine. I stopped making in place settings changes to apps altogether and started setting them up from scratch again each time I wanted to change a setting to make sure there wouldn’t be any bugs. I eventually discovered it was from orphaned settings and the app still using some even though they were no longer visible to the user, unless you go to the page that shows the app’s state and subscriptions, etc. I’ll post specifics if I come across any again, but I’ve been transitioning away from motion lighting and rule machine to apps I wrote myself, so I may not come across any again.

I had an issue about 2 months ago where I replaced 4 Sylvania lights in our home office with 4 Sengled bulbs. I added the Sengled bulbs to the hub and then deselected the Sylvania bulbs from the group and selected the Sengled. I then moved the Sylvania to another room and noticed that they still turned on and off with the office group. I rebooted the hub, but the problem remained, so I ended up deleting and rediscovering the Sylvania bulbs in order to fix the issue. It was only the group device that wouldn't let go of them. I used the Chrome browser to change the settings, and there wasn't anything that seemed unusual about it, so I was very surprised when I discovered this a couple hours later. I don't know if this has happened before, because I had always removed the lights from the hub when I was moving them somewhere else, or replacing them. I have noticed previously that if a group responded slower than expected after changing what devices were in it that deleting and recreating the group would fix the issue. I don't know if this is related or not.

Were you using Zigbee group messaging? Did you hit Done after making the changed selection?

Yes and yes.
Of course I went back in a couple more times and hit done some more and of course it didn’t make a difference (kind of like hitting the elevator button over and over). Rebooting the hub didn’t work either, so I deleted the Sylvania devices at that point and rediscovered them. I also deleted the group device and recreated it because it seemed to respond much slower than my other groups. It works very fast now, as does everything on my hub. I did recreate most of my groups after this happened, because I was worried other groups might have the same issues.

OK, deleting the devices was way overkill for an issue like this, as is removing all Groups. An issue such as this is specific to a particular Group-2.1 app instance. Don't remove the Group device, remove the app instance itself. Something got messed up in the app settings. We'd always be happy to look at something like this to see what is wrong, so perhaps we could tell what went wrong.

There was changes made to Group to make them run faster, and it is possible that those changes messed up the deselection of devices. My suggestion is to just remove a Group and create a new one, instead of editing an existing one if you are going to change out the devices in it. I will look into this issue further.

1 Like