primers

The implementation of the MVVM pattern in Android relies heavily on the implementation of Databinding in the Android Architecture Component, which makes connections between our data and our interface, We don’t have to do the tedious control assignment manually. MVVM also uses DataBinding to decouple data from the interface. It is similar to a lightweight markup language. Through the support of IDE, the interface supports the basic markup grammar operation, and converts the corresponding syntax into a number of binding implementation classes to be responsible for the binding between Model and data by compiling layout files. Thus, it can be seen that its development direction is similar to the front-end. Those who know the front end should find that this is very similar to the template language of JSP and Python in Java, but at present, its function is relatively weak, and it is not as powerful as the previous two markup languages to directly realize the mixed development of layout and code, but in the future.

  • Composition structure of MVVM
  • Use of MVVM DataBinding
  • Use of LiveData for MVVM
  • Use of MVVM Room
  • MVVM Retrofit integration with LiveData
  • Simple encapsulation of MVVM ViewModel

DataBinding

In addition to being in English, there are no shortcomings. Basically, there are corresponding operations for various ways of using DataBinding. Compared with the introduction documents on the Internet, I would like to refer to the demo case provided by Google. Look carefully you will have a sparrow is small, all the viscera of the feeling, let you feel that Google is still your uncle. Sample navigation of the related DataBinding operations provided by Google and a brief introduction to their use are provided here.

DataBindng case

An example of Google’s Databiniding is given here and the last one is an example of the MVVM pattern I used (the number of MVVM sets became DataBinding). DataBidng alone is not very beneficial as DataBinding is generally used in conjunction with the observer. Most of the following cases use Observer data or LiveData data in combination.

  • DataBindingBasicSample
  • DataBindingTwoWaySample
  • Android Architecture Blueprints (todo-mvvm-live-kotlin branch)
  • GithubBrowserSample
  • Android Sunflower
  • The MVVM case

The binding of DataBinding

DataBidng has variable binding, event binding, and adapter binding. There are one-way binding and bidirectional binding, all of which are implemented in slightly different ways.

Variable bindings

Variables are bound by the implementation of the binding object, binding by the parameters of the binding object, but also support expressions, method output, etc., as shown in the following example:

<lanyout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools>  com.example.android.databinding.basicsample.R"/ >
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
       <variable
            name="user"
            type="com.example.android.databinding.basicsample.data.ObservableFieldProfile" />
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <! -- Variable implementation of user object -->
      <TextView
                android:id="@+id/name"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="128dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="8dp"
                android:text="@{user.name}"
                android:textAppearance="@style/TextAppearance.AppCompat.Large"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/name_label"/>


    <! -- Expression implementation -->
      <ImageView  android:id="@+id/imageView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginEnd="24dp"
      android:layout_marginTop="24dp"
      android:contentDescription="@string/profile_avatar_cd"
      android:minHeight="48dp"
      android:minWidth="48dp"
      app:layout_constraintTop_toBottomOf="@+id/name"
      app:layout_constraintStart_toStartOf="parent"
      android:tint="@{user.likes > 9 ? @color/star : @android:color/black}"
      app:srcCompat="@{user.likes < 4 ? R.drawable.ic_person_black_96dp : R.drawable.ic_whatshot_black_96dp }"/>

      <! Static method implementation -->
      <ProgressBar
          android:id="@+id/progressBar"
          style="? android:attr/progressBarStyleHorizontal"
          android:layout_width="0dp"
          android:layout_height="wrap_content"
          android:layout_marginEnd="8dp"
          android:layout_marginStart="8dp"
          android:layout_marginTop="8dp"
          android:max="@ {100}"
          android:visibility="@{ConverterUtil.isZero(user.likes)}"
          app:progressScaled="@{user.likes}"
          app:layout_constraintTop_toBottomOf="@+id/imageView"
          app:layout_constraintStart_toStartOf="parent"
          tools:progressBackgroundTint="@android:color/darker_gray"/>

  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Copy the code
event

The implementation of binding mainly refers to the layout method provided by the incoming implementation (basically refers to the onClick method, of course can also be customized), there are two ways to bind events: one is lambda, the other is to ensure that andorID implementation method parameters are the same only need to pass method name, as follows:


/** * bind viewmodle */
class ProfileLiveDataViewModel : ViewModel() {
        fun onLike(a){ _likes.value = (_likes.value ? :0) + 1
        }

        fun disLike(view:View){ _unlikes.value = (_unlikes.value ? :0) + 1}}Copy the code
<lanyout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools>  com.example.android.databinding.basicsample.R"/ >
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
      <variable
            name="viewmodel"
            type="com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel"/>
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <! -- Lambda implementation -->
      <Button
        android:id="@+id/like_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginTop="16dp"
        android:onClick="@{() -> viewmodel.onLike()}"
        android:text="@string/like"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintStart_toStartOf="@+id/imageView"
        app:layout_constraintTop_toBottomOf="@+id/likes"/>


       <! -- Native style implementation -->
        <Button
        android:id="@+id/unlike_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="8dp"
        android:layout_marginTop="16dp"
        android:onClick="@{viewmodel.disLike}"
        android:text="@string/like"
        app:layout_constraintTop_toBottomOf="@id/like_button"
        app:layout_constraintStart_toStartOf="@+id/imageView"
        app:layout_constraintTop_toBottomOf="@+id/likes"/>


  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Copy the code
Adapter binding

DataBinding’s adapter binding is a bit more advanced. It’s something similar to Kotlin’s extension, except kotlin’s extension works on the concrete class that implements the code, while DataBinding’s adapter works on the properties of the View in the layout file. It can help us eliminate a lot of troublesome operations and make our code look more readable and beautiful, as follows:

Note that since the adapter uses Java static methods, annotations need to be added to each adapter method in order to accommodate Kotlin@JvmStaticMake it compatible with Java * see static use * * * on kotlin's website for details
object BindingAdapters {
    /** ** A bound adapter can be used anywhere to set ImageView's Popularity and accept value as Popularity ** /
    @BindingAdapter("app:popularityIcon")
    @JvmStatic fun popularityIcon(view: ImageView, popularity: Popularity) {

        val color = getAssociatedColor(popularity, view.context)

        ImageViewCompat.setImageTintList(view, ColorStateList.valueOf(color))

        view.setImageDrawable(getDrawablePopularity(popularity, view.context))
    }
  }

Copy the code
<lanyout  xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools>  com.example.android.databinding.basicsample.R"/ >
      <import type="com.example.android.databinding.basicsample.util.ConverterUtil"/>
      <variable
            name="viewmodel"
            type="com.example.android.databinding.basicsample.data.ProfileLiveDataViewModel"/>
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout
      android:layout_width="match_parent"
      android:layout_height="match_parent">

      <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="24dp"
            android:layout_marginTop="24dp"
            android:contentDescription="@string/profile_avatar_cd"
            android:minHeight="48dp"
            android:minWidth="48dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:popularityIcon="@{viewmodel.popularity}"/>


  </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Copy the code
Two-way binding

DataBinding itself does not support bidirectional binding, which is implemented in two ways: the Observabler interface implementation and the LiveData binding implementation. There are a few minor points to note here: in XML, @{} is used to assign values to controls, and in XML, **=@{}** is used to assign values to XML controls and change the values of corresponding variables.

  • Implementation of the Observer interface

The Observer interface is a DataBinding interface that automatically updates the USER interface when a property changes, but a variable change in the code requires that the user interface be notified to update.

/** * a viewModle base class that implements Observer functionality */
open class ObservableViewModel : ViewModel(), Observable {

    private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
        callbacks.add(callback)
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {
        callbacks.remove(callback)
    }

    /** * This method call checks all properties under Model and updates the UI */
    fun notifyChange(a) {
        callbacks.notifyCallbacks(this.0.null)}/** ** * Update modle to set the value of the control id, where ID is a variable ID generated by databinding, which is different from the control property ID@param fieldId The generated BR id for the Bindable field.
     */
    fun notifyPropertyChanged(fieldId: Int) {
        callbacks.notifyCallbacks(this, fieldId, null)}}/** * ViewModle implementation class, specific need to bind viewModle, mainly for the need to bidirectional binding need to manually remind update */
class ProfileObservableViewModel : ObservableViewModel() {
    val name = ObservableField("Ada")
    val lastName = ObservableField("Lovelace")
    val likes =  ObservableInt(0)

    fun onLike(a) {
        likes.increment()
        // Manual reminder is required to update
        notifyPropertyChanged(BR.popularity)
    }

    @Bindable
    fun getPopularity(a): Popularity {
        return likes.get().let {
            when {
                it > 9 -> Popularity.STAR
                it > 4 -> Popularity.POPULAR
                else -> Popularity.NORMAL
            }
        }
    }
}

enum class Popularity {
    NORMAL,
    POPULAR,
    STAR
}

private fun ObservableInt.increment(a) {
    set(get() + 1)}Copy the code
  • LiveData binding implementation

The implementation of LiveData is relatively easy to write. LiveData itself is an existence that is used as an Observer to listen to patterns as follows:


class ProfileLiveDataViewModel : ViewModel() {
    private val _name = MutableLiveData("Ada")
    private val _lastName = MutableLiveData("Lovelace")
    private val _likes =  MutableLiveData(0)

    val name: LiveData<String> = _name
    val lastName: LiveData<String> = _lastName
    val likes: LiveData<Int> = _likes

    // popularity is exposed as LiveData using a Transformation instead of a @Bindable property.
    val popularity: LiveData<Popularity> = Transformations.map(_likes) {
        when {
            it > 9 -> Popularity.STAR
            it > 4 -> Popularity.POPULAR
            else -> Popularity.NORMAL
        }
    }

    fun onLike(a){ _likes.value = (_likes.value ? :0) + 1}}Copy the code

In terms of the convenience of development, I recommend LiveData to implement bidirectional binding, and there are other interesting ways of LiveData waiting for us to explore. This introduction to the code similar to pseudo-code nature, does not have the penetration of the specific implementation of the use of personal or more recommended everyone can go to see my above recommended DataBinding demo code, there you want everything, it has all! Also, note that we add the setup code to the model we need:

android {

   .....
   dataBinding {
    enabled true}}Copy the code

The original link

Welcome to Enjoytoday, a blog about python, Kotlin, Java and Gradle development!