Skip to content

JuulLabs/tuulbox

Repository files navigation

codecov Slack

Tuulbox

Toolbox of utilities/helpers for Kotlin development.

badge-ios badge-js badge-jvm badge-mac

AtomicList, AtomicSet, and AtomicMap

Implementations of List, Set, and Map with strong guarantees around mutability. Each of these collections can be snapshot to reference their current values without reflecting future changes. A StateFlow of snapshots is accessible for receiving hot notifications of mutations. Returned collections and iterators automatically reference the snapshot of when they were created.

These collections do not implement the various mutable collection interfaces. To mutate these collections, you must use an explicit mutator function. These mutator functions use a lambda to modify the list, and if concurrent mutations occur these lambdas may be ran more than once. In this way each mutation is guaranteed atomic, but you must be careful with side effects.

  • mutate updates the collection without returning a value.
  • snapshotAndMutate updates the collection and returns the snapshot which was used in the successful mutation.
  • mutateAndSnapshot updates the collection and returns the snapshot which results from the mutation.

Convert a map to a Plain Old JavaScript Object by transforming the keys to strings and the values to any of the JavaScript types.

Map is not directly accessible from Javascript code. For example:

val simple = mapOf(1 to "a")
val json = js("JSON.stringify(simple)") as String
println(json)
>>>
{
  "_keys_up5z3z$_0": null,
  "_values_6nw1f1$_0": null,
  <and lots of other name-mangled properties>
}

val value = js("simple['1']") as String
println(value)
>>> ClassCastException: Illegal cast

Using this convertor to emit Object with the expected properties:

val simple = mapOf(1 to "a").toJsObject { it.key.toString() to it.value }
val json = js("JSON.stringify(simple)") as String
println(json)
>>> {"1":"a"}

val value = js("simple['1']") as String
println(value)
>>> a

badge-android badge-ios badge-js badge-jvm badge-mac

Utilities for Coroutines.

combine for up to 10 Flows:

val combined = combine(
    flow1,
    flow2,
    // ...
    flow9,
    flow10,
) { value1, value2, /* ... */, value9, value10 ->
    // ...
}

Logging

Logging has been pulled into its own library, Khronicle.

Migrating

First, update your gradle files. Be aware that Tuulbox versions and Khronicle versions are not the same.

// For general usage
- implementation("com.juul.tuulbox:logging:$tuulboxVersion")
+ implementation("com.juul.khronicle:khronicle-core:$khronicleVersion")

// For Android only
- implementation("com.juul.tuulbox:logging-android:$tuulboxVersion")
+ implementation("com.juul.khronicle:khronicle-android:$khronicleVersion")

// For Ktor integration only
- implementation("com.juul.tuulbox:logging-ktor-client:$tuulboxVersion")
+ implementation("com.juul.khronicle:khronicle-ktor-client:$khronicleVersion")

Then, update your code. Class names have been kept the same, but package names have changed. This should be as simple as a find-and-replace of com.juul.tuulbox.logging with com.juul.khronicle.

badge-ios badge-js badge-jvm badge-mac

Utilities for manipulating functions. For a full functional ecosystem, complete with Monad and the like, prefer Arrow.

badge-android badge-js badge-jvm

Toolbox of utilities for dates and times, building on KotlinX DateTime.

Various interval Flows are provided, such as: instantFlow, localDateTimeFlow, and localDateFlow. For example:

localDateTimeFlow().collect {
    println("The time is now: $it")
}

Note: Because this is built on top of KotlinX DateTime, core library desugaring must be enabled for Android targets.

badge-ios badge-js badge-jvm badge-mac

Utilities for test suites.

assertContains(
    array = arrayOf(1, 2, 3),
    value = 2,
)
assertContains(
    range = 1..10,
    value = 5,
)

badge-ios badge-js badge-jvm badge-mac

Utilities for working with binary data.

val bitSet = 0.bits
bitSet[30] = true
bitSet.asPrimitive() // 1073741824
/* | Index | ... | 3 | 2 | 1 | 0 |
 * |-------|-----|---|---|---|---|
 * | Bit   | ... | 1 | 0 | 1 | 0 |
 */
val bitSet = 10.bits

bitSet[0] // false
bitSet[1] // true
bitSet[2] // false
bitSet[3] // true
/* | Index | ... | 3 | 2 | 1 | 0 |
 * |-------|-----|---|---|---|---|
 * | Bit   | ... | 1 | 1 | 0 | 0 |
 */
val bitSet = 12L.bits

bitSet[0] // false
bitSet[1] // false
bitSet[2] // true
bitSet[3] // true
val bitSet = 0L.bits
bitSet[40] = true
bitSet.asPrimitive() // 1099511627776L

Setup

Gradle

Maven Central

Tuulbox can be configured via Gradle Kotlin DSL as follows:

Multiplatform

plugins {
    id("com.android.application") // or id("com.android.library")
    kotlin("multiplatform")
}

repositories {
    mavenCentral()
}

kotlin {
    jvm() // and/or android()
    js().browser() // and/or js().node()
    macosX64()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("com.juul.tuulbox:collections:$version")
                implementation("com.juul.tuulbox:coroutines:$version")
                implementation("com.juul.tuulbox:encoding:$version")
                implementation("com.juul.tuulbox:functional:$version")
                implementation("com.juul.tuulbox:temporal:$version")
            }
        }

        val commonTest by getting {
            dependencies {
                implementation("com.juul.tuulbox:test:$version")
            }
        }
    }
}

Platform-specific

repositories {
    mavenCentral()
}

dependencies {
    implementation("com.juul.tuulbox:collections:$version")
    implementation("com.juul.tuulbox:coroutines:$version")
    implementation("com.juul.tuulbox:encoding:$version")
    implementation("com.juul.tuulbox:functional:$version")
    implementation("com.juul.tuulbox:temporal:$version")
    testImplementation("com.juul.tuulbox:test:$version")
}

License

Copyright 2021 JUUL Labs, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.