Decoupling Binding

Miguel T
Nerd For Tech
Published in
3 min readMar 6, 2021

--

This is an article part of a series. For objectives, fundamentals, project structure, and articles summary, see Android::Simplified

Repository: https://gitlab.com/migueltt/simpleandroid

Modern Android apps are quite simple to build nowadays — that is, if you follow the Android Architecture Component guidelines. It can be simplified even more if you follow these principles:

  • An Activityor Fragment is just the glue for the Android system, the View (layouts), and ViewModels
  • Refactor all your UI related code into extension functions
  • Do not create coroutines within your Fragment — the ViewModel should be the only one launching coroutines — the main reason is that coroutines should follow the ViewModel lifecycle
  • You can define ViewModels at the Activity, navigation-graph, and Fragment level — this makes a lot easier on how data is shared and how coroutines will behave

The main objective is to have this architecture:

First, XML layouts contain all the UI design (until Jetpack Compose is finally production ready) — follow the common naming convention: fragment_test_main.xml

  • Within the XML layout, use DataBinding only if you need to reference variables — otherwise, do not use the <layout>..</layout> tag and use ViewBinding instead — just make sure your app build.gradle file specifies:
buildFeatures {
viewBinding true
dataBinding true
}
  • Remember, whenever you use <layout>..</layout> you are adding a lot of code to synchronize how data is bind into your layout — if you really don’t need it, just use ViewBinding — btw: do not use findViewById(..) or kotlin synthetic id’s.

Second, name the related Fragment using the same convention: FragmentTestMain — yeah, kind of breaking the usual standards…

Third, create a FragmentTestMainBindingExt file — this will include extension functions to decouple all UI related code from the Fragment:

  • When using DataBinding or ViewBinding a class is generated for your XML layout — it happens to follow the <xml-filename>Binding convention, and that’s why we named FragmentTestMain to our Fragment — then all your files are easy to identify
  • Within this FragmentTestMainBindingExt file, start adding extension functions related to your binding class

Fourth, within your FragmentTestMain always use the extension functions for the generated binding-class:

// Your traditional FragmentTestMain.kt file
package com.simpleandroid.modules.testui

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import com.simpleandroid.databinding.FragmentTestMainBinding

class FragmentTestMain : Fragment() {

private lateinit var binder: FragmentTestMainBinding
private val viewModel: TestViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View =
FragmentTestMainBinding.inflate(
inflater, container, false
).apply {
binder = this
}.root

override fun onViewCreated(
view: View, savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
binder.setup(this)
viewModel.sample.observe(viewLifecycleOwner) {
// do something
}
}
}
class TestViewModel : ViewModel() {
val _sample = MutableLiveData("test")
val sample: LiveData<String> = _sample
}

In another file:

// Decouple binding here: FragmentTestMainBindingExt.kt
package com.simpleandroid.modules.testui

import androidx.activity.addCallback
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.simpleandroid.R
import com.simpleandroid.databinding.FragmentTestMainBinding
import com.simpleandroid.ui.setupWithNavControllerExt

fun FragmentTestMainBinding.setup(
fragment: Fragment
) {
//reference all UI components through the binding
:
}

This decouples all your UI related code from the Fragment itself and observe LiveData from the ViewModel. That’s it, there’s nothing else to add to the Fragment — it supports configuration-changes out of the box.

There may be exceptions for some edge-cases:

  • Handling custom onBackPressed — always use onBackPressedDispatcher
  • Specific code when dealing with Toolbar (top, bottom), Drawer, FloatingActionButton — to address this, check the UX Policies series

By following these principles, migrating over Jetpack Compose will be easier since all your UI is already decoupled from the Fragment.

Now you are ready for the next step: Representing UI state using ViewModel and Repository.

--

--