Via Delegates.observable, Kotlin permits observable properties. I need, however, the ability of adding observers at runtime, as Java's Observable class does.
What I have now, is the following:
import java.util.*
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0
import kotlin.reflect.jvm.isAccessible
class MyObservable<T> (var v: T): java.util.Observable() {
operator fun getValue(thisRef: Any, prop: KProperty<*>) = v
operator fun setValue(thisRef: Any, prop: KProperty<*>, newValue: T) {
v = newValue
setChanged()
notifyObservers()
}
}
fun <T> addObserver(prop: KProperty0<T>, observerFn: (T) -> Unit) =
(prop.apply{ isAccessible = true }.getDelegate() as MyObservable<T>)
.addObserver(Observer({ o, _ -> observerFn((o as MyObservable<T>).v) }))
class ObservableExample {
var i: Int by MyObservable(3)
}
fun main(args: Array<String>) {
val ex: ObservableExample = ObservableExample();
addObserver(ex::i, { println(it) })
ex.i = 7
ex.i = 9
// prints:
// 7
// 9
}
It works, but it feels like reinventing the wheel.
Isn't there a standard solution for this?
If not, is what I've done correct?
A slightly shorter variant of the same idea:
import kotlin.properties.Delegates
typealias IntObserver = (Int) -> Unit
class ObservableExample {
val prop1Observers = mutableListOf<IntObserver>()
var prop1: Int by Delegates.observable(0) { prop, old, new ->
prop1Observers.forEach { it(new) }
}
}
fun main(args: Array<String>) {
val example = ObservableExample()
example.prop1Observers.add({ println(it) })
example.prop1 = 1
example.prop1 = 2
}
The output is as expected. Probably, it is better to make observers property private and add a method to add subscribers but I omitted it for the simplicity.
This is because you starts with a simple example, and can't find the benefits of Kotlin delegated properties.
Kotlin doesn't forcing you to implements any interface to supports delegated properties, yon can using delegated properties in Kotlin just provide getValue & setValue(?) operators. and their visibility even can be private.
Kotlin provided a provideDelegate operator function since 1.1, that let you manage/control how to create a delegate.
The delegate in Kotlin is working in the background, which means it is invisible from the source code point of view, and let the code source treat a delegated properties as a regular properties.
Kotlin delegated properties can easily let you manage java beans without using PropertyEditorSupport in Java, and you don't need to manage the delegate at all in Kotlin, just to notify the changed property only. for example:
val history = mutableMapOf<String, MutableList<Pair<Any?, Any?>>>()
val subject = Subject()
subject.subscribe { event ->
val each = history.getOrPut(event.propertyName) { mutableListOf() }
each.add(event.oldValue to event.newValue)
}
// v--- treat a delegated property as regular property
subject.number = 1
subject.string = "bar"
subject.number = 2
println(history);
// ^--- {"number":[<null,1>,<1,2>], "string": [<null,"bar">]}
Note: the getValue & setValue operator functions private below.
class Subject {
// v--- manage the delegated property internally
var string: String? by this
var number: Int? by this
private val properties by lazy {
mutableMapOf<Any?, Any?>()
}
private val listeners by lazy {
mutableListOf<PropertyChangeListener>()
}
private operator #Suppress("UNCHECKED_CAST")
fun <T : Any?> getValue(self: Any, prop: KProperty<*>): T {
return properties[prop.name] as T
}
private operator
fun <T : Any?> setValue(self: Any,prop: KProperty<*>, newValue: T) {
val event = PropertyChangeEvent(
self,
prop.name,
properties[prop.name],
newValue
)
properties[prop.name] = newValue
listeners.forEach { it.propertyChange(event) }
}
fun subscribe(listener: (event: PropertyChangeEvent) -> Unit) {
subscribe(PropertyChangeListener { listener(it) })
}
fun subscribe(subscriber: PropertyChangeListener) {
listeners.add(subscriber)
}
}
Related
I was told that MutableState just like MutableLiveData in Kotlin, and MutableState fit Compose, MutableLiveDataenter code here fit XML layout.
In Code A, I need to assign data to bb.value, but why do I assign directly to aa ?
Code A
private var aa by mutableStateOf(-1)
private var bb= MutableLiveData<Int>(-1)
fun onEditDone() {
aa = 2
bb.value = 2
}
It's because of Kotlin's delegation feature where you delegate values using by keyword.
Simple implementation for remember and mutableState, to display how it works when you build something similar to that, is as
// Delegation Functions for setting and getting value
operator fun <T> State<T>.getValue(thisObj: Any?, property: KProperty<*>): T = value
operator fun <T> MutableState<T>.setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
/*
* State
*/
interface State<out T> {
val value: T
}
interface MutableState<T> : State<T> {
override var value: T
}
class MutableStateImpl<T>(value: T) : MutableState<T> {
override var value: T = value
}
fun <T> mutableStateOf(value: T): MutableState<T> = MutableStateImpl(value)
/*
* Remember
*/
inline fun <T> remember(calculation: () -> T): T {
return calculation()
}
And you can use it as
fun main() {
val isSelected: MutableState<Boolean> = remember { mutableStateOf(true) }
isSelected.value = false
var selected by remember { mutableStateOf(false) }
selected = false
}
I started using the library for routes in an android application. There was such a problem, I don't know how to solve it.
Type mismatch.
Required:
Creator<Context, Intent>
Found:
() → Intent
Library used Cicerone. I created my object class Screens and according to some sources made an implementation like this
import com.csproject.rflex.app.App
import com.csproject.rflex.presentation.launch.LaunchActivity
import com.github.terrakok.cicerone.androidx.ActivityScreen
object Screens {
fun launch() = ActivityScreen {
LaunchActivity.newIntent(App.instance.getAppContext())
}
}
Activity code fragment
class LaunchActivity: ABaseActivity(), ILaunchView {
companion object{
fun newIntent(context: Context) = Intent(context, LaunchActivity::class.java)
}
Libriry class
sealed class AppScreen : Screen
fun interface Creator<A, R> {
fun create(argument: A): R
}
open class FragmentScreen #JvmOverloads constructor(
private val key: String? = null,
private val fragmentCreator: Creator<FragmentFactory, Fragment>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
}
open class ActivityScreen #JvmOverloads constructor(
private val key: String? = null,
private val intentCreator: Creator<Context, Intent>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
open val startActivityOptions: Bundle? = null
fun createIntent(context: Context) = intentCreator.create(context)
}
UPDATE #1
I modified the code a bit, but I'm not sure if it should work this way
fun launch(context: Context) = ActivityScreen (intentCreator = object: Creator<Context, Intent> {
override fun create(argument: Context): Intent {
return MainActivity.newIntent(context)
}
})
It works, but I think you can do it differently
Your lambda is getting its context externally rather than from a lambda argument, so its signature doesn't match the signature of your interface.
Try this:
ActivityScreen { LaunchActivity.newIntent(it) }
If it isn't able to infer the context from that, I guess you would need:
ActivityScreen { context: Context -> LaunchActivity.newIntent(context) }
class ModelFactory {
fun setA() : ModelFactory {
// blabla...
}
fun setB() : ModelFactory {
// blabla...
}
fun setC() : ModelFactory {
// blabla...
}
fun build() : Model {
// An error occurs if any of setA, setB, and setC is not called.
}
}
//example
fun successTest() {
ModelFactory().setA().setB().setC().build() // No error occurs at compile time
}
fun failTest() {
ModelFactory().setA().build() // An error occurs at compile time because setB and setC are not called.
}
It's awkward grammatically, but I think it's been expressed what I want.
I have already implemented an error-raising runtime for this requirement, but I want to check this at compile time.
If possible, I think I should use annotations. But is this really possible at compile time?
With Kotlin, I have been avoiding builder pattern, as we can always specify default values for non-mandatory fields.
If you still want to use a builder pattern, you can use Step builder pattern that expects all mandatory fields to be set before creating the object. Note that each setter method returns the reference of next setter interface. You can have multiple Step builders based on the combination of mandatory fields.
class Model(val a: String = "", val b: String = "", val c: String = "")
class StepBuilder {
companion object {
fun builder(): AStep = Steps()
}
interface AStep {
fun setA(a: String): BStep
}
interface BStep {
fun setB(b: String): CStep
}
interface CStep {
fun setC(c: String): BuildStep
}
interface BuildStep {
//fun setOptionalField(x: String): BuildStep
fun build(): Model
}
class Steps : AStep, BStep, CStep, BuildStep {
private lateinit var a: String
private lateinit var b: String
private lateinit var c: String
override fun setA(a: String): BStep {
this.a = a
return this
}
override fun setB(b: String): CStep {
this.b = b
return this
}
override fun setC(c: String): BuildStep {
this.c = c
return this
}
override fun build() = Model(a, b , c)
}
}
fun main() {
// cannot build until you call all three setters
val model = StepBuilder.builder().setA("A").setB("B").setC("C").build()
}
I am trying to understand lambdas and Kotlin. I created this trivial example
interface OnClickListener {
fun onClick(s: String)
}
class Button {
var clickListener: OnClickListener? = null
fun setOnClickListener(listener: OnClickListener?) {
clickListener = listener
}
fun click() {
clickListener?.onClick("hello")
}
}
fun main(args: Array<String>) {
val b = Button()
b.setOnClickListener(
object : OnClickListener {
override fun onClick(s: String) {
println(s)
}
}
)
/*
Variation 1
val l = {
s -> println(s)
}
b.clickListener = l*/
/*
Variation 2
b.setOnClickListener{
s -> println(s)
}
*/
/*
Variation 3
b.clickListener = {
s -> println(s)
}
*/
b.click()
}
So the above code only compiles if I pass an anonymous object. But I wanted to figure out how to use the lambdas.
None of the 3 variation to use a lambda compiles.
I thought since the OnClickListener is a SAM I should easily be able to pass in a lambda
What am I doing wrong here?
To be able to use a lambda, you need to use a Java interface.
First, create a Java file and create an interface:
public interface OnClickListener {
void onClick(String s);
}
Then in your main:
b.setOnClickListener(OnClickListener { s ->
println(s)
})
As for your Button class:
class Button {
var clickListener: OnClickListener? = null //You can use this too but there's another way as well.
//lateinit var clickListener: OnClickListener //Telling the compiler that you will initialize it later on.
fun setOnClickListener(listener: OnClickListener) { //removed redundant ? from the function signature.
clickListener = listener
}
fun click() {
clickListener?.onClick("hello") //Incase of lateinit, you don't need a '?' anymore
}
}
SAM conversion only works between a Java code and a Kotlin code.
EDIT: Since in Kotlin, you can store a function in a variable as well, here is my another two cents on how you can do it in a different way:
class Button {
lateinit var myFunction: (String) -> Unit
fun setOnClickListener(block : (String) -> Unit) {
myFunction = block //storing state of your 'listener'
}
fun onClick() = myFunction.invoke("Invoked from onClick function")
}
Then in your main:
fun main() {
val button = Button()
button.setOnClickListener { s ->
println(s)
}
button.onClick()
}
As Taseer Ahmad points out, SAM conversion only works for Java interfaces since Kotlin already has proper function types. Of course, an easy way around this is to simply define a second setOnClickListener method that takes a function type
class Button {
var clickListener: OnClickListener? = null
fun setOnClickListener(listener: OnClickListener?) {
clickListener = listener
}
inline fun setOnClickListener(crossinline listener: (String) -> Unit) {
setOnClickListener(object : OnClickListener {
override fun onClick(s: String) = listener(s)
})
}
fun click() {
clickListener?.onClick("hello")
}
}
This then allows you to write b.setOnClickListener { println(it) }. I always inline methods like this as a habit, but it's not really required, so you can remove the inline and crossinline if you want.
I'm trying to get my head around property delegates, and I have an interesting use case. Is it possible to have something like this:
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by MapDelegate(properties, "font-size")
}
That would allow me to store fontSize using the map as a delegate, but with a custom key (i.e. "font-size").
The specific use case if for storing things like CSS property tags that can be accessed through variables (fontSize) for use in code, but can be rendered properly when iterating through the map (font-size: 18px;).
The documentation on the delegated properties is a good source of information on the topic. It probably is a bit longer read than the following examples:
fun <T, TValue> T.map(properties: MutableMap<String, TValue>, key: String): ReadOnlyProperty<T, TValue> {
return object : ReadOnlyProperty<T, TValue> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[key]!!
}
}
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by map(properties, "font-size")
}
You can ease up things a little bit and avoid typing the CSS property name by converting Kotlin property names to CSS attributes equivalents like so:
fun <T, TValue> map(properties: Map<String, TValue>, naming:(String)->String): ReadOnlyProperty<T, TValue?> {
return object : ReadOnlyProperty<T, TValue?> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}
object CamelToHyphen : (String)->String {
override fun invoke(camelCase: String): String {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase)
}
}
fun <T, TValue> T.cssProperties(properties: Map<String,TValue>) = map(properties, CamelToHyphen)
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any? by cssProperties(properties)
}
The above sample uses Guava's CaseFormat.
If you'd like to have mutable property your delegate will have to implement setter method:
fun <T, TValue> map(properties: MutableMap<String, TValue?>, naming: (String) -> String): ReadWriteProperty<T, TValue?> {
return object : ReadWriteProperty<T, TValue?> {
override fun setValue(thisRef: T, property: KProperty<*>, value: TValue?) {
properties[naming(property.name)] = value
}
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}