24

My BaseFragment:

abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewBinding> : Fragment() {

    protected abstract val viewModel: ViewModel
    private var _binding: Binding? = null
    protected val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        _binding = Binding.inflate(inflater, container, false) //This line not working
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        initialize()
        setupListeners()
        observe()
    }

    abstract fun initialize()

    abstract fun setupListeners()

    abstract fun observe()

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

But this line does not work _binding = Binding.inflate(inflater, container, false)

There is a working code but as for me this is shit coding:

abstract class BaseFragment<ViewModel : BaseViewModel, T : ViewBinding>() : Fragment() {

    private var _binding:T? = null
    protected abstract val viewModel: ViewModel
    protected val binding get() = _binding!!


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        val superclass: Type = javaClass.genericSuperclass!!
        val aClass = (superclass as ParameterizedType).actualTypeArguments[1] as Class<*>
        try {
            val method: Method = aClass.getDeclaredMethod(
                "inflate",
                LayoutInflater::class.java,
                ViewGroup::class.java,
                Boolean::class.javaPrimitiveType
            )
            _binding = method.invoke(null, layoutInflater, container, false) as T

        } catch (e: Exception) {
            e.printStackTrace()
        }
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initialize()
        setupListeners()
        observe()
    }

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

    abstract fun initialize()

    abstract fun setupListeners()

    abstract fun observe()
}

How to do it right anybody has a clean solution. I know the DataBinding has DataBindingUtil but i need for ViewBinding. Is there something similar for ViewBinding

I hope for your answers

.

EDIT

Library: https://github.com/kirich1409/ViewBindingPropertyDelegate

BaseFragment:

abstract class BaseFragment<ViewModel : BaseViewModel, Binding : ViewBinding>(
    layoutID: Int
) : Fragment(layoutID) {

    protected abstract val viewModel: ViewModel
    protected abstract val binding: Binding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        setupViews()
        setupListeners()
        setupObservers()
    }

    abstract fun setupViews()

    abstract fun setupListeners()

    abstract fun setupObservers()
}
class Fragment : BaseFragment<BaseViewModel, FragmentBinding>(
    R.layout.fragment
) {

    override val viewModel: BaseViewModel by viewModels()
    override val binding: FragmentBinding by viewBinding() // this is from library

    override fun setupViews() {
      
    }

    override fun setupListeners() {

    }

    override fun setupObservers() {
        
    }
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Alis Abenov
  • 439
  • 1
  • 4
  • 11
  • Does this answer your question? [Is there a parent class for every binding class using View Binding?](https://stackoverflow.com/questions/64411049/is-there-a-parent-class-for-every-binding-class-using-view-binding) – ADM Nov 13 '20 at 10:30
  • yes I tried it and it works but is it possible to do everything at once in the base fragment – Alis Abenov Nov 21 '20 at 20:02
  • Hi I have written a blog post completely explaining view binding and have written `baseclass for activity and fragment` checkout [Androidbites|ViewBinding](https://chetangupta.net/viewbinding/) – Chetan Gupta Dec 06 '20 at 10:42
  • 1
    Does this answer your question? [How using ViewBinding with an abstract base class](https://stackoverflow.com/questions/62407823/how-using-viewbinding-with-an-abstract-base-class) – Chetan Gupta Dec 06 '20 at 10:42
  • Thanks, but would like a cleaner solution – Alis Abenov Dec 06 '20 at 11:14
  • Aside from Chetan's comment which is a good one, there are lots of Google results/Github samples for your particular requirement. – Alvin Dizon Jan 11 '21 at 01:55
  • Just don't do this ever and you'll have better code – EpicPandaForce Dec 01 '21 at 15:07

4 Answers4

58

BaseFragment.kt

typealias Inflate<T> = (LayoutInflater, ViewGroup?, Boolean) -> T

abstract class BaseFragment<VB: ViewBinding>(
        private val inflate: Inflate<VB>
) : Fragment() {
    
    private var _binding: VB? = null
    val binding get() = _binding!!

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

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

HomeFragment.kt

class HomeFragment() : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::inflate) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.homeText.text = "Hello view binding"
    }
}
Jonny-sz
  • 581
  • 4
  • 5
  • 2
    @Jonny-sz if you have views on BaseFragment how do you access them in HomeFragment? – Edalat Feizi May 14 '21 at 04:43
  • @Edalat Feizi Sorry, but i don't understand the question. Please, share example. – Jonny-sz May 15 '21 at 04:21
  • @Jonny-sz, What if my base fragment had a container(having some text views and image views) for showing Error messages? How do we get access to this container view and its children view using this solution? – Jay Sep 07 '22 at 08:26
  • 1
    @Jay you can implement function in base fragment, eg. showError(String). And this function will do the job of showing error and this function will be available to all child classes. Additionally all child classes has access to the base class fields. – redlabrat Sep 08 '22 at 09:38
3

BaseFragment.kt

abstract class BaseFragment<T : ViewBinding>(private val bindingInflater: (layoutInflater:LayoutInflater) -> T) :
Fragment() {

    // Bindings
    private var _binding: T? = null

    /**
     * Binding
     */
    protected val binding get() = _binding

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

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


}

HomeFragment.kt

class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::inflate) {

     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding?.txtMsg!!.text = "Hello"
    }

}
2

This is my baseViewBinding.

import android.content.pm.ActivityInfo
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 java.lang.reflect.ParameterizedType

/**
 *   author:yxf
 *   time:2021/9/3
 *   BaseBindingFragment
 */
abstract class BaseBindingFragment<VB : ViewBinding> : Fragment() {
    private var _binding : VB? = null
    val binding :VB get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val type = javaClass.genericSuperclass
        val clazz = (type as ParameterizedType).actualTypeArguments[0] as Class<VB>
        val method = clazz.getMethod("inflate", LayoutInflater::class.java, ViewGroup::class.java, Boolean::class.java)
        _binding = method.invoke(null, layoutInflater, container, false) as VB
        return _binding!!.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        init()
    }

    abstract fun init()

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

}

To use

class HomeFragment : BaseBindingFragment<FragmentHomeBinding>() {
    override fun init() {
        //TODO() you can use binding in the whole Fragment
    }

}
yan sam
  • 387
  • 1
  • 7
0
abstract class BaseFragment<DB : ViewDataBinding> : Fragment(), ViewContract<DB>, BaseConstant {
    private var isRegistered = false
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (getLayoutResourceId() != 0) {
            val binding: DB = DataBindingUtil.inflate(inflater, getLayoutResourceId(), container, false)
            binding.setLifecycleOwner { lifecycle}
            onBindData(binding)
            return binding.root
        } else {
            throw IllegalArgumentException("layout resource cannot be null")
        }
    }

    override fun onResume() {
        super.onResume()
        if (!isRegistered) {
            kotlinBus.register(this)
            isRegistered = true
        }
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        onInitLiveData()
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
        initActionView()
    }

    open fun initView() {
        //TODO init view such as adapter, linearLayoutManager
    }

    open fun initActionView() {
        //TODO init action for view such as onClick
    }
    open fun onInitLiveData(){

    }
    override fun onDetach() {
        super.onDetach()
        if (isRegistered) {
            kotlinBus.unregister(this)
            isRegistered = false
        }
    }


    override fun onBindData(binding: DB) {
        //TODO binding data into layout
    }

}

simple ViewContract interface :

interface ViewContract<DB> {
    fun getLayoutResourceId(): Int
    fun onBindData(binding: DB)
}

and your fragment class could be:

class SampleFragment : BaseFragment<FragmentSampleBinding>() {
    private val viewModel by viewModel<HabitsProgressOverallViewModel>()
    @ExperimentalCoroutinesApi
    private val homeViewModel: HomeViewModel by sharedViewModel()
    override fun getLayoutResourceId(): Int {
        return R.layout.fragment_sample
    }

    override fun onBindData(binding: FragmentOverallHabitsCompletionBinding) {
        super.onBindData(binding)
        binding.viewModel = viewModel
    }
}
Dean
  • 186
  • 5