Ruckus
06/16/2025, 3:25 PMRuckus
06/16/2025, 3:32 PMpublic operator fun <T> ObservableValue<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
public operator fun <T> WritableValue<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this.value = value
}
This allows code such as the following
// SimpleObjectProperty is a Java class provided
// by JavaFX that is a basic implementation of
// ObservableValue and WritableValue used above
val colorProperty = SimpleObjectProperty<Color>()
var color by colorProperty
However, this will say that color
is a Color
, but due to how JavaFX works, it really should be Color!
. It would be nice if this could be annotated some how, preferably on the operators in the library, but if not, at least on the property or delegate in locale code.Youssef Shoaib [MOD]
06/16/2025, 3:44 PMSimpleObjectProperty
defined? I think there could be a way to pass-on the fact that Color may be nullableRuckus
06/16/2025, 3:44 PMpublic operator fun <T> ObservableValue<T>.getValue(
thisRef: Any?,
property: KProperty<*>,
): @PlatformType T = value
public operator fun <T> WritableValue<T>.setValue(
thisRef: Any?,
property: KProperty<*>,
value: @PlatformType T,
) {
this.value = value
}
Or perhaps even
public operator fun <@PlatformType T> ObservableValue<T>.getValue(
thisRef: Any?,
property: KProperty<*>,
): T = value
public operator fun <@PlatformType T> WritableValue<T>.setValue(
thisRef: Any?,
property: KProperty<*>,
value: T,
) {
this.value = value
}
Less preferred, but still usable solutions:
val colorProperty = SimpleObjectProperty<@PlatformType Color>()
or
var color: @PlatformType Color by colorProperty
Ruckus
06/16/2025, 3:46 PMSimpleObjectProperty<Color?>
, but that's really a good solution for the same reason that Kotlin just marking everything from the platform as nullable isn't a good solution.Youssef Shoaib [MOD]
06/16/2025, 3:47 PMSimpleObjectProperty<Nothing>
and bad things happen. How does SimpleObjectProperty
work? If one can provide e.g. a property reference, then you could use that to smuggle a platform type thru I thinkYoussef Shoaib [MOD]
06/16/2025, 3:49 PMSimpleObjectProperty
spawns things from thin air, so things can always go wrong. If it was e.g. SimpleObjectProperty(SomeJavaFxClass::color)
, then I think it'll end up with a platform type and everything will work fineRuckus
06/16/2025, 3:54 PMSimpleObjectProperty
is a Java class (part of the JavaFx framework). It doesn't spawn anything from anywhere. It just stores a value and has useful functions for observation. Because it's a Java class, I can't guarantee the nullability of the stored value.Youssef Shoaib [MOD]
06/16/2025, 3:54 PMRuckus
06/16/2025, 3:55 PMYoussef Shoaib [MOD]
06/16/2025, 3:59 PMpublic operator fun <T> ObservableValue<T>.getValue(thisRef: Any?, property: KProperty<*>) = value // or getValue()
Ruckus
06/16/2025, 4:14 PMcolor::set
(i.e. is that a (Color) -> Unit
or a (Color!) -> Unit
)Ruckus
06/16/2025, 4:22 PMpublic fun <T> ObservableValue<T>.intBinding(
vararg dependencies: Observable,
func: (T) -> Int,
): IntegerBinding = createIntegerBinding({ func(value) }, this, *dependencies)
// createIntegerBinding is a static Java function from JavaFX
Without the ability to specify platform types somehow, I have to make the T
arg to the func
param either nullable or not, which again doesn't align with the underlying assumptions.Youssef Shoaib [MOD]
06/16/2025, 4:38 PMimport kotlin.reflect.KProperty
// This can exist in its own module, with explicit API turned off. It only needs to exist once
sealed interface TypeWrapper<T> {
companion object IMPL : TypeWrapper<Nothing>
}
@Suppress("UNCHECKED_CAST")
public fun <T> platformType() = PlatformType.identity(TypeWrapper.IMPL as TypeWrapper<T>)
//////////////////////
class SimpleObjectProperty<T>
public operator fun <T> SimpleObjectProperty<T>.getValue(thisRef: Any?, property: KProperty<*>): T = TODO()
public operator fun <T> SimpleObjectProperty<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
}
fun <T> SimpleObjectProperty(type: TypeWrapper<T>): SimpleObjectProperty<T> = TODO()
fun example() {
val foo by SimpleObjectProperty(platformType<String>()) // IntelliJ shows type as String!
foo.length // compiles fine!
foo!! // no warnings!
}
import org.jetbrains.annotations.NotNull;
public class PlatformType {
@NotNull
public static <T> TypeWrapper<T> identity(TypeWrapper<T> type) {
return type;
}
}
This creates a platform type from a user-provided normal type. The key is that the user needs to use platformType
to specify the type. You can also have the SimpleObjectProperty
automatically have a platform type by explicitly defining (with explicit API turned off):
fun <T> SimpleObjectProperty() = SimpleObjectProperty(platformType<T>())
fun example() {
val foo by SimpleObjectProperty<String>() // IntelliJ shows type as String!
foo.length // compiles fine!
foo!! // no warnings!
}
so every platform type introduction will need some explicit-API-prohibited method, but after that type has been smuggled, it can be used wherever you'd like just fine.
Is there error suppression for explicit API?Ruckus
06/16/2025, 4:45 PMSimpleObjectProperty
, but any observable or writable values. There are many different implementation of those interfaces (ObservableValue
and WritableValue
) including anonymous object implementations common for the JavaFX CSS integration, so I couldn't override them all if I wanted to.Ruckus
06/16/2025, 4:47 PMYoussef Shoaib [MOD]
06/16/2025, 5:23 PM@Suppress("UNCHECKED_CAST")
private fun <T> SimpleObjectProperty<*>.asType(type: TypeWrapper<T>) = this as SimpleObjectProperty<T>
public fun <T> SimpleObjectProperty<T>.toPlatformType() = asType(platformType<T>())
fun example() {
val foo by SimpleObjectProperty<String>().toPlatformType() // IntelliJ shows type as String!
foo.length // compiles fine!
foo!! // no warnings!
}
Make such a toPlatformType
for every interface that's used. It's annoying, but it's only a one-time costRuckus
06/16/2025, 5:33 PMPlatformType.java
class doesn't always work
2: Unless I'm missing something, it appears to still only addresses the property delegation calls, and not any other uses (such as the binding extension example I gave)Youssef Shoaib [MOD]
06/16/2025, 5:35 PMObservableValue
was converted to a platform type ObservableValue<T!>
, then the lambda should have the right typeRuckus
06/16/2025, 5:39 PMRuckus
06/16/2025, 5:42 PMPlatformType.whatever
class equivalent for whatever platform you're targeting is an annoying bit of boilerplate in an otherwise Kotlin only project.Youssef Shoaib [MOD]
06/16/2025, 5:47 PMPlatformType
class only is for your private implementation to easily create `TypeWrapper`s of platform types. You can make that private, or write it once in a library. The actual cost per type is this pattern:
@Suppress("UNCHECKED_CAST")
private fun <T> SimpleObjectProperty<*>.asType(type: TypeWrapper<T>) = this as SimpleObjectProperty<T>
public fun <T> SimpleObjectProperty<T>.toPlatformType() = asType(platformType<T>())
Which can probably be generated automatically. The point then is the user chooses to use a platform type by writing toPlatformType
, and call resolution takes care of finding the right one. You need 2 such methods for every type you want to support, but subtypes are fine btw, except that they'll be cast to whatever supertype has toPlatformType
defined on it.
So you'd define it for ObservableValue
, WriteableValue
, etc, and the user just needs to use it when possible.Ruckus
06/16/2025, 5:49 PM