Kotlin operation scopes

Or how I tried to reach the ideal “building bundle” solution

Michael Spitsin
4 min readJun 25, 2018

In the last article we’ve spoken about Bundle and how we can nicely organize the process of build them with Kotlin. But right after the post I’ve started to think “How we can sugar it (this process) even more”.

I want to warn you that this small post is just attempt to play with Kotlin’s features and build interesting pretty syntax, nothing more. So this article is about fun. It is not a guidance :)

I will remind that in the last time we came to the linked snippet of code (if changed last version a little bit, to support optional types), that allowed us to write next code of building Bundles:

val args = bundleOf(
"id" bundleTo 1,
"name" bundleTo "Michael",
"skill" bundleTo null as String?
)

Task

Our aim is to make this code is more concise, brief, short.

  1. I want to get rid of bundleTo word, since it holds 8 symbols. For example, for building Pair instance you need to use to method, which occupies only 2 symbols. We can not simple rename bundleTo method to, for example, bTo since it looks careless. I want to make this name tiny, but save readability.
  2. null as String? ? Really? Unfortunately yes, since it is trade of for using optional arguments in bundleTo function. When you want to pass explicit null arg, you will receive Overload resolution ambiguity compilation error, since compiler simply doesn’t know which optional you imply when passing null definitely.

Getting rid of casting

To get remove verbose null as String? we need to apply NullObject pattern. The point is that we need to create a uniq immutable singleton object, that will be used as one more overload method bundleTo and will indicates, that need to put null in the bundle.

object Null

infix fun String.bundleTo(value: Null) = BundlePair { it[this] = value }

operator fun
Bundle.set(key: String, value: Null) = putNull(key)

fun Bundle.putNull(key: String) = putString(key, null)

So the usage of this additional info will be:

val args = bundleOf(
"id" bundleTo 1,
"name" bundleTo "Michael",
"skill" bundleTo Null
)

As alternative you can create bundleToNull extension:

fun String.bundleToNull() = BundlePair { it.putNull(this) }

Which can give us the next result:

val args = bundleOf(
"id" bundleTo 1,
"name" bundleTo "Michael",
"skill".bundleToNull()
)

That can save you from creating additional NullObject, but suffers from not consistent view (every bundle item uses infix function, but for null the ordinary extension)

Now our snippet will look like:

Changing bundleTo method

Now I want to find an appropriate alternative for bundleTo method that will provide more declarative way of building Bundles in Android. I want to have in the end something like:

val args = bundle {
"id": 1,
"name": "Michael",
"skill": Null
}

Unfortunately with Kotlin I didn’t find the way to write exactly as aforementioned example, but still this language contains a lot of mechanism to sugar your life. One of them is operators. There is minus operator that will give us next result:

val args = bundleOf(
"id" - 1,
"name" - "Michael",
"skill" - Null
)

Definitely the code is more terse than the penultimate snippet in the previous section. But there is one bug problem.

If we reserved minus operator String extension only for bundles, we will in fact allocate abstract and general operation for only one specific reason, which is not good. Moreover, if we will want to create, for example, ContentValues building mechanism in the same way, how we will handle minus operator for them

To resolve it we need to make operator overriding not for all program, but for only specific places. Let’s call the area of operator accessibility a Scope. So, for example, we will have two building mechanisms, that uses storages of String keys and set of value types: Bundles and Content Values. Then for bundle we will have next interface:

In the same way, let’s say, we build a similar interface ContentValuesScope with bunch of String extension operators. Now if we want to build our bundle somewhere in the code, we just need implement BundleScope interface and get access to nice and concise mechanism:

class SomeClass : BundleScope {
fun openArgsFragment() {
val args = bundleOf(
"id" - 1,
"name" - "Michael",
"skill" - Null
)

//.... other code
}
}

But what if we will have both Bundles and ContentValues building in the same class. We can not implement both interfaces, because in this certain case we will have to manually choose realisation for each operator because of collisions or we will have a compilation error:

Class ‘SomeClass’ must override public open operator fun String.minus(value: Boolean): BundlePair defined in BundleScope because it inherits multiple interface methods of it

No panic. For that case we will create additional local scope methods:

object LocalBundleScope : BundleScope

inline fun bundleScope(block: LocalBundleScope.() -> Unit) =
LocalBundleScope.run(block)

inline fun <R> inBundleScope(block: LocalBundleScope.() -> R) =
LocalBundleScope.run(block)

The LocalBundleScope singleton in fact will keep operators, and both function will provide a nice access to it. Now we able to write aforementioned example with help of local scopes:

class SomeClass {
fun openArgsFragment() {
val args = inBundleScope {
bundleOf(
"id" - 1,
"name" - "Michael",
"skill" - Null
)
}

//.... other code
}
}

And with usage both of Content Values and Bundle it will look like:

class SomeClass {
fun openArgsFragment() {
val args = inBundleScope {
bundleOf(
"id" - 1,
"name" - "Michael",
"skill" - Null
)
}

val
contentValues = inContentValuesScope {
contentValuesOf(
"id" - 1,
"name" - "Michael",
"skill" - Null
)
}

//.... other code
}
}

With Scopes we will be able to use custom operators for specific aims without afraid of interleaving with other operator implementations or default operator implementation if it will be introduced some day.

Afterwords

Now with those 2 steps we’ve built nice, brief and save mechanism of building Bundles. Hooray!

--

--

Michael Spitsin
Michael Spitsin

Written by Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways

No responses yet