App's Child Device Won't Delete - Get Off My Lawn!

So I have an app. It creates children device(s) and then subscribes to them. I also have a way to remove child devices from within the app. Except that any code I put in place to do this always throws an error.

I’ve tried multiple iterations of code, placing them in various places, etc. It seems like the unsubscribe doesn’t happen right away, so the below code doesn’t work.

unsubscribe(getAllChildDevices().find(){it.name == deviceID})
deleteChildDevice("${app.id}/${deviceID}")

Gives:

error physicalgraph.exception.ConflictException: Device still in use. Remove from SmartApps, Dashboards, or Hello Home, then try again

I’ve also tried it like this (just code snippets, the program has more):

def updated() {
	unsubscribe()
	initialize()

	def mySwitches = getChildDevices()
	subscribe(mySwitches,"switch",processSwitches)
}
def initialize() {
  deleteChildDevice("${app.id}/${deviceID}")
}

In this case, the unsubscribe works before the delete, but the delete gives this error (I’m guessing because the actual delete doesn’t happen until the update method ends):

error org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [physicalgraph.app.EventSubscription#d0dc0486-c7b3-4fc6-842a-604addbb2c06] @ line 266

I’ve done many searches in Google and these forums and can’t find a solution anywhere. Has anyone come across this and found a way to handle the race conditions between unsubscribe and deleteChildDevice?

This is a known issue with child devices since I filed a ticket on it back in March.
Fix was never released obviously.
Might as well open another ticket with support.
I’ve tried every permutation of removing the little rodents, only solution currently being to un-install the app…
@beckje01 ??? any updates on this?

2 Likes

Try a runIn() or even a busy-wait loop for even a few seconds between steps, to see if that gives it enough time to avoid the conflict…?

Certainly worth a try for debugging, no?

1 Like

Well, I tried a busy-wait loop (gotten from this thread) but no dice, no matter the delay I still got the same errors. My guess is the busy-wait loop is blocking the thread, so the unsubscribe or delete doesn’t happen until after the loop is over.

The runIn() idea works! Though that doesn’t fire for at least a minute (which is apparently a known issue talked about in the previously linked thread). I’m passing the deviceNetworkID through a state variable to the runIn() method. The first time I tried it I got

java.util.ConcurrentModificationException

which I guess is because I am using state. instead of atomicstate. I guess I need to update my app to use atomicstate now…

Still, this is a better solution than my backup plan, which was unsubscribe, save the deviceNetworkID in a state variable, and delete the child the next time the app was updated.

One more quick deleteChildDevice question:
Whenever a child device is trying to be deleted, if it is being used by any apps (not just the parent app), it will throw an error and not delete. Is there any way to test for these associations so I can report them to a user? Right now it’s so cryptic when you can’t uninstall an app due to this issue!

In other words, is there a property or method to get the “In Use By” data that we can see for a device in the device list?

1 Like

Well… Just thinking aloud… I believe there is a “secret URI” to get this information in JSON format, but a very roundabout way is to query the user environment via the API … though managing the authentication for this is probably awkward or impossible.

https://graph.api.smartthings.com/device/show/<device_id>

Otherwise … try “getProperties()”?

It’s a many:many relationship, though (a Device can be used by many SmartApps, and each SmartApp can use many Devices…), so there’s got to be a linking object … and doubt we have access to it …?

Thinking out loud too.

If you try … catch the uninstall and trap for the inability to uninstall, you would know if it is used in another app (or otherwise uninstallable)

Not a great way, but a hack around the issue…

1 Like

SmartThings will still give a cryptic error, even though you have the catch (I’ve had my add and delete children methods both in try/catch block). Still, with this runIn() method, the app tries to delete the child a minute after the app is updated, so if it fails the user won’t notice (they’ll just have a phantom device hanging around…).

I wish apps could force delete their children, regardless of what the child has been attached to. Particularly when someone tries to uninstall an app.

Still, thank you both for the advice and help!

1 Like

This URI will return the JSON with all the user’s Installed SmartApps, which you can then read through each one’s Settings to find which ones have the Device ID you’re dealing with.

Again, no idea if the currently running SmartApps has authorization to access this page:

https://graph.api.smartthings.com/api/smartapps/installations/

{
id: "<id>",
label: "Screen Down Up Virtual Switch App",
smartAppVersion: {
id: "<app_id>",
version: 2.5,
state: "SELF_APPROVED",
name: "Screen Down Up Virtual Switch App",
...
settings: {
virtualSwitch: "34927527-8929-43af-97ed-e0fd8e32a4b2",
projector: "bc39d6e7-4e21-403f-b673-cf6b48f8cbdd"
}
1 Like

Neat, that URL returns quite the JSON file! If the apps can access it without additional auth, maybe I’ll create the uninstall method, and if it’s called check for potential offending uses of the children device(s). Then I can store that info to a state variable, and display it on the main screen for the user with an href or paragraph or something. So they’ll still get the cryptic message when they try to uninstall, but then the app will give them the information they need.

Welp, probably something for after I get the first version of the app I’m working on released. :slight_smile:

1 Like