How to write custom chaining - kotlin

I want to create custom chainig to prevent some repetition.
I am a bit unclear on how to define it
I have a lot of :
someLiveData.observe(this) { objectWithTextAndVisibility->
textView.text = objectWithTextAndVisibility.text
textView.visibility = objectWithTextAndVisibility.visibility
}
i want to write something that will look as follows
someLiveData.observe(this).bind(textView).on(text)
and it will do the same thing
is there a way to define this ?

If I understood your question correctly, a generic structure like this may achieve what you want to do:
infix fun <T> LiveData<T>.observe(owner: LifecycleOwner) = LiveDataHolder(this, owner)
class LiveDataHolder<T>(val liveData: LiveData<T>, val owner: LifecycleOwner)
infix fun <T, S> LiveDataHolder<T>.bind(subject: S) = LiveDataBinder(this, subject)
class LiveDataBinder<T, S>(val liveDataHolder: LiveDataHolder<T>, val subject: S)
infix fun <T : VisibilityCarrier> LiveDataBinder<T, TextView>.on(textSelector: (T) -> String) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.text = textSelector(it)
subject.visibility = it.visibility
}
}
interface VisibilityCarrier {
val visibility: Int
}
This should give you good flexibility and prevent a lot of repetition, however at the cost of adding some obscurity to your code base, and not conveying what it does right alway.
Additional and different behaviors can be added by implementing new LiveDataBinder extension functions, like the on function, even for different kinds of objects and expected interfaces (instead of only this combination of TextView and VisibilityCarrier, even by maintaining the on name) and so on.
The use style can vary a lot with this, being like the ones below (considering Data implements VisibilityCarrier interface and provides a text String property):
liveData.observe(this).bind(textView).on { it.text }
liveData.observe(this) bind textView on { it.text }
liveData.observe(this).bind(textView).on(Data::text)
liveData.observe(this) bind textView on Data::text
This on implementation also allows for defining which property to use as text and different classes as well, like so:
anotherLiveData.observe(this).bind(textView).on(AnotherData::someText)
anotherLiveData.observe(this).bind(textView).on(AnotherData::anotherText)
UPDATE: After reading the explanation in the comments I think I got it, and believe this should address the point (where Data contains text and visibility properties):
infix fun <T : Data, S : View> LiveDataBinder<T, out S>.on(textProperty: KMutableProperty1<S, in String>) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
textProperty.set(subject, it.text)
subject.visibility = it.visibility
}
}
In the case of TextView and other Java defined classes, compiler will complain about synthetic access, and this problem could be addressed like in the following snippet (Kotlin views should be fine without this kind of workaround):
var TextView.text_: CharSequence
get() = text
set(value) { text = value }
And the usage would be like:
liveData.observe(this).bind(textView).on(TextView::text_)
liveData.observe(this).bind(customView).on(CustomView::someText)
UPDATE 2: A better approach as suggested by Tenfour04 (thanks). The following will avoid the synthetic property access compiler error:
infix fun <T : Data, S : View> LiveDataBinder<T, S>.on(textProperty: S.(String) -> Unit) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.textProperty(it.text)
subject.visibility = it.visibility
}
}
And the usage would be like (without needing the text_ property extension):
liveData.observe(owner).bind(textView).on(TextView::setText)

After reading your comments on the other answer, I think I see what you're trying to do.
If I'm correct, you want to use the builder pattern to first bind something that is set (a setter), and then specify a getter/mapper of the data type to get some sub-data type that is applied with that setter.
So you can set up a couple of intermediate classes to do it like this:
fun <T> LiveData<T>.observe(owner: LifecycleOwner) = BindableObserver<T>().also { observe(owner, it) }
class BindableObserver<D>: Observer<D> {
private var boundSetter: BoundSetter<D, *>? = null
fun <S> bind(setter: (S)->Unit) = BoundSetter<D, S>(setter).also { boundSetter = it }
override fun onChanged(t: D) {
boundSetter?.execute(t)
}
}
class BoundSetter<D, S>(private val setter: (S)->Unit) {
private var dataGetter: ((D)->S)? = null
fun on(getter: (D)->S) {
dataGetter = getter
}
fun execute(newValue: D) {
val subData = dataGetter?.invoke(newValue) ?: return
setter.invoke(subData)
}
}
You can't simply pass a TextView to bind, because Kotlin won't know which property of TextView to set, so you pass the property using property syntax (::). Unfortunately, TextView has a bunch of setText() overloads, so you have to specify the input type as well.
Usage syntax would be like this:
someLiveData.observe(this)
.bind<String>(textView::setText)
.on(ObjectWithTextAndVisibility::text)
To avoid the need for specifying which function of a TextView to bind, you could add a helper function:
fun <D> BindableObserver<D>.bind(textView: TextView) = bind<String>(textView::setText)
and then usage would be closer to what you suggested:
someLiveData.observe(this)
.bind(textView)
.on(ObjectWithTextAndVisibility::text)
You could also use lambda syntax:
someLiveData.observe(this)
.bind(textView)
.on { it.text }

Related

kotlin - unknown syntax in Jetpack Owl Example

trying to find some direction on how to select multiple items in a lazy column, I have found the following code in Owl Jetpack Compose example (Onboarding.kt).
...
val (selected, onSelected) = remember { mutableStateOf(false) }
...
Even if I'm able to use the code by myself, I really not able to decode the syntax of this val declaration.
I wasn't able to find anything in kotlinlang.org site (the nearest topic I've found is about Destructuring declarations).
Could someone help me to understand it and/or point me to relevant documentation?
This syntax is Destructuring as you mentioned in question which is expclicitly as
val (selected: Boolean, onSelected: (Boolean) -> Unit) = remember { mutableStateOf(false) }
Which requires 2 components of targetted class.
(val num1:Int, val num2:Int) = Pair(1,2) is an example
With MutableState
#Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
You need to add T type and a lambda that takes T as param and returns unit.
And inside SnapshotImpl source code of MutableState it's used as
override operator fun component2(): (T) -> Unit = { value = it }
so anything you set using this lambda is assigned to value T

Generic inline function

Let's say I have an object which helps me to deserialize other objects from storage:
val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()
The methods getBookList and getPersonList are extension functions I have written. Their logic is allmost the same so I thought I may can combine them into one method. My problem is the generic return type. The methods look like this:
fun DataInput.getBookList(): MutableList<Book> {
val list = mutableListOf<Book>()
val size = this.readInt()
for(i in 0 .. size) {
val item = Book()
item.readExternal(this)
list.add(item)
}
return list
}
Is there some Kotlin magic (maybe with inline functions) which I can use to detect the List type and generify this methods? I think the problem would be val item = T() which will not work for generic types, right? Or is this possible with inline functions?
You cannot call the constructor of a generic type, because the compiler can't guarantee that it has a constructor (the type could be from an interface). What you can do to get around this though, is to pass a "creator"-function as a parameter to your function. Like this:
fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
for(i in 0 .. size) {
val item = createT()
/* Unless readExternal is an extension on Any, this function
* either needs to be passed as a parameter as well,
* or you need add an upper bound to your type parameter
* with <T : SomeInterfaceWithReadExternal>
*/
item.readExternal(this)
list.add(item)
}
return list
}
Now you can call the function like this:
val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)
Note:
As marstran mentioned in a comment, this requires the class to have a zero-arg constructor to work, or it will throw an exception at runtime. The compiler will not warn you if the constructor doesn't exist, so if you pick this way, make sure you actually pass a class with a zero-arg constructor.
You can't initialize generic types, in Kotlin or Java. At least not in the "traditional" way. You can't do this:
val item = T()
In Java, you'd pass a Class<T> and get the constructor. Very basic example of that:
public <T> void x(Class<T> cls){
cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}
You could do the same in Kotlin, but Kotlin has a reified keyword that makes it slightly easier. This requires an inline function, which means you'd change your function to:
inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
val list = mutableListOf<T>() // Use T here
val size = this.readInt()
for(i in 0 .. size) {
// This is where the initialization happens; you get the constructor, and create a new instance.
// Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
val item = T::class.java.getConstructor().newInstance()!!
item.readExternal(this) // However, this is tricky. See my notes below this code block
list.add(item)
}
return list
}
However, readExternal isn't present in Any, which will present problems. The only exception is if you have an extension function for either Any or a generic type with that name and input.
If it's specific to some classes, then you can't do it like this, unless you have a shared parent. For an instance:
class Book(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
class Person(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
Would not work. There's no shared parent except Any, and Any doesn't have readExternal. The method is manually defined in each of them.
You could create a shared parent, as an interface or abstract class (assuming there isn't one already), and use <reified T : TheSharedParent>, and you would have access to it.
You could of course use reflection, but it's slightly harder, and adds some exceptions you need to handle. I don't recommend doing this; I'd personally use a superclass.
inline fun <reified T> DataInput.getBookList(): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
val method = try {
T::class.java.getMethod("readExternal", DataInput::class.java)
}catch(e: NoSuchMethodException){
throw RuntimeException()
}catch(e: SecurityException){
throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
// The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
// These two catch them
}
for(i in 0 .. size) {
val item: T = T::class.java.getConstructor().newInstance()!!
method.invoke(item, this)
list.add(item)
}
return list
}

Can we achieve compile-time type safety for a union of types we can't control?

Let's say I have a function:
fun doSomething(vararg pairs: Pair<String, *>) {
// Do things with the pairs
}
The problem with this approach is that it allows any type for the second half of Pair (e.g. Pair<String, CustomType1>).
What if I only want to allow a finite number of types, how would I achieve that?
If the function had a simpler signature, I could achieve the restriction via overload, like so:
fun doSomethingSimpler(param: Boolean) {
// Boolean implementation
}
fun doSomethingSimpler(param: Int) {
// Int implementation
}
// etc.
If the restricted type "set" was in my control, I could use an interface or a sealed class to achieve this. E.g.
sealed class Root
class Child1 : Root()
class Child2 : Root()
fun doSomethingICanControl(param: Root) {
// Root implementation
}
Yet what if I don't have control over the types or they are primitive, how do I prevent * from allowing everything through?
I know I could use smart-casts to get run-time safety, but can this be done at compile time?
Or does the language disallow it?
Edit 1
I know I could create my own box types (e.g. MyBoolean) and use a common interface or sealed class, but that would be boilerplate that everyone would have to write every time they needed to.
Edit 2
To be clear, I'd like to be able to make an invocation like so:
doSomething(
"key1" to false,
"key2" to "value2",
"key3" to 86
)
... I.e. Have a mixed set of "second" (of Pair) types.
So to sum it quickly up:
You want to call methods from a library that expects Pair<String, *>,
but limit the possible values that * can be.
TL;DR: What you are trying to accomplish is not possible without some kind of wrapper, because
We have no Sum-Types in Kotlin, so no way to tell the compiler that you expect an Int or a Double or a Float and nothing else
If a library-method expects something to be Pair<String, *>, there is no way for us to tell the compiler, that we just want to be able to give it a String instead of *
One way to get that behaviour is to create a Decorator (Decorator Pattern), e.g. create your own extension methods that allow only a subset
class Foo {
//Allows everything
fun doSomething(param: Pair<String, *>)
}
//Now lets create our own extension methods
fun Foo.doSomethingWithInt(param: Pair<String, Int>)
fun Foo.doSomethingWithBoolean(param: Pair<String, Boolean>)
fun Foo.doSomethingWithString(param: Pair<String, String>)
Or if you dont want to be able to call Foo.doSomething() you
can create a Decoractor-Class:
class FooDecorator {
val foo = Foo()
fun doSomething(param: Pair<String, Int>) { }
}
And the following example is not possible without some kind of Wrapper, because there are no Sum-Types in Kotlin:
doSomething(
"key1" to false,
"key2" to "value2",
"key3" to 86
)
What you could do is something like:
At first, create your own JSONItem type and add Extension-Methods to Types that can be used as one
class JSONItem<T> private constructor (item: T)
fun Int.asJSONItem() = JSONItem(this)
fun String.asJSONItem() = JSONItem(this)
fun Boolean.asJSONItem() = JSONItem(this)
Then you are able to do something like that:
//Your own personal doSomething
fun doSomething(varargs: param: Pair<String, JSONItem>) {
//Call the real doSomething()
doSomething(param.map { Pair(it.first, it.second.item) }}
}
doSomething(
"key1" to false.asJSONItem(),
"key2" to "value2".asJSONItem(),
"key3" to 86.asJSONItem()
)
Denotable union and intersection types are not currently supported in Kotlin (as of 1.1.x).
This is the relevant issue.

How can I assign a value to KMutableProperty parameter?

In a method I would like to receive KMutableProperty as parameter and assign a value to it.
Another question is what is the correct way of passing a parameter into such a method.
Basically I would like to have something like that:
class MyBinder {
...
fun bind(property: KMutableProperty<Int>): Unit {
property.set(internalIntValue)
}
}
And then call it in another class
myBinder.bind(this::intProperty)
Kotlin 1.0 does not allow the this::intProperty syntax, but this is being worked currently and will be available soon as a part of the early access preview of 1.1 (issue, KEEP proposal).
With this in mind, I'd consider doing what you're describing in another way, for example making bind accept a lambda which sets the property:
class MyBinder {
fun bind(setProperty: (Int) -> Unit) {
setProperty(internalIntValue)
}
}
...
myBinder.bind { intProperty = it }
Anyway, to answer your question about setting the value of KMutableProperty: to set the value of some property or, technically speaking, to invoke the property setter, you should know its arity, or the number of parameters that property (and its getter/setter) accepts. Properties declared in a file do not accept any parameters, member properties and extension properties require one parameter (the receiver instance), while member properties which are also extensions take two parameters. These kinds of properties are represented by the following subtypes of KMutableProperty respectively: KMutableProperty0, KMutableProperty1, KMutableProperty2 -- the number means the arity and their generic type parameters mean the types of receivers. Each of these property types has a set method with the corresponding parameters. Some examples:
fun setValue(property: KMutableProperty0<Int>, value: Int) {
property.set(value)
}
fun setValue(property: KMutableProperty1<SomeType, Int>, instance: SomeType, value: Int) {
property.set(instance, value)
}
Note that there's no set (or get) method in the abstract KMutableProperty interface precisely because it's impossible to declare it, not knowing the number of required receiver parameters.
Additionally to Alexander's answer, you can try something like this:
import kotlin.reflect.KMutableProperty
class Binder {
val internalIntValue = 10
fun bind(self: Any, aProperty: KMutableProperty<Int>) {
aProperty.setter.call(self, internalIntValue)
}
}
class Foo {
var bar = 1
fun changeBar() {
Binder().bind(this, Foo::bar)
}
}
fun main(args: Array<String>) {
val foo = Foo()
assert(1 == foo.bar)
foo.changeBar()
assert(10 == foo.bar)
}
A more robust/safe way to do the same thing:
fun <T> bind(self: T, aProperty: KMutableProperty1<T, Int>) {
aProperty.set(self, internalIntValue)
}
My thanks to Alexander. His answer gave me the previous idea.

Wrong "this" being used in nested closures

I'm trying to keep this minimal, but let me know if I'm being too minimal.
Suppose you have a class hierarchy like this, designed for generating HTML (inspired by the Kotlin tutorial; semi-pseudocode follows):
class Tag {
protected val children = arrayListOf<Tag>()
operator fun String.unaryPlus() = children.add(Text(this))
}
class TagWithChildren : Tag() {
fun head(init: Head.() -> Unit) = initializeTag(Head(), init)
fun script(init: Script.() -> Unit) = initializeTag(Script(), init)
fun <T : Tag> initializeTag(tag: T, init: T.() -> Unit): T {
tag.init()
children.add(tag)
return tag
}
}
class Head : TagWithChildren()
class Script : Tag()
class Text(val str: Text) : Tag()
Notice that Head has head and script methods while Script doesn't.
Now you can construct a template that looks like this:
head {
script {
+"alert('hi');"
}
}
Which works great! However, if the block passed to script tries to call methods that aren't available on Script, it can call the method on Head instead. For example,
head {
script {
script {
+"alert('hi');"
}
}
}
not only isn't a compile error, it's actually equivalent to
head {
script {
}
script {
+"alert('hi');"
}
}
which is super confusing, from a template author's perspective.
Is there any way to prevent method lookups from traveling up the scope like that? I only want it to look at the innermost scope.
UPDATE 11/24/2016:
Kotlin 1.1-M03 has introduced scope control, which I believe solves exactly this problem. https://blog.jetbrains.com/kotlin/2016/11/kotlin-1-1-m03-is-here/
The current behavior is intentional. Code in a lambda has access to receivers of all enclosing scopes. It is possible that a future version of Kotlin will add a modifier that will restrict a lambda with receiver to calling methods on that receiver only and not the enclosing scopes, but in the current version there's no way to change that behavior.
As a workaround, I can have it fail at runtime if I change the classes to look like this:
open class Tag {
operator fun String.unaryPlus()
// pulled up from TagWithChildren, call protected method
fun head(init: Head.() -> Unit) = addChild(Head())
fun script(init: Script.() -> Unit) = addChild(Head())
// throws in Tag
open protected fun addChild(t: Tag) = throw IllegalArgumentException()
}
class TagWithChildren : Tag() {
// overridden to not throw in subclass
protected override fun addChild(t: Tag) = children.add(t)
}
This way, every Tag has the builder methods (solving the scoping problem), but actually calling them may result in a runtime failure.