How to make BaseFragment with View Binding

Viewed 10461

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() {
        
    }
}
4 Answers

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"
    }
}

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"
    }

}

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
    }

}
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
    }
}
Related