Overview
Hello folks, Greetings of the day.
Have you ever thought about creating a custom view and binding data with a custom view? If not, you are at the right place. Here I am going to discuss how we can create a custom view and after that, we will look at how we can bind data with our own custom view.
What is data binding in android?
Basic knowledge of data-binding:
The data is provided by a ViewModel, MVVM is a design pattern that works very well with Data Binding.
The Data Binding Library is an Android Jetpack library that allows you to bind UI components in your XML layouts to data sources in your app using a declarative format rather than programmatically, reducing boilerplate code.
Types of data binding
- One Way
- Two Way
One Way data-binding: One-way-data-binding means data flows in only one direction like updating UI from the data source.
Two Way data-binding: Two–way-data-binding means data flow in both direction like
Updating data source from UI and getting updated from a data source.
Creating our own Custom View
When you need to create some custom and reuse the views when it is not provided by the Android Ecosystem. Custom Views can be used as widgets like TextView, EditText, etc.
First of all, we need to create an xml file that represents the layout of our custom view.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools" android:background="@drawable/border" android:padding="10dp" android:orientation="horizontal" android:layout_height="wrap_content"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/iv" android:layout_width="30dp" android:scaleType="centerCrop" tools:src="@drawable/ic_launcher_foreground" tools:background="@drawable/ic_launcher_background" android:layout_gravity="top" android:layout_height="30dp"/> <androidx.appcompat.widget.AppCompatEditText android:id="@+id/edt" android:layout_width="0dp" android:layout_marginStart="5dp" tools:hint="Name" android:minHeight="100dp" android:gravity="top" android:background="@color/white" android:layout_weight="2" android:layout_height="wrap_content" tools:ignore="Autofill,LabelFor,TextFields" /> </LinearLayout>
And after that, we need to create our logic for our custom view
class CustomEditText @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : LinearLayout(context, attrs, defStyleAttr), LifecycleObserver { val edt: EditText private val iv: ImageView private var typedArray: TypedArray? = null init { val v = inflate(context, R.layout.custom_view_layout, this) orientation = HORIZONTAL edt = v.findViewById(R.id.edt) iv = v.findViewById(R.id.iv) attrs?.let { typedArray = context.theme?.obtainStyledAttributes ( attrs, R.styleable.CustomField, defStyleAttr, 0 ) } iv.setImageDrawable(typedArray?.getDrawable(R.styleable.CustomField_startDrawable)) edt.apply { hint = typedArray?.getString(R.styleable.CustomField_android_hint) } } }
OutPut:
Data binding with our custom view
Enable Data Binding in your Project:
So, let’s start with the binding process in custom view:
In order to bind data with the custom view, we need to create our own binding adapters which listen to the update of data
Data binding works with LiveData, Observable Fields and now it also supports Stateflow. In the latest release of Android Studio Arctic Fox 2020.3.1 (4.3), they had given the support of binding data with Stateflow. In order to bind data with Stateflow we need Gradle Plugin version 7.0.0-alpha04 or higher…so make sure to follow these configurations so you don’t get errors at runtime 😉
So, as we know how we can bind data with our views so this is similar to a custom view in addition we just need to create our own binding adapter and we are good to go…
Let’s start with creating a Binding Adapter with our Custom View
object CustomEditTextBinding { @JvmStatic @BindingAdapter(value = ["editTextValueAttrChanged"]) fun setListener(customField: CustomEditText, listener: InverseBindingListener?) { if (listener != null) { customField.edt.addTextChangedListener(object : TextWatcher { override fun beforeTextChanged( charSequence: CharSequence, i: Int, i1: Int, i2: Int ) {} override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} override fun afterTextChanged(editable: Editable) { listener.onChange() } }) } } @JvmStatic @BindingAdapter("editTextValue") fun setRealValue(customField: CustomEditText, value: String) { if(customField.edt.text.toString() != value) { customField.edt.setText(value) } } @JvmStatic @InverseBindingAdapter(attribute = "editTextValue") fun getRealValue(customField: CustomEditText): String { return customField.edt.text.toString() } }
There are two Binding Adapters
- @BindingAdapter(“editTextValue”)
- @InverseBindingAdapter(attribute = “editTextValue”)
@BindingAdapter(“editTextValue”):
This adapter works very well when we only want to set data from the model to view and this is only responsible for setting data value to a view. If we want to update from view state to a model we need @InverseBindingAdapter
@InverseBindingAdapter(attribute = “editTextValue”):
This adapter is responsible for updating the model from the view state like when we have a checkbox we get an update about if the checkbox is checked or not. In this blog, this adapter is responsible for updating the EditText text value to our variable in ViewModel
@BindingAdapter(value = ["editTextValueAttrChanged"]) fun setListener(customField: CustomEditText, listener: InverseBindingListener?)
And the other hand we have setListener() function which takes two parameters
1.CustmView
2.InverseBindingListener.
which is responsible for updating the value in real-time which is a kind of observer for the data bound to the XML.
ViewModel:
class MainViewModel : ViewModel() { val _liveData: MutableLiveData = MutableLiveData("") val liveData: LiveData = _liveData val _stateFlow : MutableStateFlow = MutableStateFlow("") val stateFlow: StateFlow = _stateFlow }
MainActivity:
class MainActivity : AppCompatActivity() { private val vm: MainViewModel by viewModels() private lateinit var binding: MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) .apply { lifecycleOwner = this@MainActivity vm = this@MainActivity.vm } } }
Layout File
<layout ...> <data class="MainActivityBinding"> <variable name="vm" type="com.yudiz.customviewwithbinding.viewmodel.MainViewModel" /> </data> <ScrollView ...> <LinearLayout ... android:orientation="vertical"> <com.google.android.material.textview.MaterialTextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:layout_marginTop="30dp" android:layout_marginBottom="30dp" android:text="@{`LiveData: ` + vm.liveData}" android:textSize="18sp" /> <com.yudiz.customviewwithbinding.util.CustomEditText android:id="@+id/cv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:hint="Binding With LiveData" app:editTextValue="@={vm._liveData}" app:startDrawable="@mipmap/ic_launcher" /> <com.google.android.material.textview.MaterialTextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:layout_marginTop="30dp" android:layout_marginBottom="30dp" android:text="@{`Flow: ` + vm.stateFlow}" android:textSize="18sp" /> <com.yudiz.customviewwithbinding.util.CustomEditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" android:hint="Binding With StateFlow" app:editTextValue="@={vm._stateFlow}" app:startDrawable="@mipmap/ic_launcher" /> </LinearLayout> </ScrollView> </layout>
Output:
Benefits of Custom VIew and Databinding
- When we have one view more than 2-3 times and have the same usage we can create a custom view that reduces boilerplate codes and we can wrap the same logic for view in just one file instead of writing in all files in which we are including that view.
- As we know data binding is part of MVVM (Model – View – ViewModel) it reduces boilerplate code faster development time, faster execution, and more readable and maintainable codebase.. 🙂
Conclusion
As we have seen, data binding and custom views are very interesting and powerful features for development. For more in-depth knowledge check out Android Developer documentation for data-binding.
Thanks for reading. To help others please click ❤ to recommend this article if you found it helpful. Happy Coding.