Kotlin Extension Functions, Android Extensions and more

0
396
Kotlin Tutorial in Android Studio
Kotlin Tutorial in Android Studio

In this Part we are going to see different Kotlin extension concepts that will help us to create our NewsFragment fragment. At the end of this story, you will learn:

  • Extension Functions (Utility class?)
  • Default Values in Parameters
  • Android Extensions (bind view)
  • Delegated Properties

1Create our NewsFragment.kt

Let’s create our NewsFragment.kt file, it will be responsible to show the latests news from the Reddit API and we are going to use a RecyclerView to show the news.

In Java we would normally create a private field to store the RecyclerView locally and assign it when we are inflating the view. Trying to do the same in Kotlin, it’ll be like this:

private var newsList: RecyclerView? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    val view = inflater.inflate(R.layout.news_fragment, container, false)
    newsList = view.findViewById(R.id.news_list) as RecyclerView?
    newsList?.setHasFixedSize(true) // use this setting to improve performance
    newsList?.layoutManager = LinearLayoutManager(context)

    return view
}

This is a valid code and works but! it’s not a “Kotlin-ized” code so we are going to make it better. With this small piece of code we are going to incorporate some new concepts about the language:

  • Extension Functions
  • Default Values in Parameters
  • Android Extensions (bind view)
  • Delegated Properties

2Extension Functions (Utility class?)

Extension functions allow us to extend the functionality of a class by adding new functions. The class doesn’t have to belongs to us (could it be a third party library) and also without requiring us to inherit the class.

This is really a super powerful feature! As we will be able to add new functions to existing classes and that’s what we are going to see right now with the ViewGroup class. Also we are going to understand how it works and how to consume it from Java.

As you know ViewGroup is a class from the Android SDK and in order to inflate it we have to do this:

inflater.inflate(R.layout.news_fragment, container, false)

But this is not really an intuitive way to do this. This should be something that the ViewGroup should be able to do like this:

val view = container?.inflate(R.layout.news_fragment)

It’s like the ViewGroup is able to inflate by itself! Awesome trick! But how to do that? Let’s create our first Extention Function:

I created a file called “Extensions.kt” in the package “commons”. Checkout the code to see it. And the code is like this:

fun ViewGroup.inflate(layoutId: Int): View {
    return LayoutInflater.from(context).inflate(layoutId, this, false)
}

What we are doing here is to add a new method to ViewGroup (see how we add the ViewGroup with a dot before the “inflate” method name) but we are not modifying the ViewGroup class but adding a new function. This function will be internally a static method but you will be calling it from an instance of a class with the dot-notation, in this case: container.inflate(…) and not ViewGroup.inflate(). This is because the compiler will be creating a Util class for us. If you want to use this Extension Function from Java you will be using it in this way:

// Java
ExtensionsKt.inflate(container, R.layout.news_fragment);
// Kotlin
container?.inflate(R.layout.news_fragment)

In the Kotlin world it’s in a more convenient way. Remember that we are adding the “?” mark just because in our example container could be a null object and this prevent us to get a NullPointerException.

The name of the Utility class will be the same as the file’s name plus “Kt” suffix or you can override it with an specific annotation:

@file:JvmName("ExtensionsUtils")

package com.droidcba.kedditbysteps.commons

import ...

fun ViewGroup.inflate(layoutId: Int): View {
    ...
}
// Use it in this way in Java:
ExtensionsUtils.inflate(container, R.layout.news_fragment);

More details about interoperability can be found here.

Coming back to our function:

fun ViewGroup.inflate(layoutId: Int): View {
    return LayoutInflater.from(context).inflate(layoutId, this, false)
}

Inside the code block it’s like you are writing code as it is a real new method of the class (like a regular method) that’s why you can access to the instance of the class with the “this” keyword and access to the local variable “context”.

Great! This is our first Extension Function and we know a lot more about this concept. Our code was updated in this way:

// old code:
val view = inflater.inflate(R.layout.news_fragment, container, false)
// replaced with:
val view = container?.inflate(R.layout.news_fragment)

But here we are losing our attachToRoot parameter that we have in the inflater inflate method. Let’s add this to our code.

3Default Values in Parameters

In Kotlin you are able to define default values in the parameters of a function (also in a class constructor but we are not going to see this now). So we are going to add a default value for the attachToRoot parameter:

fun ViewGroup.inflate(layoutId: Int, attachToRoot: Boolean = false): View {
    return LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
}

In case you don’t specify the attachToRoot parameter it will take the default value, so you can call it in different ways:

container?.inflate(R.layout.news_fragment) // default: false
container?.inflate(R.layout.news_fragment, true)

4Android Extensions

This is the Kotlin alternative for the famous “findViewById()” method (or other third party libraries to bind properties to an element of a view). Android Extensions add some convenient extension properties which allow us to access the elements of a view as it’s a property inside our Activity or Fragment with the proper view’s type already set.

Before to start changing our code let’s configure our project to enable Android Extensions. Modify the build.gradle file from our app module, apply the kotlin-android-extensions plugin and re-sync with gradle:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

That’s all! Let’s replace the findViewById with this.

NewsFragment layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/news_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

NewsFragment class:

We will be able to access the news_list RecyclerView from our NewsFragment fragment. We are going to include an import to the synthetic properties generated by the plugin:

import kotlinx.android.synthetic.main.news_fragment.*

In this way you have access to all the elements in the layout that you choose in this import and use it directly from your code. In order to make sure the layout was already inflated, we are going to move our newsList assignment from onCreateView() method to the onActivityCreated() where we are sure that the view (news_fragment) was already inflated.

We are going to be able to access news_list directly now:

// old code:
newsList = view?.findViewById(R.id.news_list) as RecyclerView?
newsList?.setHasFixedSize(true)
newsList?.layoutManager = LinearLayoutManager(context)
// new code:
news_list.setHasFixedSize(true)
news_list.layoutManager = LinearLayoutManager(context)

Notice that we use news_lists and it’s a non-nullable object so we can use it in our code without the “?” question mark. This could be a problem if you run this code in another part of the Activity lifecycle and the view was not previously inflated, this will throw an exception at runtime.

Well our code is getting better! But wait! What if we want to have news_list locally to perform some other actions like setting the adapter?

For sure, you can still use the extended property news_list but for purpose of this tutorial we are going to see Delegated Properties which will help us to do so.

5Delegated Properties

Delegated Properties are an excellent way to reuse common behavior that you may recurrently need for a property. In Kotlin you already have some common delegated properties defined in the language (also you can create your own). Here we are going to use this existing delegated property:

  • Lazy properties: The value gets computed only upon first access.

This topic is really a world so here we are going to see just the lazy property and in other parts we are going to continue introducing another concepts about Delegated Properties.

Lazy

This is a great delegated property that we are going to use to avoid initializing our newsList as a nullable object. With Lazy we are going to create it as a non-nullable property and will be executed just when you use it and just the first time.

Lazy will be initializing newsList with the value that you execute in the code block:

private val newsList: RecyclerView by lazy {
    view?.findViewById(R.id.news_list) as RecyclerView
}

Here we can use the news_list synthetic property to avoid using findViewById and as the type can be inferred by the context we can also remove the property type:

private val newsList by lazy {
    news_list
}

The lazy block will be executed when we use it, in this case in the onActivityCreated() method:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    newsList.setHasFixedSize(true) // <-- Lazy executed!
    newsList.layoutManager = LinearLayoutManager(context)
}

Also we could move this initialization code inside the onActivityCreated into the lazy block in this way:

private val newsList by lazy {
    news_list.setHasFixedSize(true)
    news_list.layoutManager = LinearLayoutManager(context)
    news_list // this will work as the return type
}

But as we are not calling the newsList in any place, the RecyclerView will raise an Exception as it doesn’t have a layout manager set. So we are going to leave it as we have it previously but we are going to change it in this way later.

6Conclusion

Well we are ready to start developing the Adapter in the next story. Now we have a new Fragment created with Kotlin 🙂

Share your thoughts

Loading Facebook Comments ...
Loading Disqus Comments ...