Send RecyclerView’s scroll events to a compose parent

We have an app that relies heavily on fragments. Every screen is an activity that hosts a fragment that hosts a recycler view:

We want to start migrating the screens to compose by keeping the screen’s content and placing it in a compose environment. This means that our activities will call setContent { } instead of setContentView() and that our top and bottom bars will be written in compose.
Also, a must have requirement is that the top bar has to react to the user’s scroll by collapsing and expanding.

Using Scaffold and AndroidFragment

Scaffold offers, among others, slots for both bars. It also provides wiring between the screens’ components so that we can easily add support for various interactions. In our case we want to leverage top bar’s enter always scroll behavior.
AndroidFragment is a composable that takes care the instantiation and hosting of a fragment.

Putting them together we start we something like this:

MaterialTheme {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(
rememberTopAppBarState()
)
Scaffold(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
title = {
Text("Top App Bar Content")
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Gray,
scrolledContainerColor = Color.Black
),
scrollBehavior = scrollBehavior
)
},
bottomBar = {
Box(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.Gray),
contentAlignment = Alignment.Center
) {
Text("Bottom Bar Content")
}
}
) { paddingValues ->
AndroidFragment<MainFragment>(
modifier = Modifier
.padding(paddingValues)
)
}
}

which renders in this:

As you can see the list renders fine but, even though we’ve setup the wiring, the top bar does not collapse/expand when we scroll.

NestedScrollConnection and NestedScrollDispatcher

Scrolling in compose is a bit different from the view systems’. When a scroll event happens, instead of just one composable handling it, multiple composables can participate and decide how much of that scroll they want to consume.

To achieve that, compose provides three components:

  1. NestedScrollConnection which is for parents to listen and consume
  2. NestedScrollDispatcher which is for children to dispatch events and
  3. the .nestedScroll() modifier which is the way to integrate these to the hierarchy

This means that if we want a parent composable to react to scroll events we need to provide a connection to it. If we want one of its the children to emit those events we need to provide to it that same connection and a dispatcher. .nestedScroll internally creates a NestedScrollNode which is used to couple the connection and the dispatcher together.

So, for our case, we have to create a dispatcher, couple it with the scroll behavior’s connection and provide it to our recycler view. Then the view will use it to dispatch its scroll events.

fun RecyclerView.setNestedScrollDispatcher()

Looking at the dispatcher’s API we can see that it provides two methods to dispatch scroll events. The first one, dispatchPreScroll, will inform the that a scroll is about to take place and that it will consume a certain distance. The second one, dispatchPostScroll, will inform that a scroll took place, it consumed a certain distance and has left some for consumption.

In the compose world all that makes sense. The scrollable modifier handles a scroll delta and communicates it properly in pre and post events.
In the view world we don’t have anything similar. We can implement the logic using gesture detectors and by intercepting touch event but we can start simpler with a OnScrollListener:

fun RecyclerView.setNestedScrollDispatcher(dispatcher: NestedScrollDispatcher) {
addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (abs(dx) > abs(dy)) return // Ignore horizontal scrolls
dispatcher.dispatchPreScroll(Offset(0f, -dy.toFloat()), UserInput)
}
})
}

which is added to the recycler view like this:

MaterialTheme {
// code…
val nestedScrollDispatcher = remember { NestedScrollDispatcher() }
Scaffold(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
// code…
) { paddingValues ->
AndroidFragment<MainFragment>(
modifier = Modifier
.padding(paddingValues)
.nestedScroll(scrollBehavior.nestedScrollConnection, nestedScrollDispatcher)
) { fragment ->
fragment.view
?.findViewById<RecyclerView>(R.id.outer_recycler_view)
?.setNestedScrollDispatcher(dispatcher = nestedScrollDispatcher)
}
}
}

Two things here:

  1. When dispatching the y delta we need to negate its sign because RecyclerView and Compose use opposite sign conventions for scroll direction.
  2. We coupled the connection with the dispatcher in the AndroidFragment because the created NestedScrollNode will try to find a parent node to dispatch its events too.

As you can see from the video the implementation works. The top bar collapses and expands accordingly. The only problem is that when the user starts scrolling slowly the top bar jiggles creating an unpleasant UX.

After adding some logs we can see that the scrolled dy is not always positive or negative through out the gesture:

NestedScrollDispatcher  gr.le0nidas.fragmentincompose        D  onScrolled: dy=3
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=1
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=6
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=-3
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=6
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=-4
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=10
NestedScrollDispatcher gr.le0nidas.fragmentincompose D onScrolled: dy=-7

The slow movement is making the framework think that we are constantly trying to move a little bit up and immediately a little bit down. This causes the jiggle since the top bar toggles constantly between collapsing and expanding.

VerticalMovementDetector

We can prevent this by determining what kind of movement we have and then ignore any deltas that are not part of that movement.

To do that we need to have a window of n deltas and then see if the majority of them is positive or negative which will mean that the user scrolls down or up respectively. After knowing that we simply ignore the deltas that we don’t want.

A couple of things that help in the UX:

  • Until we fill that window we do not dispatch anything.
  • After filling it we make sure that we keep the last n deltas. That way we can determine the movement even if the user does one continuous gesture.
private class NestedScrollDispatcherAdapter(
private val detector: VerticalMovementDetector,
private val dispatcher: NestedScrollDispatcher
) : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (abs(dx) > abs(dy)) return // Ignore horizontal scrolls
val distanceY = dy.toFloat()
when (detector.getDirection(distanceY)) {
UP -> if (distanceY > 0) return
DOWN -> if (distanceY < 0) return
null -> return
}
dispatcher.dispatchPreScroll(distanceY)
}
}
private class VerticalMovementDetector {
private val yDistances = ArrayDeque<Float>()
private var inSamplingMode = false
fun getDirection(yDistance: Float): MovementDirection? {
takeSample(yDistance)
if (isSampling()) return null
return calculateDirection()
}
fun startSampling() {
inSamplingMode = true
}
fun stopSampling() {
inSamplingMode = false
yDistances.clear()
}
private fun calculateDirection(): MovementDirection? {
val partition = yDistances.partition { it < 0 }
return if (partition.first.size > partition.second.size) UP else DOWN
}
private fun takeSample(yDistance: Float) {
if (yDistances.size >= SAMPLING_SIZE) {
yDistances.removeFirst()
}
yDistances.addLast(yDistance)
}
private fun isSampling(): Boolean = yDistances.size < SAMPLING_SIZE
}
private enum class MovementDirection {
UP,
DOWN
}

The final result looks like this:

Here is the entire code where we’ve added a sampling switch in order to keep things cleaner and avoid weird edge cases:

private const val SAMPLING_SIZE = 5
fun RecyclerView.setNestedScrollDispatcher(dispatcher: NestedScrollDispatcher) {
val detector = VerticalMovementDetector()
addOnItemTouchListener(SamplingSwitch(detector))
addOnScrollListener(NestedScrollDispatcherAdapter(detector, dispatcher))
}
private fun NestedScrollDispatcher.dispatchPreScroll(distanceY: Float) {
dispatchPreScroll(
available = Offset(0f, -distanceY),
source = UserInput
)
}
private class SamplingSwitch(private val detector: VerticalMovementDetector) :
RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(
rv: RecyclerView,
e: MotionEvent
): Boolean {
if (e.actionMasked == ACTION_UP || e.actionMasked == ACTION_CANCEL) detector.stopSampling()
if (e.actionMasked == ACTION_DOWN) detector.startSampling()
return false
}
}
private class NestedScrollDispatcherAdapter(
private val detector: VerticalMovementDetector,
private val dispatcher: NestedScrollDispatcher
) : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (abs(dx) > abs(dy)) return // Ignore horizontal scrolls
val distanceY = dy.toFloat()
when (detector.getDirection(distanceY)) {
UP -> if (distanceY > 0) return
DOWN -> if (distanceY < 0) return
null -> return
}
dispatcher.dispatchPreScroll(distanceY)
}
}
private class VerticalMovementDetector {
private val yDistances = ArrayDeque<Float>()
private var inSamplingMode = false
fun getDirection(yDistance: Float): MovementDirection? {
takeSample(yDistance)
if (isSampling()) return null
return calculateDirection()
}
fun startSampling() {
inSamplingMode = true
}
fun stopSampling() {
inSamplingMode = false
yDistances.clear()
}
private fun calculateDirection(): MovementDirection? {
val partition = yDistances.partition { it < 0 }
return if (partition.first.size > partition.second.size) UP else DOWN
}
private fun takeSample(yDistance: Float) {
if (yDistances.size >= SAMPLING_SIZE) {
yDistances.removeFirst()
}
yDistances.addLast(yDistance)
}
private fun isSampling(): Boolean = yDistances.size < SAMPLING_SIZE
}
private enum class MovementDirection {
UP,
DOWN
}

Using Ollama and Kotlin to migrate multiple files into a new library

At work, there is a need to migrate our project from using LoganSquare to kotlinx.serialization.

Part of the work involves replacing the annotations the first library is using with the ones from the second. Unfortunately some cases are not as simple as replacing foo with boo. For example, a property must be annotated with @JsonField(name = ["a_name_here"]) in LoganSquare and @SerialName("a_name_here") is kotlinx.

So, I had to decide:

  1. Do I spend 2-3 hours and migrate 100+ files manually, one by one?
  2. Do I cut the hours in half by using the search and replace tool and then fix anything the tool couldn’t manage?
  3. Do I start a journey of unknown number of hours to figure out how to perform the migration using a local LLM?

Ollama

Yeap, I chose to go with number three! And to do that I started by installing Ollama. Ollama is a tool that allows you to download open source LLMs and start playing with them locally. All prompts are handled in your device and there is no network activity.

You can either download it from its site or, if you are on macOS, use brew: brew install ollama.

After that you can run one of the models it provides, ex: ollama run llama3.2
or fire up the server it comes with and start playing with its API: ollama serve

Kotlin

The flow is simple:

  • Load in memory, one by one, the contents of the files that must be migrated
  • Provide each content along side with a prompt to an LLM
  • Store the LLM’s result to the file
  • (optional) Start dancing for building your first LLM based workflow

Reading the contents and writing them back to the files is easy with Kotlin. Communicating with the ollama server is also easy when using OkHttp and kotlinx.serialization. Believe it or not the most time consuming part was figuring out the prompt!

After a lot of attempts the one prompt that managed to produced the best result was the one where I listed the steps that I would have done manually:

We have a file written in Kotlin and we need to migrate it from LoganSquare to KotlinX Serialization.

To do that we have to replace:
- "import com.bluelinelabs.logansquare.annotation.JsonField" with "import kotlinx.serialization.SerialName"
- "import com.bluelinelabs.logansquare.annotation.JsonObject" with "import kotlinx.serialization.Serializable"
- "@JsonObject\ninternal class <class name>" with "@Serializable\ninternal class <class name>"
- "@JsonObject\nclass <class name>" with "@Serializable\nclass <class name>"
- "@JsonField(name = ["<property name>"])" with "@SerialName("<property name>")"

Everything else in the file should be copied without any changes.

Please migrate the following file:
$contents

We just want the file. Don't comment on the result.

and even then, small details did matter a lot.

For example, at the beginning of the prompt I refer to a file but later in the text I was saying Please migrate the following class. That alone was resulting in various weird migrations where a class was either missing completely or had only half of its initial code. Same results when I wasn’t using \n after the annotations.

The code

import gr.le0nidas.kotlin.ollama.OllamaClient
import gr.le0nidas.kotlin.ollama.request.GenerateRequest
import gr.le0nidas.kotlin.ollama.request.parameter.Model
import gr.le0nidas.kotlin.ollama.request.parameter.Prompt
import java.io.File
fun main(args: Array<String>) {
val ollamaClient = OllamaClient()
val requestBuilder = GenerateRequest.Builder(Model("llama3.2"))
val files = getAllFilePaths(args[0])
files.forEach { file ->
println("- Migrating file $file…")
val content = getFileContents(file)
val request = requestBuilder.build(prompt = createPrompt(content))
val response = ollamaClient.generate(request)
response
.onSuccess { saveFileContents(file, it.value) }
.onFailure { println(it.message) }
}
}
fun createPrompt(contents: String) = Prompt(
"""
We have a file written in Kotlin and we need to migrate it from LoganSquare to KotlinX Serialization.
To do that we have to replace:
– "import com.bluelinelabs.logansquare.annotation.JsonField" with "import kotlinx.serialization.SerialName"
– "import com.bluelinelabs.logansquare.annotation.JsonObject" with "import kotlinx.serialization.Serializable"
– "@JsonObject\ninternal class <class name>" with "@Serializable\ninternal class <class name>"
– "@JsonObject\nclass <class name>" with "@Serializable\nclass <class name>"
– "@JsonField(name = ["<property name>"])" with "@SerialName("<property name>")"
Everything else in the file should be copied without any changes.
Please migrate the following file:
$contents
We just want the file. Don't comment on the result.
""".trimIndent()
)
fun getAllFilePaths(directoryPath: String): List<String> {
val directory = File(directoryPath)
if (!directory.exists() || !directory.isDirectory) {
println("Directory does not exist or is not a directory")
return emptyList()
}
return directory.listFiles()
?.filter { it.isFile && it.name.endsWith(".kt") }
?.map { it.path }
?.toList()
?: emptyList()
}
fun getFileContents(filePath: String): String {
return try {
File(filePath).readText()
} catch (e: Exception) {
println("Error reading file: ${e.message}")
""
}
}
fun saveFileContents(filePath: String, contents: String) {
val file = File(filePath)
file.writeText(contents)
}

Conclusion

Was I faster than choice number two? Didn’t try this choice but I guess no. Too many things to learn, figure out and write.
Do I regret it? No! I now have a new tool in my belt and I’m pretty sure it will pay off, time wise, in the future.

ollama-kotlin-playground

One more thing that came out of this endeavour is ollama-kotlin-playground. A very simple library that does only one thing: generate a completion without even supporting all possible parameters. It is my way of not copying code from one tool/experiment to another.

Use Parceler to put your parcels on a diet

kotlin-parcelize is a great tool. Its simple to use and it helps in avoiding writing a lot of boilerplate code. There are times though that we need to take control of writing and reading to/from the parcel. One of these times is to cut down a few bytes from it (TransactionTooLargeException I am looking at you).

Meet me in the middle

@Parcelize takes full control and creates everything. Without the annotation, the developer has to do this on her own. Parceler lives in the middle of this spectrum. The plugin will create all necessary methods and classes but the actual write and read to/from the parcel will be the developer’s responsibility.

Without a Parceler the write/read looks like this:

public void writeToParcel(@NotNull Parcel parcel, int flags) {
Intrinsics.checkNotNullParameter(parcel, "parcel");
parcel.writeInt(this.id);
parcel.writeString(this.description);
parcel.writeString(this.priority.name());
parcel.writeParcelable(this.status, flags);
Attachment var10001 = this.attachment;
if (var10001 != null) {
parcel.writeInt(1);
var10001.writeToParcel(parcel, 0);
} else {
parcel.writeInt(0);
}
}
@NotNull
public final Task createFromParcel(@NotNull Parcel in) {
Intrinsics.checkNotNullParameter(in, "in");
return new Task(
in.readInt(),
in.readString(),
(Priority)Enum.valueOf(Priority.class, in.readString()),
(Status)in.readParcelable(Task.class.getClassLoader()),
in.readInt() != 0 ? (Attachment)Attachment.CREATOR.createFromParcel(in) : null
);
}

with a Parceler like this (where the Companion object is acting as a Parceler):

public void writeToParcel(@NotNull Parcel parcel, int flags) {
Intrinsics.checkNotNullParameter(parcel, "parcel");
Companion.write(this, parcel, flags);
}
@NotNull
public final Task createFromParcel(@NotNull Parcel in) {
Intrinsics.checkNotNullParameter(in, "in");
return Task.Companion.create(in);
}

Cutting down parcel’s size

The above-generated code is based on Task

@Parcelize
class Task(
val id: Int,
val description: Description,
val priority: Priority = Normal,
val status: Status = NotStarted,
val attachment: Attachment? = null
) : Parcelable
@Parcelize
class Attachment(val path: String) : Parcelable
@Parcelize
@JvmInline
value class Description(val value: String) : Parcelable
enum class Priority {
Low,
Normal,
High
}
sealed class Status : Parcelable {
@Parcelize
object NotStarted : Status()
@Parcelize
object InProgress : Status()
@Parcelize
class Completed(val completedAt: LocalDate) : Status()
}

which, creates a parcel of 248 bytes. The code does not do anything weird. All primitives, which include the value classes too, are well handled. So nothing to do here. This leaves parcelables and enums.

But first, let’s use a Parceler. This means that writing and reading to/from the parcel has to be implemented by us. For starters, we will do exactly what the generated code does except for the attachment property. For that, the generated code uses parcelable’s methods and CREATOR. In the Parceler we don’t have access to the CREATOR.

companion object : Parceler<Task> {
override fun create(parcel: Parcel): Task {
return Task(
parcel.readInt(),
Description(parcel.readString()!!),
Priority.valueOf(parcel.readString()!!),
parcel.readParcelable(Status::class.java.classLoader)!!,
parcel.readParcelable(Attachment::class.java.classLoader)
)
}
override fun Task.write(parcel: Parcel, flags: Int) {
with(parcel) {
writeInt(id)
writeString(description.value)
writeString(priority.name)
writeParcelable(status, flags)
writeParcelable(attachment, flags)
}
}
}

That leaves us with writeParcelable and readParcelable but now the parcel’s size is bigger, it is 328 bytes! Turns out that writeParcelable first writes the parcelable’s name and then the parcelable itself!

We need to use the CREATOR. After searching around I found parcelableCreator. A function that solved a well-known problem and will be added to Kotlin 1.6.20.

inline fun <reified T : Parcelable> Parcel.readParcelable(): T? {
val exists = readInt() == 1
if (!exists) return null
return parcelableCreator<T>().createFromParcel(this)
}
@Suppress("UNCHECKED_CAST")
inline fun <reified T : Parcelable> parcelableCreator(): Parcelable.Creator<T> =
T::class.java.getDeclaredField("CREATOR").get(null) as? Parcelable.Creator<T>
?: throw IllegalArgumentException("Could not access CREATOR field in class ${T::class.simpleName}")
fun <T : Parcelable> Parcel.writeParcelable(t: T?) {
if (t == null) {
writeInt(0)
} else {
writeInt(1)
t.writeToParcel(this, 0)
}
}

This allows us to revert the size increment back to 248 bytes

companion object : Parceler<Task> {
override fun create(parcel: Parcel): Task {
return Task(
//…
parcel.readParcelable()
)
}
override fun Task.write(parcel: Parcel, flags: Int) {
with(parcel) {
//…
writeParcelable(attachment)
}
}
}

Use enum’s ordinal than its name. The generated code writes enum’s name so that it can use Enum.valueOf when reading. We can write an int instead by using enum’s ordinal

companion object : Parceler<Task> {
override fun create(parcel: Parcel): Task {
return Task(
//…
parcel.readEnum()
)
}
override fun Task.write(parcel: Parcel, flags: Int) {
with(parcel) {
//…
writeEnum(priority)
}
}
}
inline fun <reified T : Enum<T>> Parcel.readEnum(): T {
return enumValues<T>()[readInt()]
}
inline fun <reified T : Enum<T>> Parcel.writeEnum(t: T) {
writeInt(t.ordinal)
}

and use Enum.values() when reading. This drops the parcel’s size to 232 bytes.

Skip a class’s parcelable implementation. This of course depends on each implementation.
For instance, Status is a sealed class that only one of its children has a construction parameter. We can leverage this by writing only that value

companion object : Parceler<Task> {
override fun create(parcel: Parcel): Task {
return Task(
//…
parcel.readStatus()
)
}
override fun Task.write(parcel: Parcel, flags: Int) {
with(parcel) {
//…
writeStatus(status)
}
}
}
fun Parcel.readStatus(): Status {
return readLong().let { value ->
when (value) {
0L -> NotStarted
1L -> InProgress
else -> Completed(LocalDate.ofEpochDay(value))
}
}
}
fun Parcel.writeStatus(status: Status) {
when (status) {
is Completed -> writeLong(status.completedAt.toEpochDay())
InProgress -> writeLong(1)
NotStarted -> writeLong(0)
}
}

this drops the parcel’s size to 136 bytes!

Conclusion

Fortunately, the generated code does a pretty good job and making any optimizations is not that common. But when needed Parceler and parcelableCreator are great tools.

PS: for measuring the parcel’s size I was using this method

fun Parcelable.sizeInBytes(): Int {
val parcel = Parcel.obtain()
try {
parcel.writeParcelable(this, 0)
return parcel.dataSize()
} finally {
parcel.recycle()
}
}

which was shamelessly stolen from Guardian’s TooLargeTool.

I think that data classes help in violating the YAGNI principle

Let me ask you something. You are the reviewer in a PR that creates a simple calculator which needs to know how to add numbers. Just that. There are no reasons to make us think that the calculator will need more functionality.

Despite that the PR includes a calculator that can add, subtract and multiply. What do you do as a reviewer?

I want to believe that you will, respectfully, discuss the removal of the extra functionality otherwise the calculator will violate the YAGNI principle and add (a) more code for the developers to maintain and (b) more ways to couple the project with the calculator. And all that with no immediate benefit.

Do we need all that functionality?

The same goes with data classes. There is no reason to have a class that can be uniquely identified by all of its properties if we don’t use its instances this way. There is no reason to have an extra getter for every property if we don’t use, extensively, the destructuring declaration. There is no reason to have a copy mechanism if we never copy instances!

If it is there it is going to be used

I recently removed the data keyword from one of our oldest classes and I noticed that many of our newest tests started to fail in compilation. The compiler could not find the copy method which was used to create dummy values from other dummy values by changing one property per test.

When to create a data class?

Here is my thought process when trying to decide the type of class I’ll use:

Q: Is this class anything but a domain entity or value object?

A: Then a simple class is just fine.

Q: Is this class a domain entity? Meaning that it can be uniquely identified by a subset of its properties (ex: an id)?

A: Then a simple class with an implementation of equals/hashCode will be enough.

Q: Is this a value object? Meaning that it can be uniquely identified by all of its properties?

A: Yes.

Q: How many properties?

A: One. Then a value class is a must.

Q: Are you sure its just one?

A: Turns out its more! We’ll use a data class.

Don’t do it for the test

Our test code is the first consumer of our production code. Changing the production code, in this case change/create a class as data, to write more quickly a couple of tests will result in having tests that can easily break every time the production code changes since the tests know too much about the code’s internals and not its behavior.

Create a seam for testing using default values and function references

I learned about seams after reading Micheal Feathers’s book Working Effectively with Legacy Code. In essence a seam is a way to circumvent code that makes testing hard or even impossible.

For example, lets say we have a class that checks if a given task is valid. For reasons that do not interest us that same class makes a connection to another service and sends some data to it. That connection alone makes the class hard to test since we need to have and maintain a connection to that service during testing:

class TaskChecker {
fun check(task: Task): CheckResult {
if (isNotCreatedInCurrentWeek(task)) return Invalid
if (isResolved(task)) return Invalid
if (isNotAssigned(task)) return Invalid
return Valid
}
private fun isNotAssigned(task: Task): Boolean {
if (task.assignedTo != Nobody) return false
val connection = Connection()
val assigner = TaskAssigner(connection)
assigner.add(task)
return true
}
//…
}

In this example, isNotAssigned() makes the necessary checks but also sends the task to TaskAssigner so if we want to write some tests for TaskChecker we need to make sure that assigner is up and running.

Object seams

According to Mr Feathers there are three types of seams. The one that fits our case is called object seam and we are going to use it in order to bypass entirely making a connection and talking to the assigner.

Following the book’s example we end up with this:

class TestingTaskChecker : TaskChecker() {
override fun sendTaskToAssigner(task: Task) {
// do nothing
}
}
open class TaskChecker {
fun check(task: Task): CheckResult {
if (isNotCreatedInCurrentWeek(task)) return Invalid
if (isResolved(task)) return Invalid
if (isNotAssigned(task)) return Invalid
return Valid
}
private fun isNotAssigned(task: Task): Boolean {
if (task.assignedTo != Nobody) return false
sendTaskToAssigner(task)
return true
}
protected open fun sendTaskToAssigner(task: Task) {
val connection = Connection()
val assigner = TaskAssigner(connection)
assigner.add(task)
}
//…
}

which does exactly what we want since it provides a way to write tests that do not involve the assigner. We just need to use TestingTaskChecker in our tests and we are good to go.

The downside with this approach is that we had to open our class which might not meet the project’s standards.

Function reference

Lets see what we can do without opening the class.

Just like before we need to extract the behavior that we want to override to its own method but this time we are also going to assign this method to a value and use the value in the calling site:

class TaskChecker {
private val safeSendTaskToAssigner: (Task) -> Unit = ::sendTaskToAssigner
fun check(task: Task): CheckResult {
if (isNotCreatedInCurrentWeek(task)) return Invalid
if (isResolved(task)) return Invalid
if (isNotAssigned(task)) return Invalid
return Valid
}
private fun isNotAssigned(task: Task): Boolean {
if (task.assignedTo != Nobody) return false
safeSendTaskToAssigner(task)
return true
}
private fun sendTaskToAssigner(task: Task) {
val connection = Connection()
val assigner = TaskAssigner(connection)
assigner.add(task)
}
//…
}

isNotAssigned() will keep talking with the assigner only this time it does it through safeSendTaskToAssigner.

Default value

Having this function reference means that we can force isNotAssigned() to change its behavior by simply assigning a new value to safeSendTaskToAssigner! And this is what we are going to do:

class TaskChecker(
seamToAssigner: ((Task) -> Unit)? = null
) {
private val safeSendTaskToAssigner: (Task) -> Unit = seamToAssigner ?: ::sendTaskToAssigner
fun check(task: Task): CheckResult {
if (isNotCreatedInCurrentWeek(task)) return Invalid
if (isResolved(task)) return Invalid
if (isNotAssigned(task)) return Invalid
return Valid
}
private fun isNotAssigned(task: Task): Boolean {
if (task.assignedTo != Nobody) return false
safeSendTaskToAssigner(task)
return true
}
private fun sendTaskToAssigner(task: Task) {
val connection = Connection()
val assigner = TaskAssigner(connection)
assigner.add(task)
}
//…
}

By default the seam is null which leads in having safeSendTaskToAssigner referencing the original behavior allowing the entire project to keep working as before without any additional changes to other files.

If now we pass a non null value then it gets assigned to safeSendTaskToAssigner and ends up being called instead of sendTaskToAssigner. This way we remove the communication from our flow allowing us to finally write some tests.

Testing

All we need to do is to write our tests by simply creating a checker with a do nothing seam:

@Test fun `a task that is not assigned is invalid`() {
val task = Task(AssignedTo.Nobody)
val taskChecker = TaskChecker {} // <– check with seam
val actual = taskChecker.check(task)
assertThat(actual, equalTo(Invalid))
}

Use suspendCoroutine to connect callbacks and coroutines

Whenever we need to write asynchronous code we tend to use callbacks which allow us to trigger an action and, instead of waiting for it to finish, get notified through the callback for the action’s completion. Coroutines change that and help us write asynchronous code but in a sequential way.

This means that instead of writing code like this:

fun main() {
downloadTasks(
object : DownloadCallback {
override fun onDownloaded(tasks: List<Task>) {
printTasks(tasks)
}
}
)
}
interface DownloadCallback {
fun onDownloaded(tasks: List<Task>)
}
private fun downloadTasks(callback: DownloadCallback) {
println("Downloading…")
// code that makes a network call and returns the list of tasks
callback.onDownloaded(listOf(Task(1), Task(2), Task(3)))
}
private fun printTasks(tasks: List<Task>) {
tasks.forEach { task -> println(task.id) }
}

we can write it like this:

fun main(): Unit = runBlocking {
launch {
val tasks = downloadTasks()
printTasks(tasks)
}
}
private suspend fun downloadTasks() {
println("Downloading…")
// code that makes a network call and returns the list of tasks
return listOf(Task(1), Task(2), Task(3))
}
private fun printTasks(tasks: List<Task>) {
tasks.forEach { task -> println(task.id) }
}

But what do we do when there is no easy way to remove callbacks from existing code or when we use a third party library that is not coroutines ready? This is where suspendCoroutine comes to save the day.

suspendCoroutine

suspendCoroutine is a function that does exactly what is says. It suspends the coroutine that it was called from and provides a way to resume it.

Lets have an example. The code here:

fun main(): Unit = runBlocking {
launch {
print("1 ")
print("2 ")
print("3 ")
print("4 ")
println("Done!")
}
}

simply prints 1 2 3 4 Done!. If we change it to:

fun main(): Unit = runBlocking {
launch {
print("1 ")
print("2 ")
print("3 ")
suspendCoroutine<Unit> { }
print("4 ")
println("Done!")
}
}

it will print 1 2 3 and then it will just wait. We suspended the coroutine but we did not resume it. To do so we will use the continuation instance that suspendCoroutine provides:

fun main(): Unit = runBlocking {
launch {
print("1 ")
print("2 ")
print("3 ")
suspendCoroutine<Unit> { continuation ->
print("… ")
continuation.resume(Unit)
}
print("4 ")
println("Done!")
}
}

now it prints 1 2 3 … 4 Done!. The coroutine printed the first three numbers, got suspended, while being suspended another block of code got executed and printed the dots and then resumed the coroutine allowing it to print the final number and done.

Continuation adapter

Back to our first example. Lets say that downloadTasks cannot be changed. We still need to call it and provide a callback for its results.

What we need to do is to suspend the coroutine, call downloadTasks to.. well.. download the tasks and provide a callback that upon completion it will resume the coroutine with the tasks at hand.

To achieve that we first need to create an adapter that will connect the callback with a continuation:

private class ContinuationAdapter(
private val continuation: Continuation<List<Task>>
) : DownloadCallback {
override fun onDownloaded(tasks: List<Task>) {
continuation.resume(tasks)
}
}

and then call suspendCoroutine:

fun main(): Unit = runBlocking {
launch {
val tasks = suspendCoroutine<List<Task>> { continuation -> downloadTasks(ContinuationAdapter(continuation)) }
printTasks(tasks)
}
}

That’s it. The adapter resumes the coroutine by providing the tasks that are then returned to the suspension point.

One more thing

Along side suspendCoroutine there is also suspendCancellableCoroutine which provides a cancellable continuation. That means that in addition of resuming we can also execute code upon cancellation:

fun main(): Unit = runBlocking {
val job = launch(Dispatchers.IO) {
val tasks = downloadAllTasks()
printTasks(tasks)
}
delay(100)
job.cancel()
}
private suspend fun downloadAllTasks(): List<Task> {
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation { print("Cancelled…") }
downloadTasks(ContinuationAdapter(continuation))
}
}
private class ContinuationAdapter(
private val continuation: Continuation<List<Task>>
) : DownloadCallback {
override fun onDownloaded(tasks: List<Task>) {
continuation.resume(tasks)
}
}
interface DownloadCallback {
fun onDownloaded(tasks: List<Task>)
}
private fun downloadTasks(callback: DownloadCallback) {
println("Downloading…")
sleep(150) // simulate network latency
val tasks = listOf(Task(1), Task(2), Task(3))
callback.onDownloaded(tasks)
}
private fun printTasks(tasks: List<Task>) {
tasks.forEach { task -> println(task.id) }
}
to improve the readability of the code we can hide the suspension inside another function

this will print Downloading… and then Cancelled…

Debounce user’s input in android without using rx or coroutines

There are myriads of blog posts that showcase how to debounce the user’s input by using either rxjava or coroutines. The question is how to implement such a functionality when the project does not have these dependencies?

Debounce

First of all, what is debounce? In essence debounce is a pattern that helps in preventing the repeated execution of a block of code. It does that by adding a delay between two consecutive calls to the block and by cancelling the first call when the second is requested. For example:

When the user types in her keyboard, every key stroke results in calling a block of code that renders what she typed:

without debounce

By having debounce when the user types, each call gets delayed and cancelled if a new one gets requested resulting in rendering the entire text when the user is finished typing:

with debounce

Before starting

We are going to add the debounce functionality to the example above. The initial code is very simple:

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bindings = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindings.root)
with(bindings) {
userInput.doAfterTextChanged { text ->
userResult.text = text
}
}
}
}

where userInput is the EditText that the user writes in and userResult is the TextView that the user’s input gets rendered.

Adding the debounce functionality

There are two ways to do this. The first uses java’s Timer and TimerTask and the second android’s Handler. Both of them help in implementing the same algorithm:

  1. on every key stroke we first cancel any previous call request
  2. then we setup some kind of timer for our delay and the code that needs to be called
  3. and finally, when the time passes, we make the call
Timer and TimerTask

We can use Timer to schedule the execution of a block of code after a given delay. The provided block must be a TimerTask which will be added to a queue and when the time comes it will be executed in a background thread. This last part is very important since we cannot set anything UI related there. That’s why we use the Timer just for the delay part and then we use the view’s Handler to execute the actual code to the main thread:

class MainActivity : AppCompatActivity() {
private var timer = Timer()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bindings = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindings.root)
with(bindings) {
userInput.doAfterTextChanged { text ->
timer.cancel() // cancel any previous delay
timer = Timer() // schedule a new one
timer.schedule(500L) {
// we are in a background thread here
userInput.handler.post { userResult.text = text }
}
}
}
}
}

Recommended when there is a need to do some intensive work before returning back to the main thread but besides that it could be an overkill. That’s why it might be better to use the view’s Handler for both the delay and the execution.

Handler

The Handler class packs the same functionality as the Timer. We can use it to add a block of code (being added as a callback in a Message instance) in a queue (the handler’s MessageQueue) and when the time comes the message gets removed from the queue and its callback gets executed in the thread that the handler was created in. In our case, since we are using the view’s handler we can be sure that the execution will take place in the main thread.

The Handler class provides methods for both adding and removing from the queue. So what we end up with is something like this:

class MainActivity : AppCompatActivity() {
private var counter = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bindings = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindings.root)
with(bindings) {
userInput.doAfterTextChanged { text ->
userInput.handler.removeCallbacksAndMessages(counter)
userInput.handler.postDelayed(500L, ++counter) { userResult.text = text }
}
}
}
}

One note about the counter variable. For removing a particular message we need to identify it and to do that we can use what is called a token. When posting for delay, we also provide an id in the form of a counter so that we can request its deletion later on.

Debounce extension

Since we are in Kotlin land and to avoid having the above code duplicated with various global counters for identification we can create an extension function that will pack everything together and help us in having a reusable component and more readable code:

fun EditText.debounce(delay: Long, action: (Editable?) -> Unit) {
doAfterTextChanged { text ->
var counter = getTag(id) as? Int ?: 0
handler.removeCallbacksAndMessages(counter)
handler.postDelayed(delay, ++counter) { action(text) }
setTag(id, counter)
}
}

So our code ends up looking like this:

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val bindings = ActivityMainBinding.inflate(layoutInflater)
setContentView(bindings.root)
with(bindings) {
userInput.debounce(500L) { text -> userResult.text = text }
}
}
}

which is very similar to the original code but provides the debounce functionality!

Lets build a coroutine

Ever since Kotlin introduced coroutines there has been a plethora of posts that showcased their usage in networking or multi threading scenarios. Inevitably many developers when asked what a coroutine is their first answer was “a lightweight thread” or “a way to write clean asynchronous code”. Unfortunately this is not the definition of a coroutine but rather a couple of things that a coroutine can help us with.

Routine (aka Subroutine)

So what is a coroutine? To answer that we must first understand what a routine is.

A routine is nothing more than the common function that we all use every day. Its main characteristic is that its execution must come to a completion before returning to the caller. It does not hold any state and calling it multiple times is like calling it for the first time.

An example:

// a routine:
fun saveUserTasks(userId: Int) {
val user = loadUser(userId)
println("user loaded")
val tasks = loadTasks(user)
println("tasks loaded")
saveTasks(tasks)
}
// when called multiple times:
fun main() {
println("Call #1:")
saveUserTasks(7)
println("Call #2:")
saveUserTasks(7)
println("Call #3:")
saveUserTasks(7)
println("done!")
}
// its like calling it for the first time:
/*
Call #1:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
Call #2:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
Call #3:
loading a user…
user loaded
loading tasks for provided user…
tasks loaded
saving provided tasks…
done!
*/
Coroutine

On the other hand a coroutine (a concept that is way older than Kotlin) is a function that can hold its current state allowing us to pause and resume its execution at certain suspension points. This can be of great help when we need to write concurrent code, meaning, when we need to run two tasks at the same time by executing small parts of those tasks one at a time.

For example, if we have task A broken in two parts (A1, A2) and task B broken in two parts as well (B1, B2), we can write code that executes first A1, then B1, then A2 and finally B2.

Building a coroutine

Our goal is to convert the above routine into a coroutine and to do so we need to have a blueprint:

fun saveUserTasks(userId: Int) {
val user = loadUser(userId)
println("user loaded")
// save loaded user
// pause execution
// resume execution
// restore saved user
val tasks = loadTasks(user)
println("tasks loaded")
// save loaded tasks
// pause execution
// resume execution
// restore saved tasks
saveTasks(tasks)
}

So, step #1 is to separate the states into blocks of code and to do that we are going to use the when statement:

fun saveUserTasks(userId: Int, state: Int) {
when (state) {
0 -> {
val user = loadUser(userId)
println("user loaded")
// save loaded user
// pause execution
}
1 -> {
// resume execution
// restore saved user
val tasks = loadTasks(user) // ERROR: unknown user
println("tasks loaded")
// save loaded tasks
// pause execution
}
2 -> {
// resume execution
// restore saved tasks
saveTasks(tasks) // ERROR: unknown tasks
}
}
}

the problem here is that the code does not compile since the states do not communicate and are missing important information.

To fix it we are moving to step #2 where the state parameter will be used as a vessel to pass data from one state to the other:

class State(
var label: Int = 0,
var result: Any? = null
)
fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
state.result = user
// pause execution
}
1 -> {
// resume execution
val user = state.result as User
val tasks = loadTasks(user)
println("tasks loaded")
state.result = tasks
// pause execution
}
2 -> {
// resume execution
val tasks = state.result as List<Task>
saveTasks(tasks)
}
}
}

the code now has distinct states that share data but it cannot resume correctly since there is no way to move from one state to the other.

Step #3 addresses that by updating the state’s label allowing the function to resume from where it was paused:

fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
state.result = user
state.label = 1
return
}
1 -> {
// resume execution
val user = state.result as User
val tasks = loadTasks(user)
println("tasks loaded")
state.result = tasks
state.label = 2
return
}
2 -> {
// resume execution
val tasks = state.result as List<Task>
saveTasks(tasks)
}
}
}

and that’s it. saveUserTasks is now a coroutine where every call executes a small part of the function allowing us to pause and resume the task:

fun main() {
val state = State()
println("Call #1:")
saveUserTasks(7, state)
println("Call #2:")
saveUserTasks(7, state)
println("Call #3:")
saveUserTasks(7, state)
println("done!")
}
// results in:
/*
Call #1:
loading a user…
user loaded
Call #2:
loading tasks for provided user…
tasks loaded
Call #3:
saving provided tasks…
done!
*/

Concurrency

We did manage to convert a routine to a coroutine but the example did not show the power of using coroutines which is concurrency. Lets change that.

To make things a little bit easier we are going to package a few common functionalities together

abstract class Coroutine {
var isFinished: Boolean = false
private set
protected val state: State by lazy { State() }
protected fun resumeWith(result: Any) {
state.label++
state.result = result
}
protected fun <T> restore(): T {
return state.result as T
}
protected fun finish() {
state.label++
isFinished = true
}
protected class State(
var label: Int = 0,
var result: Any? = null
)
}

which allows us to create these

class SaveUserTasks : Coroutine() {
operator fun invoke(userId: Int) {
saveUserTasks(userId, state)
}
private fun saveUserTasks(userId: Int, state: State) {
when (state.label) {
0 -> {
val user = loadUser(userId)
println("user loaded")
resumeWith(user)
return
}
1 -> {
val user = restore<User>()
val tasks = loadTasks(user)
println("tasks loaded")
resumeWith(tasks)
return
}
2 -> {
val tasks = restore<List<Task>>()
saveTasks(tasks)
finish()
}
}
}
}
class ApplyDownloadedSettings : Coroutine() {
operator fun invoke() {
applyDownloadedSettings(state)
}
private fun applyDownloadedSettings(state: State) {
when (state.label) {
0 -> {
val settings = downloadSettings()
println("settings downloaded")
resumeWith(settings)
return
}
1 -> {
val settings = restore<Settings>()
applySettings(settings)
println("setting applied")
finish()
}
}
}
}

This way we can call the two functions as such:

fun main() {
val saveUserTasks = SaveUserTasks()
val applyDownloadedSettings = ApplyDownloadedSettings()
while (!saveUserTasks.isFinished || !applyDownloadedSettings.isFinished) {
saveUserTasks(7)
applyDownloadedSettings()
}
println("done!")
}
/*
loading a user…
user loaded
downloading settings…
settings downloaded
loading tasks for provided user…
tasks loaded
applying settings…
setting applied
saving provided tasks…
done!
/*

As you can see the two functions are being executed concurrently. First we load the user, then we download the settings, then we load the user’s task and so on and so forth!

Further reading

My goal with this post was to give you a better understanding on what a coroutine is and I did it by using the concept of a finite state machine. The same concept that Kotlin’s implementation of coroutines uses.

For digging in the actual implementation of coroutines I suggest you take a look at a couple of great posts that helped me a lot in grasping the concept:

The memento design pattern in Kotlin

I started playing with the memento pattern for a use case I was researching when I realized that the Kotlin implementation had a, potentially, show stopper in comparison with the Java one:

I could not use a private property from within the same file

Why was that a show stopper? We’ll see, but first, what is the memento pattern?

Memento pattern

This pattern is a good way to implement a functionality that helps in restoring previous states. One good example is the undo in our text editors. You can write, edit, delete and then, by hitting undo, take each action back.

There are three main ingredients for this pattern:

  1. the originator that holds the current state and creates snapshots of itself,
  2. the memento that, in essence, is the snapshot with perhaps some additional metadata and
  3. the caretaker that orchestrates the backup/restore of the state

So in our example the originator is the editor which knows what the text is, the carets position etc, the memento a copy of those values and the caretaker can be the interface between the user and the editor.

Java implementation

Lets try to have an overly simplified version of the above example in Java:

public final class Editor {
private final List<String> text;
private int caretPosition;
public Editor() {
this.text = new ArrayList<>();
this.caretPosition = 0;
}
public void write(final String sentence) {
text.add(sentence);
caretPosition = calculateCaretPositionInEndOf(text);
}
public void edit(final int index, final String newSentence) {
text.remove(index);
text.add(index, newSentence);
final List<String> subText = text.subList(0, index + 1);
caretPosition = calculateCaretPositionInEndOf(subText);
}
public void delete(final int index) {
final List<String> subText = new ArrayList<>(text.subList(0, index));
text.remove(index);
caretPosition = calculateCaretPositionInEndOf(subText);
}
public void render(final Screen screen) {
final String allText = String.join("", text);
screen.render(allText);
screen.renderCaretAt(caretPosition);
}
public Memento backup() {
return new Memento(text, caretPosition);
}
public void restore(final Memento memento) {
text.clear();
text.addAll(memento.text);
caretPosition = memento.caretPosition;
}
private int calculateCaretPositionInEndOf(final List<String> lines) {
return lines.stream().mapToInt(String::length).sum() + 1;
}
public static final class Memento {
private final List<String> text;
private final int caretPosition;
public Memento(List<String> text, int caretPosition) {
this.text = new ArrayList<>(text);
this.caretPosition = caretPosition;
}
}
}

Here the editor, besides manipulating text, is able to produce snapshots of its state in a way that only itself can access the state’s values. The Memento class might be public, in order to allow the caretaker to handle instances of it, but its fields are private and only the originator can read them.
A great way to copy something while having the smallest possible API surface and maximum privacy.

As a matter of fact, here is the caretaker and its usage:

class UI(
private val screen: Screen
) {
private val editor = Editor()
private val backups = mutableListOf<Memento>()
fun write(text: String) {
backups.add(0, editor.backup())
editor.write(text)
editor.render(screen)
}
fun edit(index: Int, text: String) {
backups.add(0, editor.backup())
editor.edit(index, text)
editor.render(screen)
}
fun delete(index: Int) {
backups.add(0, editor.backup())
editor.delete(index)
editor.render(screen)
}
fun undo() {
val memento = backups.removeAt(0)
editor.restore(memento)
editor.render(screen)
}
}
fun main() {
val screen = StdoutScreen()
val ui = UI(screen)
with(ui) {
write("Hello, there! ")
write("How are you? ")
write("I hope you feel good :)")
edit(1, "Kotlin! ")
delete(1)
undo()
undo()
undo()
undo()
undo()
}
}
/* which produces this:
Hello, there! |
Hello, there! How are you? |
Hello, there! How are you? I hope you feel good :)|
Hello, there! Kotlin! |I hope you feel good 🙂
Hello, there! |I hope you feel good 🙂
Hello, there! Kotlin! |I hope you feel good 🙂
Hello, there! How are you? I hope you feel good :)|
Hello, there! How are you? |
Hello, there! |
|
/*

As you can see the UI uses the editor to write, edit, delete but before that it saves a backup with the editor’s state in order to restore it every time the user hits undo!

Kotlin implementation

So lets move originator and memento to Kotlin. Ctrl+Alt+Shift+K and boom.. we have a problem:

Kotlin, in contrast with Java, does not allow accessing private properties when in the same file.

What do we do? Well we can always make the properties public:

class Memento(text: List<String>, caretPosition: Int) {
val text: List<String>
val caretPosition: Int
init {
this.text = ArrayList(text)
this.caretPosition = caretPosition
}
}

but this way we, indirectly, expose the editors state:

Another way to implement the pattern is to have Memento as an interface with no state for the public API and have a private implementation of it for internal usage:

class Editor {
// …
fun backup(): Memento {
return ActualMemento(text, caretPosition)
}
fun restore(memento: Memento) {
if (memento !is ActualMemento) return
text.clear()
text.addAll(memento.text)
caretPosition = memento.caretPosition
}
// …
interface Memento
private class ActualMemento(text: List<String>, caretPosition: Int) : Memento {
val text: List<String>
val caretPosition: Int
init {
this.text = ArrayList(text)
this.caretPosition = caretPosition
}
}
}

this way we do not expose any state but we do open a bit our API. We now have an interface that can be implemented and given to the restore() function.

Inner classes

Fortunately Kotlin has inner classes. An inner class can access the outer class’s members but, most importantly, can be extended only from within the outer class. This means that this:

class Editor {
// …
fun backup(): Memento {
return ActualMemento(text, caretPosition)
}
fun restore(memento: Memento) {
memento as ActualMemento
text.clear()
text.addAll(memento.text)
caretPosition = memento.caretPosition
}
// …
open inner class Memento
private inner class ActualMemento(text: List<String>, caretPosition: Int) : Memento() {
val text: List<String>
val caretPosition: Int
init {
this.text = ArrayList(text)
this.caretPosition = caretPosition
}
}
}

checks all our boxes. We keep the originator’s state private and our overall API small!

I prefer not to use the keyword “it”

And the reason is simple:

I want to be as explicit as possible and allow the reader of my code to have an uninterrupted flow.

Think about it. Every time you encounter the it keyword you do, a quick, conversion between what you see and what it represents. Personally I do it even in very small lambdas, imagine if you are two or three lines deep in a lambda and you see an it:

val animals = listOf("Lion", "Penguin", "Giraffe", "Whale", "Shark")
val usernames = animals.map {
val randomNumber = Random.nextInt(0, 10)
val randomCharacter = listOf("!", "@", "$", "%", "#")[Random.nextInt(0, 5)]
"$it$randomCharacter$randomNumber"
}

It might not look much in this simple example but read it now with an explicit value:

val animals = listOf("Lion", "Penguin", "Giraffe", "Whale", "Shark")
val usernames = animals.map { animalAsBase ->
val randomNumber = Random.nextInt(0, 10)
val randomCharacter = listOf("!", "@", "$", "%", "#")[Random.nextInt(0, 5)]
"$animalAsBase$randomCharacter$randomNumber"
}

You don’t have to do a mental translation and it also provides some details regarding the format of username.

This last part can make the code even more readable since it allows us to describe the values we use:

values.map { rawValue -> Name.of(rawValue) }

this hints that (a) values list does not contain usable data and (b) the of function will perform some kind of cleaning