Dynamic Child App Pages

Is there a way to dynamically define child apps in a parent based on whether or not the apps code is actually installed?

My child apps listing is a little long in the tooth. My thoughts are to make the child apps optional in HPM and only show those that are installed.

Not a neatly packaged one but @gavincampbell posted this code (or something very close) a while back for getting installed apps.

def getAppsList() {        
	def params = [
		uri: "http://127.0.0.1:8080/app/list",
		textParser: true,
		headers: [
			Cookie: state.cookie
		]
	  ]
	
	def allAppsList = []
    def allAppNames = []
	try {
		httpGet(params) { resp ->     
			def matcherText = resp.data.text.replace("\n","").replace("\r","")
			def matcher = matcherText.findAll(/(<tr class="app-row" data-app-id="[^<>]+">.*?<\/tr>)/).each {
				def allFields = it.findAll(/(<td .*?<\/td>)/) // { match,f -> return f } 
				def id = it.find(/data-app-id="([^"]+)"/) { match,i -> return i.trim() }
				def title = allFields[0].find(/title="([^"]+)/) { match,t -> return t.trim() }
				allAppsList += [id:id,title:title]
                allAppNames << title
			}
		}
	} catch (e) {
		log.error "Error retrieving installed apps: ${e}"
        log.error(getExceptionMessageWithLine(e))
	}
    state.allAppNames = allAppNames.sort { a, b -> a.toLowerCase() <=> b.toLowerCase() }
}

Shouldn’t be too hard to point it at the apps code tab and process similarly

3 Likes

@thebearmay ...I don't know if anyone has told you lately, but you are a gem.
@gavincampbell ...thanks for coming up with this excellent approach.

This is actually looking at the apps code list. I've never really paid attention until now, but /app/list/ is the code section and /installedapp/list/ is the normal apps page.

I did make a few tweaks as the IDE was complaining about the code as posted. I also dropped the ID portions as I don't need it for my use-case.

Revised Code
def getAppsList() {        
	def params = [
		uri: "http://127.0.0.1:8080/app/list",
		textParser: true,
		headers: [
			Cookie: state.cookie
		]
	  ]
	
	def allAppNames = []
	try {
		httpGet(params) { resp ->
			def matcherText = resp.data.text.replace("\n","").replace("\r","")
			def matcher = matcherText.findAll(/(<tr class="app-row" data-app-id="[^<>]+">.*?<\/tr>)/).each {
				def allFields = it.findAll(/(<td .*?<\/td>)/) // { match,f -> return f } 
				def title = allFields[0].find(/title="([^"]+)/) { match,t -> return t.trim() }
                allAppNames.add(title)
			}
		}
	} catch (e) {
		log.error "Error retrieving installed apps: ${e}"
        log.error(getExceptionMessageWithLine(e))
	}
    state.allAppNames = allAppNames.sort()
}

Followed by a simple IF statement in the dynamic page:

if ("Sensor Groups+_CO" in state.allAppNames) {
				app(name: "coApp+", appName: "Sensor Groups+_CO", namespace: "rle.sg+", title: "Add a new CO Sensor Group+ Instance", multiple: true)
                }

With the child app installed:
image

With the child app deleted:
image

2 Likes

Going one step farther...I dumped my child apps into a variable and was able to whittle down generating the entire child app list.

        def childApps = ["Sensor Groups+_CO","Sensor Groups+_Contact","Sensor Groups+_Humidity","Sensor Groups+_Motion","Sensor Groups+_Smoke","Sensor Groups+_Switch","Sensor Groups+_Temp","Sensor Groups+_Water"]
		logDebug "Installed apps are ${state.allAppNames}"
		if(state.appInstalled == 'COMPLETE'){
			section("${app.label}") {
				paragraph "Provides options for combining multiple sensors into a single device to provide combined updates."
			}
			section("Child Apps") {
                childApps.each { it ->
                    if (it in state.allAppNames) {
                        app(name: it+"App+", appName: it, namespace: "rle.sg+", title: "Add a new ${it} Instance", multiple: true)
                    } else {
                    logDebug "${it} not installed."
				    }
                }
			}

I do it like this:

def isAppInstalled(name)
{
    def result
    
    try
    {
        def app = addChildApp("tomw", name, name)
        deleteChildApp(app.id)
        
        result = true
    }
    catch(hubitat.exception.NotFoundException e)
    {
        result = false           
    }
    finally
    {
        return result
    }
}

Is that just trying to create a child app to see if HE throws and error or not (indicating whether or not the app code is installed)?

Yes. As long as they don't change the exception they raise, this will always work. I prefer it to string parsing the HTML page.

Here's how I use it in a dynamicPage:

if(isAppInstalled("Presence Debounce"))
                {
                    app(name: "anyOpenApp", appName: "Presence Debounce", namespace: "tomw", title: "<b>Add a new Presence Debounce instance</b>", multiple: true)
                }
1 Like

This topic was automatically closed 365 days after the last reply. New replies are no longer allowed.