DEV Community

Cover image for #MakeItBetter: My Everyday Kotlin Extension Functions
Tavon
Tavon

Posted on

#MakeItBetter: My Everyday Kotlin Extension Functions

This post originally appeared on my blog, Gatling.XYZ. You can check
out the original post here and maybe you'll find something else you like, too.
๐Ÿ‘จ๐Ÿพโ€๐Ÿ’ป And now, without further ado...

Once I tried Kotlin, I was hooked. There is just so much to it that I can't live without anymore, one of which being Kotlin Extension Functions, or just Extensions. If you're unfamiliar with Kotlin, Extensions are a way to add more methods (and even properties) to another class without actually modifying that class. This is useful in the case of wanting to add additional functionality to a third-party library you don't have access to. I've come up with a few that have been invaluable to me, and I use them every day.

1. View.show()


fun View.show(show: Boolean) {
    this.visibility = if (show) View.VISIBLE else View.GONE
}

Enter fullscreen mode Exit fullscreen mode

My codebase is set up with MVP and I frequently make calls to my view to show and hide progress spinners when I make API calls. In my View, I can quickly show or hide a view: progressSpinner.show(true)

Tada! I rarely use View.INVISIBLE so there's no need to attempt to handle that case; if I were to need it, I'd specifically code for that scenario when it arises.

2. Throwable.log()


fun Throwable.log() {
    if (this !is SocketTimeoutException && this !is UnknownHostException && this !is HttpException) {
        Timber.e(this, this.message)
        Crashlytics.logException(this)
    }
}

Enter fullscreen mode Exit fullscreen mode

As part of RxKotlin, when I subscribe to an API call I supply it with two consumers: one for success (this::onGetDataSuccess()) and another for error/failure (this::onGetDataFailure()). The error consumers must take a Throwable parameter, and the very first line of that function is normally the same for me. Here's a quick example of what an error consumer looks like for me:


private fun onGetDataFailure(throwable: Throwable) {
    throwable.log()
    // one two skip a few...
}

Enter fullscreen mode Exit fullscreen mode

And now my Throwable has been logged to my loggers of choice with one simple line.

3. LocalDate.toEpochMili() && LocalDateTime.toEpochMili()


fun LocalDate.toEpochMilli(zoneId: ZoneId = ZoneId.systemDefault()): Long {

    return this.atStartOfDay().atZone(zoneId).toInstant().toEpochMilli()

}

fun LocalDateTime.toEpochMilli(zoneId: ZoneId org.threeten.bp.ZoneId.systemDefault()): Long {
    return this.atZone(zoneId).toInstant().toEpochMilli()
}

Enter fullscreen mode Exit fullscreen mode

We use LocalDate and LocalDateTime in our application (the Threeten version, not the standard, but this could presumably work with that as well), and it amazed me that there wasn't a simple way to turn that object into milliseconds. Before these extensions, things were not pretty. But now? Easy peasy.

Bonus: Here is its lesser-used cousin, Long?.toLocalDateTime():


fun Long?.toLocalDateTime(zoneId: ZoneId? = ZoneId.systemDefault()): LocalDateTime? {
    if (this == null) {
        return null
    } else {
        return Instant.ofEpochMilli(this).atZone(zoneId).toLocalDateTime()
    }
}

Enter fullscreen mode Exit fullscreen mode

And to get a LocalDate from this LocalDateTime, call .toLocalDate() on the LocalDateTime object.

4. T?.orNull()


fun <T> T?.orNull() = listOf(null, this).random()

Enter fullscreen mode Exit fullscreen mode

I know what you're thinking. "Why?????" First, I'll start by saying I only use this one in my unit tests. When I'm making an object and stuffing it with random data, sometimes that data can be null in the real world. So, when I make my Fakers (just an Object class with a method that creates a "fake" version of a real object using random variables), I sometimes set up a variable to return null instead of an actual value. Here's a quick example:


object class SomeDataFaker {

    fun basic() = SomeData(
        name = Randomizer.name(),
        text = Randomizer.sentence().orNull(),
        moreData = MoreDataFaker().basic().orNull()
    )

}

Enter fullscreen mode Exit fullscreen mode

The name must not be null, but the other two can be, and my code should be able to handle any oddities should either of those come back null. If I need to handle specific cases I make sure to set the variable to have a value or null, but when I just need some data that could be anything, throwing some nulls in there gives me some peace of mind that the code will be able to handle them.

How to make your own

Making an Extension Function is easy. You can make them private for a given class, or public for your entire application to use. When I make my Extentions, I make them in an appropriately named file specific(-ish) to whatever class I'm extending. This makes it easier to find when I have to bravely go into legacy code territory that's still in Java. Here's a quick example:

# in a file called ClassImExtendingExtensions.kt

fun ClassImExtending.functionName(someParam: String) {
    // "this" refers to the instance of ClassImExtending
    // You can do all kinds of things here, as you've seen above.

    this.existingVariable = someParam
}

Enter fullscreen mode Exit fullscreen mode

Are there any custom extension functions you use regularly,? Any tips on improving mine? Let me know by leaving a comment or contacting me on Twitter @gatlingxyz.

Top comments (4)

Collapse
 
ahmedmamdouh13 profile image
Ahmed Mamdouh

I loved this tip , true i spend a lot of time writing the same long code , what is the benefit of Kotlin if it doesn't make my code easier.

Collapse
 
gatlingxyz profile image
Tavon

Exaaacttllyy! I kind of wish I had more to share, lol. I think I need to look at what pieces of code I regularly write out repetitively and see if I can turn that into an extension.

Collapse
 
ahmedmamdouh13 profile image
Ahmed Mamdouh

Yes, until you write a whole app in one line LOL!

Thread Thread
 
gatlingxyz profile image
Tavon

EVEN BETTER! lmfao