DEV Community

Adam Bennett
Adam Bennett

Posted on

7 2

Conditional statements in Builders in Kotlin

At Blockchain, we recently updated our Android app to use more robust push notifications via FCM, notifying users of BTC received to their wallets. 

As you might expect, we added a custom vibration pattern to our notifications, but this presented a slight problem. As it turns out, devices where Android ≤4.3 will throw an errant SecurityException: Requires VIBRATE permission. This is easily fixed, but how do we do this in an idiomatic way?

One suggestion was to try/catch firing the notification — this is obviously quite nasty and is a raw deal for users on old devices. The simple way would be to this would be to turn the builder into a local val and then simply amend the builder separately:

val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
// Excluded for brevity
.setContentText(text)
if (AndroidUtils.is19orHigher()) {
builder.setVibrate(longArrayOf(100))
} else {
builder.setVibrate(longArrayOf())
}

This is absolutely fine but it breaks up the Builder pattern and we can do better. What if we could put this logic in-line?

Using extension functions, we can extend NotificationCompat.Builder and create a new function that accepts a predicate, and calls either one function or another depending on the outcome. Taking advantage of function literals with receivers (ie, A.() -> A), we can ensure that the functions to be triggered are both existing public methods of NotificationCompat.Builder and return the NotificationCompat.Builder object itself.

private fun NotificationCompat.Builder.ternaryBuilder(
predicate: () -> Boolean,
trueFunc: NotificationCompat.Builder.() -> NotificationCompat.Builder,
falseFunc: NotificationCompat.Builder.() -> NotificationCompat.Builder
): NotificationCompat.Builder = if (predicate()) this.trueFunc() else this.falseFunc()

This allows us to do this:

val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
// Excluded for brevity
.ternaryBuilder(
{ AndroidUtils.is19orHigher() },
{ setVibrate(longArrayOf(100)) },
{ setVibrate(longArrayOf()) }
)
.setContentText(text)

Much cleaner, and doesn’t break the Builder flow. This ternary function seems pretty useful, can we generify it for use with any Builder class? Of course:

fun <T> T.ternaryBuilder(
predicate: () -> Boolean,
trueFunc: T.() -> T,
falseFunc: T.() -> T
): T = if (predicate()) this.trueFunc() else this.falseFunc()

So this worked great and I felt pretty good about it for about 15 minutes, and then I realised that infact, Kotlin already had me covered:

val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
// Excluded for brevity
.apply {
if (AndroidUtils.is19orHigher()) {
setVibrate(longArrayOf(100))
} else {
setVibrate(longArrayOf())
}
}
.setContentText(text)

apply is in-fact what exactly I was looking for, as this is both it’s receiver and return type. Sheepishly, I corrected my fun-but-not-idiomatic code and pushed it to origin.

Still, this was a fun learning experience that I thought I’d share. Kotlin and it’s high-order functions, including those built-in, are incredibly powerful and I love finding new applications for them.

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (1)

Collapse
 
crosdev profile image
José Martínez

Ooohh, wow. It's very useful. I'll to update my code xD.

Thanks for sharing.

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay