DEV Community

loading...
Cover image for How to set up a Base Fragment Class with ViewBinding and ViewModel on Android

How to set up a Base Fragment Class with ViewBinding and ViewModel on Android

Enya Emmanuel
Hi there! I build mobile mobile apps using modern tools and software engineering best practices. I am also a youtuber and a technical writer.
・3 min read

Hey guys! This is my first article on dev.to, and I am super excited about writing again after a long while. Quickly, let's get into the subject at hand.

Building native Android apps require you have a fair knowledge of object-oriented programming. This forms the basis for this article because we will leverage the concepts of Inheritance to set up a base class. In addition to this, you’ll learn about generics, with practical examples.

One may be curious as to why we would want to add another layer of inheritance to our fragment class? This question is usually common with beginner programmers yet to realize the benefits of inheritance.

What is Inheritance in Object-Oriented Programming (OOP)?

Inheritance is a feature in which a class inherits all the features of another class. The class from which the features are inherited is known as the base class, superclass, or parent class, and the class that inherits the features is known as a derived class, subclass, or child class.

E.g. If Class D extends A, it is inheriting the features of A.

What are Generics?

Generics are simply parameterized types. The idea is to allow type (Integer, String, … etc, and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.

Enough theory, let's get started on creating a base fragment class.

STEP 1:

Create a class BaseFragment that extends a Fragment

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

    open var useSharedViewModel: Boolean = false

    protected lateinit var viewModel: ViewModel
    protected abstract fun getViewModelClass(): Class<ViewModel>

    protected lateinit var binding: VBinding
    protected abstract fun getViewBinding(): VBinding

    private val disposableContainer = CompositeDisposable()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        init()
    }

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

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

    open fun setUpViews() {}

    open fun observeView() {}

    open fun observeData() {}

    private fun init() {
        binding = getViewBinding()
        viewModel = if (useSharedViewModel) {
            ViewModelProvider(requireActivity()).get(
                getViewModelClass()
            )
        } else {
            ViewModelProvider(this).get(getViewModelClass())
        }
    }

    fun Disposable.addToContainer() = disposableContainer.add(this)

    override fun onDestroyView() {
        disposableContainer.clear()
        super.onDestroyView()
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Basically what we've done here is create a BaseFragment which accepts two types, that is a ViewBinding and a ViewModel. With this class, we move common logic and set up to one place, thereby reducing duplicate codes (boilerplates).

The ViewModel class and the ViewBinding object specified as fields in the base class will be provided by the subclass when getViewModelClass() and getViewBinding() are invoked, respectively.

open fun setUpViews() {} and open fun observeData() {} are methods with a default empty implementation. This makes it optional for its sub-classes to override. The subclasses are not forced to override these methods.

STEP 2:

With the base class set up, we'll use it by creating another fragment class that extends BaseFragment.


@AndroidEntryPoint
class UserListFragment : BaseFragment<FragmentUserListBinding, UserViewModel>() {

    override var useSharedViewModel = true

    override fun getViewModelClass() = UserViewModel::class.java

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

    override fun setUpViews() {

        // set up recycler view and bind data to UI
    }

}

Enter fullscreen mode Exit fullscreen mode

From the code block above, we have been able to achieve a simple and readable fragment class, using the concepts of inheritance and generics.

In summary,

  1. Using a base fragment helps you avoid code and pattern repetition.
  2. You achieve a clean and readable code with the concepts discussed in this article.

That is it for this article. Please share your thoughts on this subject.

Discussion (5)

Collapse
sebastianseno profile image
Sebstian Seno • Edited

Halo Enya, Nice article! But when I try your code for my base fragment, why my application crash when I try to go back to previous fragment ?

the error said 'Fragment no longer exists' did you have the same issue ?

hope you will respond my message. Thanks in advance

Collapse
matiasdelbel profile image
Mati Del Bel

Hey! Nice article! One thing, shouldn't you clear the binding instance on onDestroyView?

Collapse
enyason profile image
Enya Emmanuel Author

Yeah Mati. I did not take that into consideration because I was only trying to demonstrate the use of base fragment. However, for the sake of best practices, I will update the code accordingly. Thanks.

Collapse
msfjarvis profile image
Harsh Shandilya

Great first article Enya! Looking forward to seeing more of what you write :D

Collapse
enyason profile image
Enya Emmanuel Author

Thanks Harsh... Sure you will 😊