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.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Fragments Tutorial: An Introduction with Kotlin
30 mins
- Getting Started With Android Fragments
- Android Fragment Lifecycle
- The V4 Support Library
- Creating a Fragment
- Using a factory method
- Adding a Fragment
- Adding a Fragment Dynamically
- Data Binding
- Communicating With the Activity
- Fragment Arguments and Transactions
- Binding Adapters
- Navigating the Fragment Back Stack
- Where To Go From Here?
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 modulebuild.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…
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
.
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:
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.