jeudi 2 novembre 2017

Adding new functionality with Kotlin’s extension functions

Is there a Java class that you've always felt was missing some useful functionality for Android development? With Kotlin it is possible to quickly and easily add functionality to existing classes, thanks to its extension functions. Here's how to customize Kotlin and Java classes so that they provide exactly the functionality your project requires, including closed classes that were previously impossible to modify.

What are extension functions?

Kotlin's extension functions provide you with a way of "adding" methods to a class, without having to inherit from that class or use any type of design pattern. Once you've created an extension function, you can use it just like any other regularly defined function inside that class.

Extension functions have the potential to make your code more concise, readable, and logical by trimming boilerplate code from your project. Less code also means fewer opportunities for errors. For example, you're far less likely to slip up when writing the extension function:

  toast("Hello World!")  

Compared to:

  Toast.makeText(getActivity(), "Hello World!", Toast.LENGTH_LONG).show();  

Note that even though extension functions are commonly discussed in terms of "modifying" or "adding" functionality to an existing class, they don't actually insert any new members into the class you're extending. Under the hood, extension functions are resolved statically, so when you define an extension function you're actually making a new function callable on variables of this type.

Creating an extension function

You can define extension functions anywhere in your project, although to help keep everything organized you may want to place them inside a dedicated file. This approach can also help you re-use extension functions, with this file acting as a library of helper functions to be copied and pasted across multiple projects. Throughout this article, I'll be defining all my extension functions inside an extensions.kt file.

To create an extension function, write the name of the class or the type that you want to extend (known as the receiver type), followed by the dot notation (.) and the name of the function you want to create. You can then write the function as normal.

  fun receiver-type.function-name() {  //Body of the function//  

Let's look at how you'd create an extension function that lets you create a toast in much less code. By default, you need to write the following to display a toast:

  Toast.makeText(context, text, Toast.LENGTH_SHORT).show();  

Let's move this code into an extension function, by extending Context with a 'toast' function:

  import android.content.Context  import android.widget.Toast    fun Context.toast(message: CharSequence, duration: Int = Toast.LENGTH_LONG) {     Toast.makeText(this, message, duration).show()  }  

The 'this' keyword inside the extension function body references the receiver object, which is the instance that you're calling the extension function on (i.e whatever's passed before the dot notation).

Then, simply import this extension function at the call site and you're ready to use 'toast' just like any other function:

  import android.support.v7.app.AppCompatActivity  import android.os.Bundle  import kotlinx.android.synthetic.main.activity_main.*    //Import the extension function//    import com.jessicathornsby.kotlinexample.toast    class MainActivity : AppCompatActivity() {       override fun onCreate(savedInstanceState: Bundle?) {         super.onCreate(savedInstanceState)         setContentView(R.layout.activity_main)         helloTextView.setText("Hello World")           button.setOnClickListener {            toast("Button Clicked!")         }     }  }  

Note that I'm using Kotlin Android Extensions to import references to the Button and TextView UI elements into the Kotlin source file, which is why there's no findViewByIds in the above code.

Android Studio also takes your extension functions into account when offering suggestions. Once you've defined a 'toast' function, Android Studio will suggest that you invoke the toast extension function whenever you're inside Context or an instance of Context.

You can define extension functions for any class missing functionality which you want to use in your project. For example, if you've always wished View contained 'short' and 'hide' methods, you can implement them as extension functions:

  import android.view.View  ...  ...  ...    fun View.show() { visibility = View.VISIBLE }  fun View.hide() { visibility = View.GONE }  

Another common example is creating extension functions that take the pain out of formatting large amounts of text. Here we're creating an extension function that capitalizes the first letter of every String:

  fun String.upperCaseFirstLetter(): String {     return this.substring(0, 1).toUpperCase().plus(this.substring(1))  }  

A big part of Kotlin's appeal is that it's 100 percent interoperable with Java. This makes it possible to introduce Kotlin into your existing code bases without having to immediately convert all your existing Java code to Kotlin.

To preserve compatibility with Java, all extension functions are compiled to regular static methods, with a receiver object on the first parameter.

When we created our 'toast' extension function in the extensions.kt file, the compiler created an ExtensionsKt Java class with the static method toast(). To create a name for this class, the compiler takes the corresponding Kotlin source file (extensions), capitalizes it (Extensions), and adds 'Kt.' In fact, if you place your cursor inside the toast("Button Clicked!") line of code, and then select 'Tools > Kotlin > Show Kotlin Bytecode' from the Android Studio toolbar, you'll see this static method being invoked.

You can even use this extension function in a Java class by importing it at the call site:

  import com.jessicathornsby.kotlinexample.ExtensionsKt.toast  

Member Extension Functions

We've been declaring extension functions directly under a package as top-level functions, but it's also possible to define an extension function inside the class or object where you're going to use this extension as a member extension function.

When you're only planning to use a function in a single location, it may make more sense to define your extension as a member extension function, rather than extracting it to a dedicated extensions.kt file.

When you're working with a member extension function, the receivers have different names:

  • The class you define the extension function for is referred to as the extension receiver.
  • An instance of the class where you declare the extension is called the dispatch receiver.

If there's ever a name conflict between the dispatch receiver and the extension receiver, then the compiler will always choose the extension receiver.

Extension properties

If there's one or more properties you feel are missing from a class, then you can add them by creating an extension property for that class. For example, if you regularly find yourself writing the following bit of boilerplate:

  PreferenceManager.getDefaultSharedPreferences(this)  

You can define the following extension property:

  val Context.preferences: SharedPreferences         get() = PreferenceManager         .getDefaultSharedPreferences(this)  

You can then use 'preferences' as though it's a property of Context:

  context.preferences.contains("...")  

However, since extensions don't insert members into a class, it's not possible to add an extension property with a backing field, so initializers aren't allowed for extension properties.

Before you can get the value of an extension property, you'll need to explicitly define a get() function. If you want to set the value then you'll need to define a set() function.

Companion Object Extensions

Kotlin introduces the concept of "companion object," which essentially replace Java's static members. A companion object is a singleton object that belongs to the class itself, rather than an instance of the class. It contains the variables and methods that you might want to access in a static fashion.

You create a companion object by adding the 'companion' keyword to the object declaration inside the class. For example:

  class myClass {   companion object {  ...  ...  ...   }  }  

If a class has a companion object defined, then you can add a static extension function to this class, by inserting ".Companion" between the extension type and the function name:

  Class myClass {        companion object {  }    }  fun myClass.Companion.helloWorld() {         println("Hello World!")     }  }  

Here, we're defining the extension function helloWorld on the companion object myClass.Companion. Similarly to the other extension function variants we've looked at, you're not actually modifying the class. Instead, you're adding the companion object extension to the companion object.

Once you've defined a companion object extension, you can call the extension function as though it's a regular static function defined inside the 'myClass' companion object:

  myClass.helloWorld()  

Note that you're calling this extension using class type, not class instance.

The drawback is that you can only add static extension functions to a Java or Kotlin class with the help of a companion object. This means you can only create these kinds of extensions in classes where a companion object is already explicitly defined. Although there is a open Kotlin feature request to make it possible to declare statically accessible members for Java classes.

Potential drawbacks

Extension functions can make your code more concise, readable, and less prone to errors. Like any feature, if used incorrectly, extension functions can have the opposite effect and introduce complexities and errors into your projects.

In this final section we're going to look at the most common pitfalls of working with extension functions and what you can do to avoid them.

Lay some ground rules

Despite how awkward and verbose some Java classes may feel when used in Android development, vanilla Java is understood by all Java developers. When you introduce custom extension functions into your code, it becomes more difficult for others to understand.

Confusing extension functions can be a particular problem when collaborating on a project with other developers, but even if you're working on a project solo it's still possible to get into a tangle with extension functions— especially if you get carried away and create a ton of them.

To ensure extension functions don't end up adding complexity to your code, it's important to stick to the following best practices:

  • Set some rules and make sure everyone on your team follows them! At minimum, you should establish a clear naming convention for your extension functions and decide where they should be stored. When you're collaborating on a project, it's usually easier if everyone defines their extension functions in the same location.
  • Don't repeat yourself. Creating multiple extension functions that provide identical, or even very similar functionality, but have different names is a good way to introduce inconsistencies to your code. Assuming all your extension functions are defined in the same location, you should make a point of reading through that file every time you consider adding a new extension function, just to make sure this function hasn't already been defined. This is particularly important if you're working in a team, as it's possible someone may have defined this exact extension function since the last time you checked the extensions.kt file.
  • Don't get carried away. Just because you can extend classes that have previously been locked down tight, doesn't mean that you should. Before creating an extension function, consider whether the potential benefits outweigh the time it'll take to make, as well as the potential confusion it might cause anyone else who encounters your code. Always ask yourself how often you're likely to use this extension function before implementing it. How much boilerplate code or complexity will it actually remove?
  • Consider creating a centralized resource. If your team uses extension functions across multiple projects, then it might be worth creating a resource such as a wiki, that contains the definition for every extension function your team creates. Using the same set of extension functions consistently ensures that everyone can understand the code across all your projects and move between projects with ease.

Never use the same signature as a member function

Extension functions cannot override functions that are already defined in a class. If you define a function that has the same receiver type and the same name as one that's already present in the receiver class, the compiler will ignore your extension function.

Your code will still compile, which means this could derail your project as every call to your extension function will execute the member function instead. Be careful not to define any extension functions that have the same signature as a member function.

Wrapping up

Kotlin's extension functions open up lots of possibilities for adding "missing" functionality to classes. Are there any classes that you always felt were missing some important functionality? Do you plan on using extension functions to add these features? Let us know in the comments below!



from Android Authority http://ift.tt/2zaR9CY
via IFTTT

Aucun commentaire:

Enregistrer un commentaire