0
votes

I am attempting to create a generic extension function that can walk an Android view hierarchy and return the first occurence of a view of a specific type.

The idea would be to invoke the extension as follows (to find the first occurence of a Toolbar inside of parentView):

val someView = parentView.findFirstChildRecursive<Toolbar>()

Unfortunately the code below doesn't compile. I guess Kotlin is not happy about having recursive inline functions, but I can't use a reified type without inlining the function.

inline fun <reified T> View.findFirstChildRecursive(): T? {
  when (this) {
    is T -> return this
    is ViewGroup -> {
      for (i in 0 until childCount) {
        getChildAt(i).findFirstChildRecursive<T>()?.let { return it }
      }
    }
  }
  return null
}

I'm a little bit of a Kotlin newbie, so I was hoping someone could explain why or propose a good solution?

2
did the provided answer help?s1m0nw1
@VictorRendina did you ever find a solution to this? I am also attempting to do the same thing.portfoliobuilder
@portfoliobuilder see my answer below for one potential solutionVictor Rendina

2 Answers

1
votes

I'm gonna add a little bit to Victor Rendina's answer.

You can have two functions: one with the clazz: Class<T> parameter and the other one inline with reified generic:

inline fun <reified T : View> View.findFirstChildRecursive(): T? {
    return findFirstChildRecursive(T::class.java)
}

fun <T: View> View.findFirstChildRecursive(clazz: Class<T>): T? {
    if (this::class.java == clazz) {
        @Suppress("UNCHECKED_CAST")
        return this as T
    } else if (this is ViewGroup) {
        for (i in 0 until childCount) {
            getChildAt(i).findFirstChildRecursive(clazz)?.let { return it }
        }
    }
    return null
}
0
votes

Basically the bottom line is Kotlin doesn't allow you to inline recursive functions because it could potentially have to inline an infinite number of calls.

See this related post: Can a recursive function be inline?

The method above also can not be a tailrec function because calling itself is not the last operation in the function.

See the Kotlin function docs here: https://kotlinlang.org/docs/reference/functions.html

If you still want to achieve something similar, you can pass the class into the function.

val someView = parentView.findFirstChildRecursive(Toolbar::class.java)

fun <T: View> View.findFirstChildRecursive(clazz: Class<T>): T? {
    if (this::class.java == clazz) {
        @Suppress("UNCHECKED_CAST")
        return this as T
    } else if (this is ViewGroup) {
        for (i in 0 until childCount) {
            getChildAt(i).findFirstChildRecursive(clazz)?.let { return it }
        }
    }
    return null
}