Skip to content

Instantly share code, notes, and snippets.

@Bloody-Badboy
Last active January 3, 2023 18:09
Show Gist options
  • Save Bloody-Badboy/6878b5e4a1f633310e2652185b677577 to your computer and use it in GitHub Desktop.
Save Bloody-Badboy/6878b5e4a1f633310e2652185b677577 to your computer and use it in GitHub Desktop.
Inflate ViewBinding using reflection
package dev.arpan.ui.base
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
import dev.arpan.ui.utils.inflateBinding
@Suppress("MemberVisibilityCanBePrivate")
abstract class BaseFragment<out Binding : ViewBinding> : Fragment() {
private var _binding: Binding? = null
val binding: Binding
get() = _binding ?: throw IllegalStateException(
"Fragment $this binding cannot be accessed before onCreateView() or " +
"after onDestroyView() is called."
)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = inflateBinding(inflater, container, false)
return binding.root
}
}
package dev.arpan.ui.utils
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
import java.util.concurrent.ConcurrentHashMap
private val sInflateMethodByClass = ConcurrentHashMap<Class<*>, Method>()
/**
* In Proguard or R8 rules make sure to keep the bind method available:
*
* <code>
* -keep,allowoptimization class * extends androidx.viewbinding.ViewBinding {
* public static * inflate(android.view.LayoutInflater,android.view.ViewGroup,boolean);
* }
* </code>
*
*/
@Suppress("UNCHECKED_CAST")
internal fun <T : ViewBinding> Any.inflateBinding(
layoutInflater: LayoutInflater,
root: ViewGroup? = null,
attachToRoot: Boolean = false
): T {
return getInflateMethodFrom<T>(javaClass).also {
it.isAccessible = true
}.invoke(null, layoutInflater, root, attachToRoot) as T
}
@Suppress("UNCHECKED_CAST")
@Synchronized
private fun <T : ViewBinding> getInflateMethodFrom(javaClass: Class<*>): Method =
sInflateMethodByClass.getOrPut(javaClass) {
val actualTypeOfThis = getSuperclassParameterizedType(javaClass)
val viewBindingClass = actualTypeOfThis.actualTypeArguments
.filterIsInstance<Class<T>>()
.first()
viewBindingClass.getDeclaredMethod(
"inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java
) ?: error(
"The binder class ${viewBindingClass.canonicalName} should have a method bind(View)"
)
}
private fun getSuperclassParameterizedType(klass: Class<*>): ParameterizedType {
val genericSuperclass = klass.genericSuperclass
return (genericSuperclass as? ParameterizedType) ?: getSuperclassParameterizedType(
genericSuperclass as Class<*>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment