The Android arsenal – data binding


🧬 Android DataBinding Kit for notifying data changes from model levels to UI levels. This library provides base classes for DataBinding (BindingActivity, BindingFragment, BindingViewModel) and supports ways to notify data changes without observable fields and LiveData.

Use case

You can refer to the good use cases of this library in the following repositories.

  • Pokedex – 🗡️ Android Pokedex with Hilt, Motion, Coroutines, Flow, Jetpack (Raum, ViewModel, LiveData) based on MVVM architecture.
  • DisneyMotions – 🦁 A Disney app that uses transformation movements based on the MVVM architecture (ViewModel, Coroutines, LiveData, Room, Repository, Koin).
  • MarvelHeroes – ❤️ A sample application for Marvel heroes based on the MVVM architecture (ViewModel, Coroutines, LiveData, Room, Repository, Koin).
  • TheMovies2 – 🎬 A demo project with The Movie DB based on the Kotlin MVVM architecture as well as material design and animation.

Download

Gradle

Add the following codes to your code root build.gradle File (not your Module Build.gradle file).

allprojects { repositories { mavenCentral() }
}

And add a dependency code to yours module‘s build.gradle File.

dependencies { implementation "com.github.skydoves:bindables:1.0.2"
}

SNAPSHOT

Snapshots of the current development version of Bindables are available to keep track of the latest versions.

repositories { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

Set up data binding

If you are already using DataBinding You can skip this step in your project. Add below on yours build.gradle and make sure to use DataBinding in your project.

plugins { ... id 'kotlin-kapt'
}android { ... buildFeatures { dataBinding true }
}

Initialize

We should bind BR Class generated by the DataBinding process at compile time BindingManager. This is recommended for binding Application Class.

BindingManager.bind(BR::class)

Binding activity

BindingActivity is a base class for activities with which the content layout should be linked DataBindingUtil. It offers one binding Property that extends ViewDataBinding from abstract information. The binding The property is initialized sluggishly, but ensures that it is initialized before it is called super.onCreate in activities. So we don’t have to inflate layouts, set setContentView and manually initialize a binding property.

class MainActivity : BindingActivity<ActivityMainBinding>(R.layout.activity_main) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding.vm = viewModel // we can access a `binding` propety. // Base classes provide `binding` scope that has a receiver of the binding property. // So we don't need to use `with (binding) ...` block anymore. binding { lifecycleOwner = this@MainActivity adapter = PokemonAdapter() vm = viewModel } }
}

BindingFragment

The concept of BindingFragment is not much different from that BindingActivity. It takes care of that binding Property to be initialized in onCreateView.

class HomeFragment : BindingFragment<FragmentHomeBinding>(R.layout.fragment_home) { private val viewModel: MainViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { super.onCreateView(inflater, container, savedInstanceState) // we should call `super.onCreateView`. return binding { adapter = PosterAdapter() vm = viewModel }.root }
}

BindingViewModel

BindingViewModel provides a way in which the user interface can be notified of changes from the model layers.

BindingProperty

bindingProperty notifies that a certain person has changed and can be observed in UI levels. The getter for the property to use to make changes @get:Bindable.

class MainViewModel : BindingViewModel() { @get:Bindable var isLoading: Boolean by bindingProperty(false) private set // we can prevent access to the setter from outsides. @get:Bindable var toastMessage: String? by bindingProperty(null) // two-way binding. fun fetchFromNetwork() { isLoading = true // ... // }
}

In our XML layout, the changes in the property value are automatically notified to DataBinding when we change the value.

<ProgressBar android:id="@+id/progress" android:layout_width="wrap_content" android:layout_height="wrap_content" app:gone="@{!vm.loading}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />

notifyPropertyChanged

We can customize general property setters to notify data changes at UI levels using @get:Bindable Note and notifyPropertyChanged() by doing BindingViewModel.

@get:Bindablevar message: String? = null set(value) { field = value // .. do something.. // notifyPropertyChanged(::message) // notify data changes to UI layers. (DataBinding) }

Two-way binding

We can implement bidirectional binding properties using bindingProperty. Here is a representative example of bidirectional binding with TextView and EditText.

class MainViewModel : BindingViewModel() { // This is a two-way binding property because we don't set the setter as privately. @get:Bindable var editText: String? by bindingProperty(null)
}

Here is an XML layout. The text is changed whenever the viewModel.editText is changed.

<androidx.appcompat.widget.AppCompatTextView android:id="@+id/textView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{viewModel.editText}" />
<EditText android:id="@+id/editText" android:layout_width="wrap_content" android:layout_height="wrap_content" />

We can determine this in your activity or fragment viewModel.editText Worth whenever the EditTextThe entry is changed. We can implement this in other ways inversebindingadapter.

binding.editText.addTextChangedListener { vm.editText = it.toString()
}

Binding functions

We can implement bindable functions with @Bindable Note and notifyPropertyChanged() by doing BindingViewModel. And the @Bindable The name of the commented method must begin with get.

class MainViewModel : BindingViewModel() { @Bindable fun getFetchedString(): String { return usecase.getFetchedData() } fun fetchDataAndNotifyChaged() { usecase.fetchDataFromNetowrk() notifyPropertyChanged(::getFetchedString) }
}

Whenever we call notifyPropertyChanged(::getFetchedData), getFetchedString() is called and the UI layer receives the updated data.

android:text="@{viewModel.fetchedData}"

Attachment flow

We can create a mandatory property from Flow With @get:Bindable and asBindingProperty. UI levels receive newly collected data from the Flow or StateFlow on the viewModelScope. And the property of the Flow must be read-only (val), since its value can only be changed by observing the changes in Flow.

class MainViewModel : BindingViewModel() { private val stateFlow = MutableStateFlow(listOf<Poster>()) @get:Bindable val data: List<Poster> by stateFlow.asBindingProperty() @get:Bindable var isLoading: Boolean by bindingProperty(false) private set init { viewModelScope.launch { stateFlow.emit(getFetchedDataFromNetwork()) // .. // } }
}

Binding from SavedStateHandle

We can create a mandatory property from SavedStateHandle by doing BindingViewModel using using @get:Bindable and asBindingProperty(key: String). UI levels receive newly saved data from the SavedStateHandle and we can put the value into that SavedStateHandle if we just set a value for the property.

@HiltViewModelclass MainViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle
) : BindingViewModel() { @get:Bindable var savedPage: Int? by savedStateHandle.asBindingProperty("PAGE") // .. //

BindingRecyclerViewAdapter

We can create binding properties in the RecyclerView.Adapter Use of BindingRecyclerViewAdapter. In the following example the isEmpty Property can be observed in the XML layout. And we can also notify value changes to DataBinding notifyPropertyChanged.

class PosterAdapter : BindingRecyclerViewAdapter<PosterAdapter.PosterViewHolder>() { private val items = mutableListOf<Poster>() @get:Bindable val isEmpty: Boolean get() = items.isEmpty() fun addPosterList(list: List<Poster>) { items.clear() items.addAll(list) notifyDataSetChanged() notifyPropertyChanged(::isEmpty) }
}

In the following example we can do that placeholder Removed when the adapter’s item list is empty or data is being loaded.

<androidx.appcompat.widget.AppCompatTextView android:id="@+id/placeholder" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/empty" app:gone="@{!adapter.empty || viewModel.loading}" />

BindingModel

We can use binding properties in our own classes by extending those BindingModel.

class PosterUseCase : BindingModel() { @get:Bindable var message: String? by bindingProperty(null) private set init { message = getMessageFromNetwork() }
}

Do you find this library useful? ❤️

Support it by joining Star gazers for this repository. And Follow me for my next creations! 🤩

Copyright 2021 skydoves (Jaewoong Eum)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox