paint-brush
Improve Your Code Quality With These Useful Kotlin Extensions for Android by@victorbrndls
255 reads

Improve Your Code Quality With These Useful Kotlin Extensions for Android

by Victor BrandaliseFebruary 2nd, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Kotlin has many amazing features but one that stands out is extensions. Extensions are used to add functionality to existing classes. They are extremely useful at helping to reduce boiler plate code. Some of them look good, but are actually disguised code smells. If there’s anything you disagree with, please leave a comment below.
featured image - Improve Your Code Quality With These Useful Kotlin Extensions for Android
Victor Brandalise HackerNoon profile picture

Kotlin has many amazing features but one that stands out is extensions. Extensions are used to add functionality to existing classes.


They are extremely useful at helping to reduce boiler plate code. The good thing is that you can add them to any class, including the ones from third-party libraries that you can’t modify.


Most of the time we see Extension functions but Kotlin also supports Extension properties.

Useful Extension Functions

Lets start with some extensions that make the code easier to read, the ones that improve the code quality over time by reducing boiler plate code.

Context

The Android API changes over time and something as simple as getting the color for a given ID can become not so simple. The extensions below helps reduce the boiler plate code associated with retrieving resources for a given ID.


fun Context.getCompatColor(@ColorRes colorId: Int) = ResourcesCompat.getColor(resources, colorId, null)

fun Context.getCompatDrawable(@DrawableRes drawableId: Int) = AppCompatResources.getDrawable(this, drawableId)!!

Usage


// Activity
getCompatColor(R.color.white)

// Fragment
requireContext().getCompatColor(R.color.white)


We all know how repeating it’s to check whether the user has granted the permissions the app requires. The extension below can help with that. It doesn’t solve the whole problem but helps reduce the code size.


fun Context.hasPermissions(vararg permissions: String) = permissions.all { permission ->
    ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
}

Usage


// Traditional way (Activity)
(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
   != PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
   != PackageManager.PERMISSION_GRANTED)

// Extension (Activity)
hasPermissions(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION)


Copying some text or URL to the clipboard should be something very simple. It only works with plain text but that’s what we use most of the time, if you need to support other formats you can change it.


fun Context.copyToClipboard(content: String) {
    val clipboardManager = ContextCompat.getSystemService(this, ClipboardManager::class.java)!!
    val clip = ClipData.newPlainText("clipboard", content)
    clipboardManager.setPrimaryClip(clip)
}


Is there an URL in your app that the user needs to open the browser to view? Are you handling the case where the browser or equivalent app might be missing? The next extension abstracts most of what’s needed. You just need to give it the Uri and do something such as showing an error if there’s no app to resolve the intent.


fun Context.isResolvable(intent: Intent) = intent.resolveActivity(packageManager) != null

fun Context.view(uri: Uri, onAppMissing: () -> Unit) {
    val intent = Intent(Intent.ACTION_VIEW, uri)

    if (!isResolvable(intent)) {
        onAppMissing()
        return
    }

    startActivity(intent)
}

Usage


// Activity
view(uri, onAppMissing = {
    // show error message
})

Date

This extension is very useful if you need to log a date object over and over or send it via an API call. Something to notice here is that the ISO format is universal, it doesn’t change from country to country, I’ll show why that’s important later.


val isoFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")

fun Date.toISOFormat() : String = isoFormatter.format(this)

Usage


Date(1620862687852).toISOFormat() // 2021-05-12 23:38:07.540


Some other extensions that might be useful


fun Date.plusDays(days: Int): Date {
    return Date(this.time + (days * ONE_DAY_MS))
}

fun Date.plusMillis(millis: Long): Date {
    return Date(this.time + millis)
}

Photo by Artur Tumasjan on Unsplash

All that glitters is not gold

Many things including extensions can be abused. Some of them look good, but are actually disguised code smells.


Let’s start with a useless extension. I was reading an article about extensions the other day and found this one.


fun Any?.isNull() = this == null

if (something.isNull()) { ... }


  1. There’s no code reduction by using the extension.
  2. By using the extension the compiler will consider it a normal function call and refrain from optimizing the null case if possible.
  3. That’s bad use of OOP, we add methods to objects to “ask” them about something, null by definition is the absence of an something. An object is never null, what’s is null is the reference to an object.

Be careful with localization

The extension below may look reasonable to most of us. It formats a Double to its decimal format.


fun Any?.isNull() = this == null

if (something.isNull()) { ... }


The dangerous thing about this code is that money is not formatted equally worldwide. In some countries you’ll see 1,200.00 (comma first, dot after), in others 1.200,00 (dot first, comma after). This is only a problem if the app is/will be used in many countries.


My solution would be to add the formatting pattern to strings.xml and create an extension on Context to handle that. That way you can define the monetary format for each language and still use the same function.


fun Context.formatPrice(price: Double) : String {
    val formatter = DecimalFormat(getString(R.string.price_formatter))
    return formatter.format(price)
}

Extension Properties

Kotlin also allows us to add properties as extensions. I’ve not found an use case for them yet but it’s useful to know that it’s possible to use them.


val <T> List<T>.lastIndex: Int
    get() = size - 1

Thank you for reading. If there’s anything you disagree with, please leave a comment below so we can discuss it and arrive at a better solution.


Cover photo by Fran Jacquier on Unsplash


Also published here.