2

To use viewbinding in an android app I am basically creating base classes for Activity & Fragment to remove boilerplate of everytime writing inflating code.

ACTIVITY:

BaseActivity with viewbinding:

abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = getViewBinding()
    }

    abstract fun getViewBinding(): VB

}

MainActivity:

class MainActivity : BaseActivity<ActivityMainBinding>() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        //we can directly use binding now and it works fine inside activity
        //binding.view.doSomething() 
    }

 override fun getViewBinding(): ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
}

FRAGMENTS :

BaseFragment:

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

    var binding: VB? = null

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

    abstract fun getViewBinding(view: View): VB
}

DemoFragment:

class DemoFragment : BaseFragment<DemoFragmentBinding>() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //problem is here
        binding.txtData.text="Something"
    }

    override fun getViewBinding(view: View): DemoFragmentBinding = DemoFragmentBinding.bind(view)

}

demo_fragment.xml:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.fragments.DemoFragment">

    <TextView
        android:id="@+id/txt_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Hello" />

</FrameLayout>

Problem : Unable to access views using binding inside Demofragment. I don't know why it works with activity and not with fragment.

2nd way that I don't want todo:

implementation 'androidx.fragment:fragment-ktx:1.3.1'

class DemoFragment : Fragment(R.layout.demo_fragment) {

    lateinit var binding: DemoFragmentBinding

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = DemoFragmentBinding.bind(view).apply {
            txtData.text = "Hello World"
        }
    }
}
Sumit Shukla
  • 4,116
  • 5
  • 38
  • 57

4 Answers4

6

You need to override onCreateView in BaseFragment and initialize the viewbinding

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

Then change this line

override fun getViewBinding(view: View): DemoFragmentBinding = DemoFragmentBinding.bind(view)

with

override fun getViewBinding() = DemoFragmentBinding.inflate(layoutInflater)

BaseFragment:

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

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

    abstract fun getViewBinding(): VB
}

DemoFragment:

class DemoFragment : BaseFragment<DemoFragmentBinding>() {

    override fun getViewBinding() = DemoFragmentBinding.inflate(layoutInflater)

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

        binding.apply {
            txtData.text = "Something"
        }
    }
}
Yanuar
  • 151
  • 4
2

Here is another way to implement this factory abstraction with ViewBinding. I am sharing the implementation code below. I am using genrics here. If anyone needs further explanation, I am here for that. Make sure you have enabled viewbinding feature already into the build.gradle file. Then use the following BaseFragment.kt as your fragment abstraction.

BaseFragment:

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

abstract class BaseFragment<V: ViewBinding>(
    private val inflate: Inflate<V>
    ) : Fragment() {

    private lateinit var _binding: V
    val binding get() = _binding

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

N:B: Know more about typealias.

HomeFragment:

// Implement the BaseFragment like below
class HomeFragment : BaseFragment<FragmentHomeBinding>(FragmentHomeBinding::inflate) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // usages by calling public variable 'binding' from base class
        binding.message.text = "update $value"
    }
}
Nimantha
  • 6,405
  • 6
  • 28
  • 69
A S M Sayem
  • 2,010
  • 2
  • 21
  • 28
0

I looked through the documentation, I'm not sure if this breaks what you wanted with the abstract pattern in the BaseFragment, but I tested your code with this change and it worked. Only change was in the DemoFragment:

//Add this for the onCreateView implementation
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        binding = DemoFragmentBinding.inflate(LayoutInflater.from(context), null, false)
        val view = binding!!.root
        return view
    }

Then I tested in onViewCreated:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //problem is here
        binding?.txtData?.text="Something"

    }

And it worked. The documentation I looked at was this section

I think doing this still allows you to avoid some boilerplate, but the onCreateView override is going to be necessary for every fragment, because of the different viewbinding inflation (e.g. DemoFragmentBinding.inflate etc etc)

Amin
  • 908
  • 1
  • 11
  • 23
  • I can pass ``demo_fragment.xml`` in Fragment constructor params directly using `androidx.fragment:fragment-ktx:1.2.0` without overriding onCreateView but I don't want to do that! Although thanks for the answer. – Sumit Shukla Mar 19 '21 at 04:14
  • What do you mean you can pass layout_file? – Amin Mar 19 '21 at 04:16
  • Passing the layout xml doesn't give you viewbinding though. You need to inflate specifically using the ```DemoFragmentBinding.inflate(``` method – Amin Mar 19 '21 at 04:21
0

Uses https://github.com/hoc081098/ViewBindingDelegate

abstract class BaseFragment<VB : ViewBinding>(@LayoutRes layoutId: Int) : Fragment(layoutId) {
  protected abstract val binding: VB
}


import com.hoc081098.viewbindingdelegate.*
class MainFragment: BaseFragment<FragmentMainBinding>(R.layout.fragment_main) {
  override val binding by viewBinding()
}