Compile-time value preprocessed into Kotlin code from Gradle - kotlin

I am trying to get some values from a Gradle build into some Kotlin code.
For example, if my code was:
const val compileDate = THE_DATE_THAT_THE_CODE_WAS_COMPILED_ON;
const val compileBranch = THE_GIT_BRANCH_THAT_THE_CODE_WAS_COMPILED_FROM;
fun main() {
println("$compileDate - $compileBranch")
}
Is there any way to get those constants embedded into the code from the build itself?
If its not possible in that manner, I would think it might be possible using annotations, but again - I don't know how.
Any help would be appreciated.
Thank you.

Using annotations
So I found a solution by writing an annotation processor. Here's a fairly minimal working example:
./annotations/src/main/kotlin/net/example/CompileTimeValue.kt:
package net.example
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
annotation class CompileTimeValue(val of : String)
./annotations/build.gradle.kts:
plugins {
kotlin("jvm")
}
./processor/src/main/kotlin/net/example/CompileTimeEvaluator.kt:
package net.example
import com.google.auto.service.AutoService
import java.io.File
import java.io.IOException
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
#AutoService(Processor::class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedOptions(CompileTimeEvaluator.KAPT_OPTION)
class CompileTimeEvaluator : AbstractProcessor() {
private val assignments = mutableMapOf<Element, String?>()
override fun getSupportedAnnotationTypes(): MutableSet<String> =
mutableSetOf(CompileTimeValue::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion =
SourceVersion.latest()
override fun process(set: MutableSet<out TypeElement>?, env: RoundEnvironment?): Boolean {
env?.getElementsAnnotatedWith(CompileTimeValue::class.java)?.forEach {
val note = it.getAnnotation(CompileTimeValue::class.java) as CompileTimeValue
assignments[it] = outputOfCommand(note)
}
val kaptGeneratedDir = processingEnv.options[KAPT_OPTION]
val file = File(kaptGeneratedDir, "ComputedConstants.kt")
file.writeText(generatedClass())
return true
}
private fun generatedClass() : String = """
object ComputedConstants {
fun load() {
${loadAssignments()}
}
}
""".trimIndent()
private fun loadAssignments() : String =
assignments.entries.joinToString("\n") {
val element = it.key
val value = it.value
val elementName = element.simpleName.toString()
// TODO: I think a recursive solution to find the true parent class
// is necessary here to deal with nested classes
val elementNest = element.enclosingElement.simpleName.toString()
val elementPack = processingEnv.elementUtils.getPackageOf(element)
val fullName = "$elementPack.$elementNest.$elementName"
if (value != null)
"$fullName = \"\"\"$value\"\"\""
else
"$fullName = null"
}
private fun outputOfCommand(note : CompileTimeValue) : String? = try {
val process = ProcessBuilder(*note.of.split(' ').toTypedArray())
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
process.waitFor()
process.inputStream.bufferedReader().readText().trimEnd('\n')
} catch (e : IOException) {
null
}
companion object {
const val KAPT_OPTION = "kapt.kotlin.generated"
}
}
./processor/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
}
kapt {
correctErrorTypes = true
}
dependencies {
"kapt"(project(":annotations"))
compileOnly(project(":annotations"))
implementation(fileTree("libs").include("*.jar"))
implementation("com.google.auto.service:auto-service:1.0-rc6")
annotationProcessor("com.google.auto.service:auto-service:1.0-rc6")
}
./example/src/main/kotlin/net/example/Example.kt:
package net.example
object Constants {
#field:CompileTimeValue("date")
var compileDate : String? = "Unknown"
}
fun main() {
ComputedConstants.load()
println("Compiled on: ${Constants.compileDate}")
}
./example/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
application
}
application {
mainClass.set("net.example.ExampleKt")
}
dependencies {
implementation(project(":annotations"))
"kapt"(project(":annotations"))
"kapt"(project(":processor"))
}
./example/build/generated/source/kaptKotlin/main/ComputedConstants.kt:
object ComputedConstants {
fun load() {
net.example.Constants.compileDate = """Sat 27 Mar 2021 01:30:46 PM EDT"""
}
}
./build.gradle.kts:
plugins {
// apply(false) so that it can be re-used in children
kotlin("jvm") version "1.4.31" apply(false)
kotlin("kapt") version "1.4.31" apply(false)
}
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
}
./settings.gradle.kts:
include(":example")
include(":annotations")
include(":processor")

Another quick way is to use the gradle copy task to inflate a kotlin source template.
Create a kotlin template file App.kt some where in the source tree (src/main/templates/App.kt)
object App {
const val VERSION = "$version"
const val BASE_VERSION = "$base_version"
}
Create a copy task in your build file
val copyTemplates by registering(Copy::class) {
filteringCharset = "UTF-8"
from(project.projectDir.resolve("src/main/templates"))
into(project.buildDir.resolve("generated-sources/templates/kotlin/main"))
}
Add the generated template to the main sourceset
sourceSets {
main {
java.srcDirs(copyTemplates)
}
}
Now you can access App as regular kotlin object from your classes
fun main() {
println("Version is : ${App.VERSION}")
}

Related

Unresolved reference: preferencesKey Android Kotlin

I'm trying to store boolean in DataStore.
class OnboardingPrefs(val context: Context) {
companion object {
const val ON_BOARDING_PREFS = "on_boarding_prefs"
const val ONBOARD_KEY = "onBoard"
}
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = ON_BOARDING_PREFS
)
suspend fun saveOnboarding(save: Boolean) {
context.dataStore.edit { preferences ->
preferences[preferencesKey<Boolean>(ONBOARD_KEY)] = save
}
}
}
Problem is preferencesKey cannot be resolved.
Gradle:
implementation 'androidx.datastore:datastore-preferences:1.0.0'

Receiver class does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()'

I'm trying to run local server with KTOR and to cover it with tests. I wrote some code and after writing some tests, the tests successfully raised the local server and passed. However, if I try to start a local server, I get this error
Exception in thread "main" java.lang.AbstractMethodError: Receiver
class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does
not define or inherit an implementation of the resolved method
'abstract boolean getDevelopmentMode()' of interface
io.ktor.application.ApplicationEnvironment.
I attach the code and the stack trace from below
server.kt
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.gson.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
gson()
}
routing {
val controller = Controller()
get("/converter{from}{to}") {
try {
val fromCurrency = call.request.queryParameters["from"]
val toCurrency = call.request.queryParameters["to"]
val rate = controller.converter(fromCurrency, toCurrency)
val response = Response(
"1 $fromCurrency = $rate $toCurrency",
null
)
call.respond(HttpStatusCode.OK, response)
} catch (e: ControllerException) {
val response = Response(
null,
e.message
)
call.respond(HttpStatusCode.BadRequest, response)
}
}
get {
call.respond(HttpStatusCode.NotFound, Response())
}
}
}
#Serializable
data class Response(
#SerialName("converterResponse")
val converterResponse: String? = null,
#SerialName("errorMessage")
val errorMessage: String? = null
)
converterTest.kt
import com.google.gson.Gson
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.server.testing.*
import kotlin.test.Test
import kotlin.test.assertEquals
class ConverterTest {
#Test
fun `Identical Correct Currencies`() {
withTestApplication(Application::module) {
handleRequest(HttpMethod.Get, "/converter?from=USD&to=USD").apply {
assertEquals(HttpStatusCode.OK, response.status())
val expectedResponse = Response("1 USD = 1 USD")
assertEquals(
Gson().toJson(expectedResponse),
response.content
)
}
}
}
}
stacktrace
Exception in thread "main" java.lang.AbstractMethodError: Receiver class io.ktor.server.engine.ApplicationEngineEnvironmentReloading does not define or inherit an implementation of the resolved method 'abstract boolean getDevelopmentMode()' of interface io.ktor.application.ApplicationEnvironment.
at io.ktor.application.Application.<init>(Application.kt:20)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.instantiateAndConfigureApplication(ApplicationEngineEnvironmentReloading.kt:269)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.createApplication(ApplicationEngineEnvironmentReloading.kt:125)
at io.ktor.server.engine.ApplicationEngineEnvironmentReloading.start(ApplicationEngineEnvironmentReloading.kt:245)
at io.ktor.server.netty.NettyApplicationEngine.start(NettyApplicationEngine.kt:126)
at io.ktor.server.netty.EngineMain.main(EngineMain.kt:26)
at ServerKt.main(server.kt:11)
build.gradle
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
kotlin("jvm") version "1.5.30" // or kotlin("multiplatform") or any other kotlin plugin
application
}
group = "me.admin"
version = "1.0-SNAPSHOT"
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
dependencies {
val kotlin_version = "1.5.30"
val ktor_version = "1.6.3"
implementation("io.ktor:ktor-server-netty:1.4.0")
implementation("io.ktor:ktor-client-cio:1.4.0")
implementation("io.ktor:ktor-html-builder:1.4.0")
implementation("io.ktor:ktor-client-serialization:1.3.2-1.4-M2")
implementation("org.jetbrains.kotlinx:kotlinx-html-jvm:0.7.2")
implementation("org.slf4j:slf4j-api:2.0.0-alpha5")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC")
implementation(kotlin("stdlib-jdk8"))
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlin_version")
testImplementation("io.ktor:ktor-server-test-host:$ktor_version")
implementation("io.ktor:ktor-gson:$ktor_version")
}
tasks.test {
useJUnitPlatform()
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}
application {
mainClassName = "ServerKt"
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "1.8"
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "1.8"
}
The dependency io.ktor:ktor-server-netty:1.4.0 causes the AbstractMethodError. To fix it just use the same version for all Ktor dependencies:
implementation("io.ktor:ktor-server-netty:$ktor_version")
implementation("io.ktor:ktor-client-cio:$ktor_version")
implementation("io.ktor:ktor-html-builder:$ktor_version")
implementation("io.ktor:ktor-client-serialization:$ktor_version")

Using Ktor, Kmongo and kotlinx.serialization together causing ClassCastException ... What am I doing wrong?

https://github.com/reticent-monolith/winds_server is the github repo if anyone finds it easier looking there.
I'm trying to use KMongo and Ktor with Kotlin's Serialization module but creating the MongoClient results in the following exception:
java.lang.ClassCastException: class org.litote.kmongo.serialization.SerializationCodecRegistry cannot be cast to class org.bson.codecs.configuration.CodecProvider (org.litote.kmongo.serialization.SerializationCodecRegistry and org.bson.codecs.configuration.CodecProvider are in unnamed module of loader 'app')
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry(ClassMappingTypeService.kt:83)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.codecRegistry(SerializationClassMappingTypeService.kt:40)
at org.litote.kmongo.serialization.SerializationClassMappingTypeService.coreCodecRegistry(SerializationClassMappingTypeService.kt:97)
at org.litote.kmongo.service.ClassMappingType.coreCodecRegistry(ClassMappingType.kt)
at org.litote.kmongo.service.ClassMappingTypeService$DefaultImpls.codecRegistry$default(ClassMappingTypeService.kt:79)
at org.litote.kmongo.KMongo.configureRegistry$kmongo_core(KMongo.kt:79)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:70)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:57)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:47)
at org.litote.kmongo.KMongo.createClient(KMongo.kt:39)
at com.reticentmonolith.repo.MongoDispatchRepo.<init>(MongoDispatchRepo.kt:11)
at com.reticentmonolith.ApplicationKt.<clinit>(Application.kt:14)
... 23 more
Am I missing something in my build.gradle?
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
}
}
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.5.10'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10'
id 'application'
}
group 'com.reticentmonolith'
version '0.0.1-SNAPSHOT'
mainClassName = "io.ktor.server.netty.EngineMain"
sourceSets {
main.kotlin.srcDirs = main.java.srcDirs = ['src']
test.kotlin.srcDirs = test.java.srcDirs = ['test']
main.resources.srcDirs = ['resources']
test.resources.srcDirs = ['testresources']
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
// KOTLIN
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// KTOR
implementation "io.ktor:ktor-server-netty:$ktor_version"
implementation "ch.qos.logback:logback-classic:$logback_version"
implementation "io.ktor:ktor-server-core:$ktor_version"
testImplementation "io.ktor:ktor-server-tests:$ktor_version"
implementation "io.ktor:ktor-serialization:$ktor_version"
// MONGO
implementation group: 'org.mongodb', name: 'mongo-java-driver', version: '3.12.8'
// KOTLINX SERIALIZATION
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.2.1"
// KMONGO
implementation 'org.litote.kmongo:kmongo-serialization:4.2.7'
implementation group: 'org.litote.kmongo', name: 'kmongo-id-serialization', version: '4.2.7'
// KBSON (optional dependency for KMONGO and Serialization)
implementation "com.github.jershell:kbson:0.4.4"
}
Here's my MongoClient class:
import com.mongodb.client.MongoDatabase
import com.reticentmonolith.models.Dispatch
import java.time.LocalDate
import org.litote.kmongo.*
class MongoDispatchRepo: DispatchRepoInterface {
private val client = KMongo.createClient()
private val database: MongoDatabase = client.getDatabase("zw")
private val windsData = database.getCollection<Dispatch>("winds")
override fun createDispatch(dispatch: Dispatch) {
windsData.insertOne(dispatch)
}
override fun getAllDispatches(): Map<String, List<Dispatch>> {
val list = windsData.find().toList()
return mapOf("dispatches" to list)
}
override fun getDispatchesByDate(date: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date eq date).toList())
}
override fun getDispatchesByDateRange(start: LocalDate, end: LocalDate): Map<String, List<Dispatch>> {
return mapOf("dispatches" to windsData.find(Dispatch::date gte(start), Dispatch::date lte(end)).toList())
}
override fun getDispatchById(id: Id<Dispatch>): Dispatch? {
return windsData.findOneById(id)
}
override fun updateDispatchById(id: Id<Dispatch>, update: Dispatch): Boolean {
val oldDispatch = getDispatchById(id)
if (oldDispatch != null) {
update.date = oldDispatch.date
update._id = oldDispatch._id
windsData.updateOneById(id, update)
return true
}
return false
}
override fun updateLastDispatch(update: Dispatch): Boolean {
val lastDispatch = getLastDispatch() ?: return false
update.date = lastDispatch.date
update._id = lastDispatch._id
windsData.updateOneById(lastDispatch._id, update)
return true
}
override fun deleteDispatchById(id: Id<Dispatch>) {
windsData.deleteOne(Dispatch::_id eq id)
}
override fun getLastDispatch(): Dispatch? {
val todaysDispatches = getDispatchesByDate(LocalDate.now())
if (todaysDispatches.isEmpty()) return null
return todaysDispatches["dispatches"]?.last()
}
override fun addSpeedsToLastDispatch(line4: Int?, line3: Int?, line2: Int?, line1: Int?) {
val lastDispatch = getLastDispatch() ?: return
lastDispatch.riders.get(4)?.speed = line4
lastDispatch.riders.get(3)?.speed = line3
lastDispatch.riders.get(2)?.speed = line2
lastDispatch.riders.get(1)?.speed = line1
updateLastDispatch(lastDispatch)
}
fun purgeDatabase() {
windsData.deleteMany(Dispatch::comment eq "")
}
}
And my Application.kt for Ktor:
package com.reticentmonolith
import com.reticentmonolith.models.createExampleDispatches
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.http.*
import com.reticentmonolith.repo.MongoDispatchRepo
import io.ktor.features.*
import io.ktor.serialization.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.id.serialization.IdKotlinXSerializationModule
val repo = MongoDispatchRepo()
fun main(args: Array<String>) = io.ktor.server.netty.EngineMain.main(args)
#Suppress("unused") // Referenced in application.conf
#kotlin.jvm.JvmOverloads
fun Application.module(testing: Boolean = false) {
install(ContentNegotiation) {
json(
Json { serializersModule = IdKotlinXSerializationModule },
contentType = ContentType.Application.Json
)
}
repo.purgeDatabase()
val dispatchList = repo.getAllDispatches()["dispatches"]
if (dispatchList != null && dispatchList.isEmpty()) {
createExampleDispatches(0).forEach {
repo.createDispatch(it)
println("######## date: ${it.date} ################")
}
}
routing {
get("/dispatches/") {
call.response.status(HttpStatusCode.OK)
val dispatches = repo.getAllDispatches()
println("Dispatches from Mongo: $dispatches")
println("Dispatches encoded for response: $dispatches")
}
}
}
And finally the class being serialized:
package com.reticentmonolith.models
import com.reticentmonolith.serializers.DateSerializer
import com.reticentmonolith.serializers.TimeSerializer
import kotlinx.serialization.Contextual
import org.litote.kmongo.*
import kotlinx.serialization.Serializable
import java.time.LocalDate
import java.time.LocalTime
#Serializable
data class Dispatch(
var riders: MutableMap<Int, #Contextual Rider?> = mutableMapOf(
1 to null,
2 to null,
3 to null,
4 to null
),
var comment: String = "",
var wind_degrees: Int,
var wind_speed: Double,
var winds_instructor: String,
var bt_radio: String,
var _id: #Contextual Id<Dispatch> = newId(),
// #Serializable(with=DateSerializer::class)
#Contextual var date: LocalDate = LocalDate.now(),
// #Serializable(with=TimeSerializer::class)
#Contextual var time: LocalTime = LocalTime.now()
)
fun createExampleDispatches(amount: Int): Collection<Dispatch> {
val dispatches = mutableListOf<Dispatch>()
for (i in 0..amount) {
dispatches.add(Dispatch(
bt_radio = "BT Instructor",
winds_instructor = "Winds Instructor",
wind_speed = 20.5,
wind_degrees = 186
).apply {
this.riders[2] = Rider(67, front_slider = Slider.BLACK, trolley = 125)
this.riders[3] = Rider(112, front_slider = Slider.NEW_RED, rear_slider = Slider.YELLOW, trolley = 34)
})
}
return dispatches
}
Any help would be hugely appreciated :) Thanks!

Unable to use kotlinx.serialization in multiplatform project

I am attempting to use kotlinx.serialization in a multiplatform (JVM/JS) project.
When I add #Serializable annotation to some data classes in some class in common module:
#Serializable
data class User(
val user: String
)
The build succeeds without errors, but it doesn't look like the encoders/decoders are generated.
In build/generated-src I don't see any related kotlin files which provide the encodeToString extension function, and when I try to use something like:
JSON.encodeToString(User(login = "X"))
I get an Unresolved reference error:
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline fun <reified T> StringFormat.encodeToString(value: TypeVariable(T)): String defined in kotlinx.serialization
Any help to resolve this would be appreciated. Thanks in advance.
My build.gradle.kts:
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
/* buildscript {
dependencies {
val kotlinVersion: String by System.getProperties()
classpath(kotlin("serialization", version = kotlinVersion))
}
} */
plugins {
val kotlinVersion: String by System.getProperties()
kotlin("multiplatform") version kotlinVersion
id("kotlinx-serialization") version kotlinVersion
val kvisionVersion: String by System.getProperties()
id("kvision") version kvisionVersion
}
version = "1.0.0-SNAPSHOT"
group = "tech.lorefnon"
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
maven { url = uri("https://repo.spring.io/milestone") }
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") }
mavenLocal()
}
// Versions
val kotlinVersion: String by System.getProperties()
val kvisionVersion: String by System.getProperties()
val ktorVersion: String by project
val logbackVersion: String by project
val commonsCodecVersion: String by project
val webDir = file("src/frontendMain/web")
val mainClassName = "io.ktor.server.netty.EngineMain"
kotlin {
jvm("backend") {
compilations.all {
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs = listOf("-Xjsr305=strict")
}
}
}
js("frontend") {
browser {
runTask {
outputFileName = "main.bundle.js"
sourceMaps = false
devServer = KotlinWebpackConfig.DevServer(
open = false,
port = 3000,
proxy = mapOf(
"/kv/*" to "http://localhost:8080",
"/login" to "http://localhost:8080",
"/logout" to "http://localhost:8080",
"/kvws/*" to mapOf("target" to "ws://localhost:8080", "ws" to true)
),
contentBase = listOf("$buildDir/processedResources/frontend/main")
)
}
webpackTask {
outputFileName = "main.bundle.js"
}
testTask {
useKarma {
useChromeHeadless()
}
}
}
binaries.executable()
}
sourceSets {
val commonMain by getting {
dependencies {
implementation(kotlin("stdlib-common"))
api("pl.treksoft:kvision-server-ktor:$kvisionVersion")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
}
kotlin.srcDir("build/generated-src/common")
kotlin.srcDir("src/commonMain/kotlin")
}
val commonTest by getting {
dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
}
}
val backendMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation(kotlin("reflect"))
implementation("com.auth0:java-jwt:3.11.0")
implementation("io.ktor:ktor-server-netty:$ktorVersion")
implementation("io.ktor:ktor-auth:$ktorVersion")
implementation("io.ktor:ktor-auth-jwt:$ktorVersion")
implementation("ch.qos.logback:logback-classic:$logbackVersion")
implementation("commons-codec:commons-codec:$commonsCodecVersion")
implementation("org.redisson:redisson:3.14.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.4.2")
}
}
val backendTest by getting {
dependencies {
implementation(kotlin("test"))
implementation(kotlin("test-junit"))
}
}
val frontendMain by getting {
resources.srcDir(webDir)
dependencies {
implementation("pl.treksoft:kvision:$kvisionVersion")
implementation("pl.treksoft:kvision-redux:${kvisionVersion}")
implementation("pl.treksoft:kvision-bootstrap:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-select:$kvisionVersion")
implementation("pl.treksoft:kvision-datacontainer:$kvisionVersion")
implementation("pl.treksoft:kvision-bootstrap-dialog:$kvisionVersion")
implementation("pl.treksoft:kvision-fontawesome:$kvisionVersion")
implementation("pl.treksoft:kvision-i18n:$kvisionVersion")
implementation(npm("redux-logger", "3.0.6"))
}
kotlin.srcDir("build/generated-src/frontend")
}
val frontendTest by getting {
dependencies {
implementation(kotlin("test-js"))
implementation("pl.treksoft:kvision-testutils:$kvisionVersion:tests")
}
}
}
}
fun getNodeJsBinaryExecutable(): String {
val nodeDir = NodeJsRootPlugin.apply(project).nodeJsSetupTaskProvider.get().destination
val isWindows = System.getProperty("os.name").toLowerCase().contains("windows")
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
val command = NodeJsRootPlugin.apply(project).nodeCommand
val finalCommand = if (isWindows && command == "node") "node.exe" else command
return nodeBinDir.resolve(finalCommand).absolutePath
}
afterEvaluate {
tasks {
getByName("frontendProcessResources", Copy::class) {
dependsOn("compileKotlinFrontend")
exclude("**/*.pot")
doLast("Convert PO to JSON") {
destinationDir.walkTopDown().filter {
it.isFile && it.extension == "po"
}.forEach {
exec {
executable = getNodeJsBinaryExecutable()
args(
"$buildDir/js/node_modules/gettext.js/bin/po2json",
it.absolutePath,
"${it.parent}/${it.nameWithoutExtension}.json"
)
println("Converted ${it.name} to ${it.nameWithoutExtension}.json")
}
it.delete()
}
}
}
create("frontendArchive", Jar::class).apply {
dependsOn("frontendBrowserProductionWebpack")
group = "package"
archiveAppendix.set("frontend")
val distribution =
project.tasks.getByName("frontendBrowserProductionWebpack", KotlinWebpack::class).destinationDirectory!!
from(distribution) {
include("*.*")
}
from(webDir)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
into("/assets")
inputs.files(distribution, webDir)
outputs.file(archiveFile)
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis()
)
)
}
}
getByName("backendProcessResources", Copy::class) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
getByName("backendJar").group = "package"
create("jar", Jar::class).apply {
dependsOn("frontendArchive", "backendJar")
group = "package"
manifest {
attributes(
mapOf(
"Implementation-Title" to rootProject.name,
"Implementation-Group" to rootProject.group,
"Implementation-Version" to rootProject.version,
"Timestamp" to System.currentTimeMillis(),
"Main-Class" to mainClassName
)
)
}
val dependencies = configurations["backendRuntimeClasspath"].filter { it.name.endsWith(".jar") } +
project.tasks["backendJar"].outputs.files +
project.tasks["frontendArchive"].outputs.files
dependencies.forEach {
if (it.isDirectory) from(it) else from(zipTree(it))
}
exclude("META-INF/*.RSA", "META-INF/*.SF", "META-INF/*.DSA")
inputs.files(dependencies)
outputs.file(archiveFile)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
create("backendRun", JavaExec::class) {
dependsOn("compileKotlinBackend")
group = "run"
main = mainClassName
classpath =
configurations["backendRuntimeClasspath"] + project.tasks["compileKotlinBackend"].outputs.files +
project.tasks["backendProcessResources"].outputs.files
workingDir = buildDir
}
getByName("compileKotlinBackend") {
dependsOn("compileKotlinMetadata")
}
getByName("compileKotlinFrontend") {
dependsOn("compileKotlinMetadata")
}
}
}
And gradle.properties:
javaVersion=1.8
#Plugins
systemProp.kotlinVersion=1.4.20
systemProp.serializationVersion=1.0.1
#Dependencies
systemProp.kvisionVersion=3.17.2
ktorVersion=1.4.3
commonsCodecVersion=1.10
logbackVersion=1.2.3
kotlin.mpp.stability.nowarn=true
kotlin.js.compiler=legacy
org.gradle.jvmargs=-Xmx2g
And settings.gradle.kts:
pluginManagement {
repositories {
mavenCentral()
jcenter()
maven { url = uri("https://plugins.gradle.org/m2/") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-eap") }
maven { url = uri("https://kotlin.bintray.com/kotlinx") }
maven { url = uri("https://dl.bintray.com/rjaros/kotlin") }
mavenLocal()
}
resolutionStrategy {
eachPlugin {
when {
requested.id.id == "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
requested.id.id == "kvision" -> useModule("pl.treksoft:kvision-gradle-plugin:${requested.version}")
}
}
}
}
rootProject.name = "test"
I often face same problem. You just need to add import:
import kotlinx.serialization.encodeToString
You probably don't want to add import manually, I usually start typing encodeToStr and, wait suggestions to appear and choose encodeToString(value: T), this will add needed import.
I believe it's a bug that IDE don't give you a suggestion to import, and give us this error instead.

How do I refactor a view to allow filtering of items bound to an observableArrayList inside a tornadofx app

I started with the gradle hello-world example found https://github.com:JetBrains/kotlin-examples.git and modified it to use TornadoFX.
This is an app that displays a list of items. You can add to the list, and the RequestView will automatically display all the items.
I have it working so that the items stored are bound to an observableArrayList but I now want to implement a filter using the TextView at the bottom. But, I'm struggling to understand whether this means I should create a new list which is managed internally in the RequestView, and filter from that, or how to do it.
package demo
import javafx.collections.FXCollections
import javafx.geometry.Pos
import javafx.scene.control.TextField
import javafx.scene.layout.VBox
import javafx.scene.text.FontWeight
import tornadofx.*
class helloWorldApp : App(HelloWorld::class) {
}
class HelloWorld : View() {
override val root = VBox()
var requestView: RequestView by singleAssign()
var filterField: TextField by singleAssign()
init {
with(root) {
requestView = RequestView()
this += requestView
filterField = TextField()
this += filterField
}
requestView.items.add("Hi there")
requestView.items.add("Another one")
}
}
class RequestView() : View() {
var items = FXCollections.observableArrayList<String>()
override val root = listview(items) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it) {
alignment = Pos.CENTER_LEFT
style {
fontSize = 15.px
fontWeight = FontWeight.BOLD
}
}
}
}
}
}
}
}
Here is the build.gradle file, just in case it is helpful.
buildscript {
ext.kotlin_version = '1.1.2'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = 'demo.helloWorldApp'
defaultTasks 'run'
repositories {
mavenCentral()
}
tasks.compileKotlin.kotlinOptions.jvmTarget = "1.8"
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile 'junit:junit:4.11'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
compile 'no.tornado:tornadofx:1.7.10'
}
task wrapper(type: Wrapper) {
gradleVersion = "2.7"
}
You should use the SortedFilteredList which wraps an ObservableList and accepts a predicate which is used to discriminate the entries.
There is an unfortunate coupling between your two views, so you should consider firing an event instead, but here is a working solution with minimal changes to your example. I did move the data to a model, and cleaned up the ui code, as well as got rid of the singleAssign statements and applied some best practices to the builders :)
As you can see, the SortedFilteredList has a filterWhen function that will be called whenever the textProperty() of the textfield changes.
class HelloWorldApp : App(HelloWorld::class)
class HelloWorld : View() {
val requestView: RequestView by inject()
override val root = vbox {
add(requestView)
textfield {
promptText = "Filter"
requestView.data.filterWhen(textProperty()) { query, item ->
item.contains(query, ignoreCase = true)
}
}
}
}
class ItemsModel : ViewModel() {
val items = FXCollections.observableArrayList<String>()
fun addItem(item: String) = items.add(item)
init {
addItem("Hi there")
addItem("Another one")
}
}
class RequestView() : View() {
val model: ItemsModel by inject()
val data = SortedFilteredList(model.items)
override val root = listview(data) {
cellFormat {
graphic = cache {
form {
fieldset {
label(it) {
alignment = Pos.CENTER_LEFT
style {
fontSize = 15.px
fontWeight = FontWeight.BOLD
}
}
}
}
}
}
}
}