Home > OS >  Kotlin inflate generic viewbinding class in parent
Kotlin inflate generic viewbinding class in parent

Time:02-26

Aim is to declare a base class

abstract class BaseDialog<T : ViewBinding> : AppCompatDialogFragment() {
       lateinit var binding: T
}

and all child classes should extend this parent class

class ChildClass: BaseDialog<ChildClassViewBinding>() {
}

Then I want to inflate the binding in parent class and save it to binding property This seems out of my scope of knowledge of kotlin

Is this really possible to do?

CodePudding user response:

If I were to do this, I'd do it like this:

class BaseDialogFragment<T: ViewBinding>(private val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> T)
: AppCompatDialogFragment() {
    var _binding: T? = null
    val binding: T get() = _binding ?: error("Must only access binding while fragment is attached.")

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = bindingInflater(inflater, container, false)
        return binding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

with usage like:

class ChildClass: BaseDialog(ChildClassViewBinding::inflate) {

}

However, I would not do this in the first place (since there's a nice alternative). It can become messy pretty quickly to rely on inheritance for these kinds of things. What happens if you want to add some other features for a dependency injection framework, or some other common things you like to use? What if there's some features you like to use in some of your fragments but not all of them? And are you also creating base classes like this for Activity and non-Dialog Fragments?

These problems are why there's a programming axiom: "composition over inheritance".

Sometimes there's no choice but to use inheritance to avoid code duplication. But in the case of Fragments and Bindings, I don't think so. You can pass your layout reference to the super constructor, and use ViewBinding.bind() instead of inflate(). Since bindings rarely need to be accessed outside the onViewCreated function, you usually don't need a property for it.

class ChildClass: AppCompatDialogFragment(R.layout.child_class_view) {

    override fun onViewCreated(view: View, bundle: Bundle?) {
        super.onViewCreated(view, bundle)
        val binding = ChildClassViewBinding.bind(view)
        //...
    }

}

If you do need a property for it, @EpicPandaForce has a library that makes it a one-liner and handles the leak-avoidance on destroy for you inside a property delegate.

Library here

Usage:

class ChildClass: AppCompatDialogFragment(R.layout.child_class_view) {
    private val binding by viewBinding(ChildClassViewBinding::bind)

}

CodePudding user response:

Create Base Fragment

abstract class BaseFragment<VB : ViewBinding> : Fragment() {

    private var _bi: VB? = null
    protected val bi: VB get() = _bi!!

    abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        _bi = bindingInflater(inflater, container, false)
        return _bi!!.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _bi = null
    }
}

In your Child Fragment

class HomeFragment : BaseFragment<HomeFragmentBinding>() {
    override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> HomeFragmentBinding
        get() = HomeFragmentBinding::inflate
}
  • Related