Skip to content

Woody230/KotlinExtensions

Repository files navigation

Kotlin Extensions

Kotlin Multiplatform extensions and implementations for the Kotlin standard library and third-party libraries.

Gradle

Published to Maven Central.

repositories {
    mavenCentral()
}
implementation("io.github.woody230.ktx:$Module:$Version")

Modules

aboutlibraries

Extensions for AboutLibraries. See compose-aboutlibraries for the associated composable.

comparator

  • Nullable string comparator.
  • User friendly string and enum comparators.

Jetpack Compose/Compose Multiplatform

Compose Multiplatform is the compose version used by this project to support both Android and the JVM.

New to Compose?

compose-aboutlibraries

compose-multiplatform composable for AboutLibraries

@Composable
fun Libraries(libraries: List<Library>) = LibraryProjector(
    interactor = LibraryInteractor(
        libraries = libraries
    )
).Projection()

Using the AssetReader from the resource module, you can use the libraries output from AboutLibraries as a moko-resource asset.

First, you will need to add the following to gradle in order to copy it as a resource.

val aboutLibrariesResource = task("aboutLibrariesResource") {
    dependsOn("exportLibraryDefinitions")

    copy {
        from("$buildDir\\generated\\aboutLibraries") {
            include("aboutlibraries.json")
        }
        into("$projectDir\\src\\commonMain\\resources\\MR\\assets")
    }
}

tasks.whenTaskAdded {
    if (name == "generateMRcommonMain") {
        dependsOn(aboutLibrariesResource)
    }
}

Now you can use the AssetReader to read the resource and create the libraries from it.

val libraries = with(AssetReader) {
    val content = MyResources.assets.aboutlibraries.readText()
    Libs.Builder().withJson(content).build().libraries
}

compose-accompanist

A copy of Accompanist modules (v0.23.1 and v0.2.1 Snapper) with multiplatform capability. Currently this only includes the pager and pager indicators.

compose-constraint-layout

A copy of ConstraintLayout (v2.1.3 core and v1.0.0 compose) with multiplatform capability.

compose-resource

Wrappers for strings and images for the compose module using the resource module.

Conversion of image moko-resources:

val painter = MyResources.images.my_resource.painter()
val localized = MyResources.strings.my_resource.localized()

Standardized AlertDialog buttons:

val withConfirmation = AlertDialogInteractor.Builder().uniText().build()
val withConfirmationCancel = AlertDialogInteractor.Builder().biText().build()
val withConfirmationCancelReset = AlertDialogInteractor.Builder().triText().build()

IconInteractor wrappers for common use cases such as:

val delete = deleteIconInteractor()
val language = languageIconInteractor()
val settings = settingsIconInteractor()

Conversion of text moko-resources:

val text = MyResources.strings.my_resource.textInteractor()

compose-serialization

kotlinx.serialization extensions for compose related classes.

  • ColorSerializer for storing the Hex associated with a androidx.compose.ui.graphics.Color

compose-settings

Setting to compose-multiplatform state converters.

@Composable
fun Observe(setting: Setting<Instant>, nullableSetting: Setting<Instant?>) {
    val defaulted: MutableState<Instant> = setting.safeState()
    val nullable: MutableState<Instant?> = setting.nullState()

    // Keep the ability to be nullable, but the value will be defaulted and not null.
    val defaultedNullable: MutableState<Instant?> = nullableSetting.defaultState()
}

ColorSetting for androidx.compose.ui.graphics.Color.

fun color(settings: SuspendSettings): Setting<Color> = ColorSetting(
    settings = settings,
    key = "Color",
    defaultValue = Color.Blue
)

compose-ui

The ShowAlert() composable method is used to send notifications.

  • On Android, a Toast is created. Note that the title is ignored.
  • On Desktop, a notification is sent to the tray.
@Composable
fun SendMessage() = ShowAlert(title = "Important!", "An error occurred.", type = AlertType.ERROR)

The ApplicationSize companion object can be used to get the current size of the Android device or desktop window size.

val size: DpSize = ApplicationSize.current

Conversion of a ByteArray to an androidx.compose.ui.graphics.ImageBitmap.

val content = byteArrayOf()
val bitmap = content.asImageBitmap()

compose-ui-geometry

ArcShape for creating arcs.

compose-ui-graphics

Converting hexadecimal colors in ARGB or RGB string format (with optional # prefix) to androidx.compose.ui.graphics.Color.

val color = Hex("3dab5a").color()
val hex = color.hex()

compose-ui-intl

Converter for the com.bselzer.ktx.intl.Locale to androidx.compose.ui.text.intl.Locale.

LocalLocale composition local that can be provided by the following in order to recompose on Localizer.locale changes:

@Composable
fun Content() {
    ProvideLocale {
        val locale = LocalLocale.current
        // ...
    }
}

compose-ui-layout, compose-ui-layout-common, compose-ui-layout-custom

Wrappers and custom composable components using the content/presentation model and projection concept as described by kirill-grouchnikov's Aurora project.

The common module contains wrappers for official components. The custom module contains completely custom components or special wrappers for official components.

See the compose-layout file for documentation on the custom components provided.

compose-ui-text

AnnotatedString and SpanStyle extensions.

compose-ui-unit

Pixel to Dp and Dp to pixel conversions.

val dp = 50f.toDp()
val pixel = dp.toPx()

coroutine

coroutines

Managing locks by a key:

val lock = LockByKey<Int>()
lock.withLock(1) {
    // Perform
}

datetime

kotlinx.datetime

  • Formatting by pattern or style.
val patternFormatter = PatternDateTimeFormatter("EEEE")
val timeFormatter = FormatStyleDateTimeFormatter(dateStyle = null, timeStyle = FormatStyle.SHORT)

datetime-serialization

  • LenientDurationSerializer using kotlinx.serialization.
    • Uses the more lenient Duration.parse() method instead of Duration.parseIsoString().

function

General Kotlin standard library extensions.

enum class Position {
    FIRST, SECOND
}

val userFriendly: String = Position.FIRST.userFriendly()
val first: Position = "FIRST".enumValue<Position>()

val items: Array<Position> = buildArray {
    add(Position.FIRST)
    add(Position.SECOND)
}

val bytes = "AdsnAAA=".decodeBase64ToByteArray()
val string = bytes.encodeBase64ToString()

geometry

Two and three dimensional geometrical objects.

Two dimensional objects include a digon, quadrilateral, 2D point, 2D size. Three dimensional objects include a 3D point.

geometry-serialization

Serializers for two and three dimensional geometrical objects using kotlinx.serialization.

image-ktor-client

val client = ImageClient()
val content: ByteArray = client.getImage("https://assets.gw2dat.com/1459697.png").content

image-kodein-db

Kodein-DB extensions for images.

image-model

data class Image(
    val url: String,
    val content: ByteArray
)

intent

Based on Android intents. Currently supports browser opening and mailto.

Browser.open("https://google.com")

Emailer.send(
  email = Email(
    to = listOf("foo@bar.com", "fizz@buzz.com"),
    cc = listOf("test@test.com"),
    bcc = listOf("bar@baz.com", "test@test.com"),
    subject = "This is a test subject.",
    body = "This is a test body."
  )
)

intl

Internationalization through locale support.

com.bselzer.ktx.intl.Locale is added as a non-composable way to handle locale changes. However, it can be converted to androidx.compose.ui.text.intl.Locale. See the compose section.

DefaultLocale provides the system's default locale. However, there is no ability to be notified of changes to this locale. Instead, a Localizer should be used to maintain an instance of a locale and to be able to be notified of changes by adding a listener which can then be used to update the DefaultLocale if needed.

intl-serialization

Serializer for the com.bselzer.ktx.intl.Locale using kotlinx.serialization.

kodein-db

Kodein-DB extensions.

Note that Kodein-DB is currently archived but may return under a new name.

If you would still like to use it, then apply the following:

val version = "0.9.0-beta"

// For applications
implementation("org.kodein.db:kodein-db:$version")

// For libraries
implementation("org.kodein.db:kodein-db-api:$version")

// kotlinx.serialization
implementation("org.kodein.db:kodein-db-serializer-kotlinx:$version")

// Desktop LevelDB native build depending on OS
implementation("org.kodein.db:kodein-leveldb-jni-jvm-linux:$version")
implementation("org.kodein.db:kodein-leveldb-jni-jvm-macos:$version")
implementation("org.kodein.db:kodein-leveldb-jni-jvm-windows:$version")

The fully intact documentation can be found in commit 0f310b317920fbbea56d5dc81a9049a072aaa435.

  • IdentifiableMetadataExtractor for Identifiable objects from the value module to use the identifier's value as the metadata.
  • IdentifierValueConverter for using an Identifier.value as a Value if it is for an Int, Long, or String.
  • DBTransaction for managing reading and writing a batch to a DB instance
    • clear() extension method for deleting all models of a given type
    • findByReferenceId() and findByIds() extension methods for finding all Identifiable models based on given ids
    • getById() extension method for finding an Identifiable model with a given id or requesting it if it does not exist
    • putMissingById() extension method for finding all Identifiable models based on given ids and then requesting those missing to be put into the DB

ktor-client

Client side Ktor extensions.

  • GenericTypeInfo for enforcing the type of the reified usage of TypeInfo creation.
  • UrlOptions can be used to merge url based options and to replace path parameters.

ktor-client-connectivity

  • Connectivity for determining if an active connection is able to be established

logging

Logging wrapper around Napier.

resource

moko-resources extensions.

  • AssetReader to read the text content associated with a moko-resource asset.
  • Common strings for the German, English, French, and Spanish languages including:
    • DurationUnit enums
    • Locale for German, English, French, and Spanish language locales only.
    • Boolean to stringResource converter for enabled/disabled.
  • Common images taken from material icons that are not bundled directly with compose.
    • Currently this project is not using the extended material icons set from org.jetbrains.compose.material:material-icons-extended-desktop.

Resources are generated with the name KtxResources:

val days = KtxResources.strings.days

serialization-core

kotlinx.serialization core extensions.

  • Base StringFormatContext:
  • Enum conversion of strings, collections of strings, or maps with string keys.
    • This will convert based on the @SerialName associated with the enum, which is different from the function conversion that would compare the name of the enum instead.
enum class LegendName {
    @SerialName("Legend1")
    DRAGON,
}

with(JsonContext) {
    val legend: LegendName = "Legend1".decode() // LegendName.DRAGON
    val nullableLegend: LegendName? = "Legend2".decodeOrNull() // Null
}
  • Array merging:

    • Concat: Appends the elements.
    • Union: Skips elements that already exist.
    • Replace: Replaces the existing array with a new array.
    • Merge: Replaces the elements within the same index.
  • Null merging:

    • Ignore
    • Merge
  • IntegerSerializer for lenient integer parsing as a string or numeric

serialization-json

kotlinx.serialization json extensions.

  • JsonContext which extends from StringFormatContext
    • Supports merging JsonElements.
  • DelimitedListSerializer for converting a delimited string into a list.

serialization-xml

xmlutil extensions.

  • XmlContext which extends from StringFormatContext
  • LoggingUnknownChildHandler for XML deserialization that will log the failed deserialization of a child not defined in your object instead of throwing an exception

serialization-yaml

yamlkt extensions

serialization-yaml-json-conversion

Extensions for converting a YamlElement from yamlkt to a JsonElement from kotlinx.serialization and vice versa.

settings

Async wrapper of primitive and serializable multiplatform-settings settings.

  • Primitive wrappers include Boolean, Double, Float, Int, Long, and String alongside their Identifier counterparts from the value module.
  • Serializable wrappers include a generic SerializableSetting by providing the serializer associated with the object, and for the Duration class.
fun lastRefresh(settings: SuspendSettings): Setting<Instant> {
    return SerializableSetting(
        settings = settings,
        key = "LastRefresh",
        defaultValue = Instant.DISTANT_FUTURE, 
        serializer = serializer()
    )
}

These wrappers must provide an instance of SuspendSettings, a key, and a default value to use when the preference is not set. When calling get() or observe(), this default value will be used as the default instead of null.

Method Purpose
get Gets the value of the preference or the default value if it does not exist.
getOrNull Gets the value of the preference or null if it does not exist.
set Sets the value of preference and notifies observers about the change.
remove Removes the value of the preference and notifies observers about the change.
exists Used to determine if the preference has been initialized.
initialize Only sets a value if the preference has not been set.
observe Creates a callback flow that listens to preference changes. Uses the default value if the preference does not exist.
observeOrNull Creates a callback flow that listens to preference changes. Uses null if the preference does not exist.

value-enumeration

Value classes for wrapping an enumeration. The StringEnum class:

  • Holds the original string provided during deserialization.
  • Provides the convenience of conversion methods to the intended enumeration without the consequence of being forced to fail, especially with leniency enabled.

value-identifier

Value classes for an Identifier wrapping a Byte, Boolean, Double, Float, Int, Long, Short, or String. The Identifiable interface can be used to specify an Identifier id.

@JvmInline
value class LuckId(override val value: String = "") : StringIdentifier {
  override fun toString(): String = value
}

data class AccountLuck(
  override val id: LuckId = LuckId(),
  val value: Int = 0
) : Identifiable<LuckId, String>