Android Fragments Tutorial: An Introduction with Kotlin

In this Android Fragments with Kotlin tutorial you will learn the fundamental concepts of fragments while creating an app that displays dogs breeds. By Aaqib Hussain.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Data Binding

While poking around the project you may have noticed a few things:

  • A file called DataBindingAdapters.
  • A reference to dataBinding in the app module build.gradle:
    dataBinding {
      enabled = true
    }
    
  • A data section in the recycler_item_dog_model.xml layout file.
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    
      <data>
    
        <variable
          name="dogModel"
          type="com.raywenderlich.android.eldogo.DogModel"/>
     </data>
    
      ...
    </layout>
    
  • A DogModel data class.
dataBinding {
  enabled = true
}
<layout xmlns:android="http://schemas.android.com/apk/res/android">

  <data>

    <variable
      name="dogModel"
      type="com.raywenderlich.android.eldogo.DogModel"/>
 </data>

  ...
</layout>

If you haven’t used data binding before you may be like…

What is this

Let’s take a quick walkthrough.

Normally, if you want to set the value of properties in your layout, you’d use something like the following in your fragments and activities:

programmer.name = "a purr programmer"
view.findViewById<TextView>(R.id.name).setText(programmer.name)

The problem with that is that if you change the value of name for programmer, you would need to do a subsequent setText to the TextView in order to update the item. Imagine having a tool where you could bind a variable from your fragments and activities to your view and allow for changes to the variable to automatically update in the View. That is what data binding does for you.

Looking at our El Dogo app, the enabled=true in the build.gradle enables data binding in the application. Your data class contains data that you want to use in your fragment and display in your view. The data field contains variables consisting of name and type options which specify the type and name of the variable being bound.

This data is used in the view using {@} notation. For example, the following would set a text field to the value held by the name field of the dogModel variable. You can see this in the TextView with the ID name:

tools:text="@{dogModel.name}" 

Now that you have your view set up, you need to access your view and bind the variables to it. This is where the data binding magic comes in!

Whenever a view has a data field, the framework automatically generates a binding object. It creates the name of the object by converting the snake case name of the view into camel case and adding binding to the name. For example, a view called recycler_item_dog_model.xml would have a corresponding binding called RecyclerItemDogModelBinding. You can see this in DogListAdapter in DogListFragment.kt:

override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): 
  ViewHolder {
  val recyclerDogModelBinding = 
      RecyclerItemDogModelBinding.inflate(layoutInflater, viewGroup, false)
  val dog = DogModel(imageResIds[position], names[position],
          descriptions[position], urls[position])
  recyclerDogModelBinding.dogModel = dog

You can then inflate the view via the inflater method on the binding object and set properties via standard property access mechanisms.

Data binding follows a Model-View-ViewModel (MVVM) pattern. MVVM consists of three components:

  • View: The layout file.
  • Model: The data class
  • View Model/Binder: The auto-generated binding files.

For further reading on the MVVM, and other design patterns, refer to the tutorial Common Design Patterns for Android. You’ll see more on data biding later on.

Communicating With the Activity

Even though fragments attach to an activity, they don’t necessarily all talk to one another without some further encouragement from you.

For El Dogo, you’ll need DogListFragment to let MainActivity know when the user has made a selection so that DogDetailsFragment can display the selection.

To start, open DogListFragment.kt and add the following Kotlin interface at the bottom of the class:

interface OnDogSelected {
 fun onDogSelected(dogModel: DogModel)
}

This defines a listener interface for the activity to listen to the fragment. The activity will implement this interface and the fragment will invoke the onDogSelected() when the user selects an item, passing the selection to the activity.

Add this new field below the existing ones in DogListFragment:

private lateinit var listener: OnDogSelected

This field is a reference to the fragment’s listener, which will be the activity.

In onAttach(), add the following directly below super.onAttach(context):

if (context is OnDogSelected) {
  listener = context
} else {
  throw ClassCastException(
    context.toString() + " must implement OnDogSelected.")
}

This initializes the listener reference. Wait until onAttach() to ensure that the fragment actually attached itself. Then verify that the activity implements the OnDogSelected interface via is.

If it doesn’t, it throws an exception since you can’t proceed. If it does, set the activity as the listener for DogListFragment.

Note: Now you can remove the if (context != null) line in onAttach() if you like.

Okay, I lied a little: The DogListAdapter doesn’t have everything you need! In the onBindViewHolder() method in DogListAdapter, add this code to the bottom.

viewHolder.itemView.setOnClickListener { 
  listener.onDogSelected(dog) 
}

This adds a View.OnClickListener to each dog breed so that it invokes the callback on the listener, the activity, to pass along the selection.

Open MainActivity.kt and update the class definition to implement OnDogSelected:

class MainActivity : AppCompatActivity(), 
    DogListFragment.OnDogSelected {

You will get an error asking you to make MainActivity abstract or implement the abstract method onDogSelected(dogModel: DogModel). Don’t fret yet, you’ll resolve it soon.

This code specifies that MainActivity is an implementation of the OnDogSelected interface.

For now, you’ll show a toast to verify that the code works. Add the following import below the existing imports so that you can use toasts:

import android.widget.Toast

Then add the following method below onCreate():

override fun onDogSelected(dogModel: DogModel) {
    Toast.makeText(this, "Hey, you selected " + dogModel.name + "!",
        Toast.LENGTH_SHORT).show()
}

The error is gone! Build and run. Once the app launches, click one of the dog breed. You should see a toast message naming the clicked item:

El Dogo app with toast displayed

Now you’ve got the activity and its fragments talking. You’re like a master digital diplomat!

Fragment Arguments and Transactions

Currently, DogDetailsFragment displays a static Drawable and set of Strings. But what if you want it to display the user’s selection?

First, replace the entire view in fragment_dog_details.xml with:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

  <data>

    <variable
      name="dogModel"
      type="com.raywenderlich.android.eldogo.DogModel" />
  </data>

  <ScrollView xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true"
    tools:ignore="RtlHardcoded">

    <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      android:orientation="vertical">

      <TextView
        android:id="@+id/name"
        style="@style/TextAppearance.AppCompat.Title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/dog_detail_name_margin_top"
        android:layout_marginBottom="0dp"
        android:text="@{dogModel.name}" />

      <ImageView
        android:id="@+id/dog_image"
        imageResource="@{dogModel.imageResId}"
        android:layout_width="wrap_content"
        android:layout_height="@dimen/dog_detail_image_size"
        android:layout_marginTop="@dimen/dog_detail_image_margin_vertical"
        android:layout_marginBottom="@dimen/dog_detail_image_margin_vertical"
        android:adjustViewBounds="true"
        android:contentDescription="@null"
        android:scaleType="centerCrop" />

      <TextView
        android:id="@+id/description"
        style="@style/TextAppearance.AppCompat.Body1"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="@dimen/dog_detail_description_margin_left"
        android:layout_marginTop="0dp"
        android:layout_marginRight="@dimen/dog_detail_description_margin_right"
        android:layout_marginBottom="@dimen/dog_detail_description_margin_bottom"
        android:autoLink="web"
        android:text="@{dogModel.text}" />

    </LinearLayout>

  </ScrollView>
</layout>

This is almost the same as the layout was before except with data binding added. At the top you’ll see that you’ve added a variable for our DogModel. The text for name and description is bound to the variables of the same name in the DogModel object. Then, you’re using this variable to set values on the views.