Kotlin Data Binding in Fragments

If necessary see Data Binding in Activities post for other files.

import ...
import androidx.databinding.DataBindingUtil
import com.example.android.navigation.databinding.FragmentTitleBinding

class TitleFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val binding: FragmentTitleBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_title, container, false
        )
        return binding.root
    }

}

 

Kotlin Data Binding

Data binding avoids repeatedly traversing of the view hierarchy by findViewById (an expensive transaction) by generating a helper class at compile-time which the Activity/Fragment can refer to. It can also be used to bind a data class to a View so the View can access the data easily.

The idea behind data binding is to create an object that connects/maps/binds two pieces of distant information together at compile time, so that you don’t have to look for it at runtime. The object that surfaces these bindings to you is called the Binding object. You create an instance of the binding object, and then reference views through the binding object with no extra overhead.

See how you can combine this with Lifecycle components (ViewModel and LiveData) by going to this post.

In build.gradle (Module: app)

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.aboutme"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    dataBinding {
        enabled = true
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

In each layout file wrap in <layout> tag, and optionally bind to a data class if you want to:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    
    <data>
        <variable
                name="myName" // Alias for this data variable
                type="com.example.aboutme.MyName" /> // Points to the Kotlin data class
    </data>
    
    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:paddingStart="@dimen/padding"
            android:paddingEnd="@dimen/padding"
            tools:context=".MainActivity">

        <TextView
                android:id="@+id/name_text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={myName.name}" // Refers to the variable declared above, with the 'name' property specified
                android:textAlignment="center"/>

        <TextView
                android:id="@+id/nickname_text"
                style="@style/NameStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@={myName.nickname}" // Refers to the variable declared above, with the 'nickname' property specified
                android:textAlignment="center"
                android:visibility="visible"/>

    </LinearLayout>
</layout>

In Activity:

import ...
import com.example.aboutme.databinding.ActivityMainBinding // import this auto-generated class based on Activity name
import android.databinding.DataBindingUtil // This should be imported automatically
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding // Declare this class based on Activity name. A rebuild is required for this to be generated and remove error warning

    private val myName: MyName = MyName("Al", "AldeZu") // Create an instance of the data class

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main) // Initialise the binding variable with the view

        binding.myName = myName // Set the value of the myName variable used in the layout file to the data class

//        findViewById<Button>(R.id.done_button).setOnClickListener { // Don't do this any more
        binding.doneButton.setOnClickListener { // Use auto-generated view name instead of invoking costly findViewById
            addNickname(it)
        }
    }

    private fun addNickname(view: View) {
        binding.apply { // Use Kotlin's apply function to make the code easier to read when multiple views are referenced
            myName?.nickname = nicknameEdit.text.toString()
            invalidateAll() // After changing data we need to invalidate all the references to force a refresh
            nicknameEdit.visibility = View.GONE
            doneButton.visibility = View.GONE
            nicknameText.visibility = View.VISIBLE
        }

        val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm.hideSoftInputFromWindow(view.windowToken, 0)
    }
}

 

data class MyName(var name: String = "", var nickname: String = "")

 

Data Binding Library

Links the UI to actual data without having to use findViewById for every item.

In build.gradle, add 'dataBinding.enabled = true':

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"
    ...
    dataBinding.enabled = true;
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
}

Create a plain old Java object to act as a placeholder for the data:

/**
 * BoardingPassInfo is a simple POJO that contains information about, you guessed it, a boarding
 * pass! Normally, it is best practice in Java to declare member variables as private and provide
 * getters, but we are leaving these fields public for ease of use.
 */
public class BoardingPassInfo {

    public String passengerName;
    public String flightCode;
    public String originCode;
    public String destCode;

    public Timestamp boardingTime;
    public Timestamp departureTime;
    public Timestamp arrivalTime;

    public String departureTerminal;
    public String departureGate;
    public String seatNumber;

    public int barCodeImageResource;

    public long getMinutesUntilBoarding() {
        long millisUntilBoarding = boardingTime.getTime() - System.currentTimeMillis();
        return TimeUnit.MILLISECONDS.toMinutes(millisUntilBoarding);
    }
}

Add layout as the root tag to the UI (in this case, activity_main.xml file). Android will automatically create a Binding class for any layout with these tags:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<ScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.constraint.ConstraintLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:text="@string/passenger_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
            
            ...

    </android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>

In your Activity, create a Binding instance, and point the instance to the correct content view using DatabindingUtil:

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding mBinding; // ActivityMainBinding is automatically generated based on the activity_main.xml file name

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); // Passes in the Activity and sets the content view to the correct layout file
        BoardingPassInfo boardingPass = generateBoardingPassInfo(); // Whatever method to produce an instance of your placeholder class populated with data
        displayBoardingPassInfo(boardingPass); // Passes the placeholder instance to the method below
    }

    private void displayBoardingPassInfo(BoardingPassInfo info) {

        mBinding.textViewPassengerName.setText(info.passengerName);
        mBinding.textViewFlightCode.setText(info.flightCode);
        mBinding.textViewOriginAirport.setText(info.originCode);
        mBinding.textViewDestinationAirport.setText(info.destCode);
        mBinding.textViewTerminal.setText(info.departureTerminal);
        mBinding.textViewGate.setText(info.departureGate);
        mBinding.textViewSeat.setText(info.seatNumber);

        SimpleDateFormat formatter = new SimpleDateFormat("hh:mm a", Locale.getDefault());
        String boardingTime = formatter.format(info.boardingTime);
        mBinding.textViewBoardingTime.setText(boardingTime);
        String departureTime = formatter.format(info.departureTime);
        mBinding.textViewDepartureTime.setText(departureTime);
        String arrivalTime = formatter.format(info.arrivalTime);
        mBinding.textViewArrivalTime.setText(arrivalTime);

        long totalMinutesUntilBoarding = info.getMinutesUntilBoarding();
        long hoursUntilBoarding = TimeUnit.MINUTES.toHours(totalMinutesUntilBoarding);
        long minutesMinusHoursUntilBoarding = totalMinutesUntilBoarding - (hoursUntilBoarding*60);
        String hoursAndMinutesUntilBoarding = hoursUntilBoarding + ":" + minutesMinusHoursUntilBoarding;
        mBinding.textViewBoardingInCountdown.setText(hoursAndMinutesUntilBoarding);
    }
}

ud851-Exercises-student\Lesson11-Completeing-The-UI\T11.02