Create instance in Kotlin and only set property if not null - kotlin

I have a data class that looks like this:
data class MyDataCass(val x: String, val y: String,
val something: Something = Something(),
val somethingElse : SomethingElse = SomethingElse())
Since this class is going to be used from both Kotlin and Java I've also created a builder to make it easier to construct it from Java. The problem is that something and somethingElse are optional and I'd like to avoid duplication of the default values. This is essentially what I've tried:
data class MyDataCass(val x: String, val y: String,
val something: Something = Something(),
val somethingElse : SomethingElse = SomethingElse()) {
class Builder() {
private lateinit var x: String
private lateinit var y: String
private var something: Something? = null
private var somethingElse: SomethingElse? = null
// builder functions to set the properties defined above
...
fun build() = MyDataCass(x, y, something = ??, somethingElse = ??)
}
}
What I'd like to do is to set something and somethingElse only if they've been defined by the builder (i.e. they are not null), otherwise I'd like to use the default values as defined by the data class constructor. I could of course change the build function to look like this:
fun build() = if (something == null && somethingElse == null) {
MyDataCass(x, y)
} else if(somethingElse == null) {
MyDataCass(x, y, something = something)
} else {
MyDataClass(x,y, somethingElse = somethingElse)
}
}
but I find this to be quite verbose and error prone. Is there a less verbose or more idiomatic way of achieving this?

This is not supported very well by the language, if there was a good solution to this question that would help you too.
With that said, here's what I could come up with that lets you have the default values declared just once - at the cost of duplicating other code, however:
data class MyDataCass(val x: String, val y: String,
val something: Something,
val somethingElse: SomethingElse) {
companion object {
operator fun invoke(x: String, y: String,
something: Something? = null,
somethingElse: SomethingElse? = null) =
MyDataCass(x, y,
something ?: Something(),
somethingElse ?: SomethingElse())
}
class Builder {
private lateinit var x: String
private lateinit var y: String
private var something: Something? = null
private var somethingElse: SomethingElse? = null
// builder functions to set the properties defined above
// ...
fun build() = MyDataCass(x, y, something, somethingElse)
}
}
This way you can keep using the builder from Java, and in Kotlin the syntax that looks like is a call to the constructor is instead a call to the invoke function of the companion object, which has the default values. Your data class of course still has non-nullable types so that it's easier to use.

Related

Why can the author reassign a new object to a val via update?

The Code A is from offical sample code here.
The private val _uiState is val, in my mind, a val can be only assigned a object for one time.
It seems that _uiState.update { it.copy(loading = true) } shows _uiState is assigned to a new object again by update.
I don't understand why the author can reassign a new object to a val via update, could you tell me?
Code A
data class InterestsUiState(
val topics: List<InterestSection> = emptyList(),
val people: List<String> = emptyList(),
val publications: List<String> = emptyList(),
val loading: Boolean = false,
)
class InterestsViewModel(
private val interestsRepository: InterestsRepository
) : ViewModel() {
// UI state exposed to the UI
private val _uiState = MutableStateFlow(InterestsUiState(loading = true))
val uiState: StateFlow<InterestsUiState> = _uiState.asStateFlow()
private fun refreshAll() {
_uiState.update { it.copy(loading = true) }
...
}
...
}
data class InterestsUiState(
val topics: List<InterestSection> = emptyList(),
val people: List<String> = emptyList(),
val publications: List<String> = emptyList(),
val loading: Boolean = false,
)
/**
* Updates the [MutableStateFlow.value] atomically using the specified [function] of its value.
*
* [function] may be evaluated multiple times, if [value] is being concurrently updated.
*/
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
Added Content
To Chaoz: Thanks!
But all members in data class InterestsUiState(...val loading: Boolean = false)is val type, and you can't change any member vaule when you have created the object of InterestsUiState.
So I can't still understand why the member value of _uiState can be changed when the author launch _uiState.update { it.copy(loading = true) }.
And more
_uiState.update { it.copy(loading = true) } is equal to
_uiState.value = _uiState.value.copy(loading = true), right?
The val keyword only refers to which object the variable holds, not the data inside said object. For example:
class MyClass(var value: Int)
The following code is not allowed:
val obj = MyClass(5)
obj = MyClass(7) // compile error
because the val keyword refers to the variable itself being reassigned to a different object. This code, however, is allowed:
val obj = MyClass(5)
obj.value = 7
Here, obj is still the same object, only a property of said object changed value. In your provided code, the update function modifies data stored inside the _uiState object, however it does not swap it for a new object. This is important because:
var obj = MyClass(5)
val copy = obj
obj = MyClass(7)
println(copy.value) // prints 5
println(obj.value) // prints 7
When reassigning a variable, the old object remains, and any other variables referencing that object are not updated. In your case, _uiState.value is modified, not the variable itself. Hope this clears things up!
Edit:
Yes, it.copy() is an expression which creates a new object. However, this code is executed in the line _uiState.update { it.copy(loading = true) }, in the refreshAll() function. As it is the last statement in a lambda expression (also the only one, but doesn't matter), it is the return value of said lambda. Here we have to look at the declaration of the update function.
The lambda is stored in the function variable (of type (T)->T). This means, whenever function() is called, the code inside the lambda is executed, and its result (a new object) is then returned by the function() call. This value is assigned to the val nextValue variable and not to _uiState itself. The compareAndSet function modifies _uiState.value and does not change the object the _uiState variable references.
And by the way, the object returned by it.copy() is of type T, and not of type MutableStateFlow<T>, which your _uiState variable holds. T is the type of _uiState.value.

Create instance of class kotlin

How can I create an instance of InfoA that contains also title. Do I need to modify the classes?
Can't specify the title.
Also, do I need to create setters for it? To not access with the _
val info = InfoA(_subtitle = "SUBTITLE", title = ...)
open class Info(
open val action: Action = Action(),
open val title: String? = ""
) {
fun hasAction(): Boolean = action.hasAction()
}
class InfoA(
private val _subtitle: String? = "",
private val _image: String? = "",
private val _backgroundImage: String? = "",
private val _backgroundColor: String? = null,
private val _foregroundColor: String? = null,
private val _borderColor: String? = null
) : Info() {
val subtitle: String
get() = _subtitle.orEmpty()
val image: String
get() = _image.orEmpty()
val backgroundImage: String
get() = _backgroundImage.orEmpty()
val backgroundColor: Int?
get() = if (_backgroundColor != null) convertRgbStringToColorInt(_backgroundColor) else null
val foregroundColor: Int?
get() = if (_foregroundColor != null) convertRgbStringToColorInt(_foregroundColor) else null
val borderColor: Int?
get() = if (_borderColor != null) convertRgbStringToColorInt(_borderColor) else null
}
As the code is written, title is a val, so it can't be changed from its initial value — which is empty string if (as in the case of InfoA) something calls its constructor without specifying another value.
If it were changed to be a var, then it could be changed later, e.g.:
val info = InfoA(_subtitle = "SUBTITLE").apply{ title = "..." }
Alternatively, if you want to keep it a val, then InfoA would need to be changed: the most obvious way would be to add a title parameter in its constructor, and pass that up to Info:
class InfoA(
title: String? = "",
// …other fields…
) : Info(title = title) {
Note that this way, InfoA can never use Info's default value for title, so you may need to duplicate that default in InfoA's constructor.
The need to duplicate superclass properties in a subclass constructor is awkward, but there's currently no good way around it.  (See e.g. this question.)  If there are many parameters, you might consider bundling them together into a single data class, which could then be passed easily up to the superclass constructor — but of course users of the class would need to specify that.  (Some people think that having more than a few parameters is a code smell, and that bundling them together can often improve the design.)

Destructuring Declarations in Kotlin

I want to return multiple values from a function. As suggested by another SO answer, I used Destructuring with public class, but the problem is I can't assign the returned destructured result to already existing variables.
data class Result(val res1 :Int, val res2: Int)
class test{
fun retresult():Result{
return Result(2,2)
}
}
fun main(args: Array<String>) {
var var1:Int = 0
var var2:Int = 0
var des = test()
//(var1, var2) = des.retresult() this doesn't work
var1 = des.retresult().res1
var2 = des.retresult().res2 // **works but calls function twice**
}
I don't want to initialize local vals at return point like
val (var1, var2) = des.retresult()
You can assign these two variables without calling the function twice, using with:
fun main(args: Array<String>) {
var var1:Int = 0
var var2:Int = 0
var des = test()
with (des.retresult()) {
var1 = res1
var2 = res2
}
}
Alternatively, your function could take function arguments for setting the results, and then you can pass the setters for these properties. This wouldn't work for local variables, only member properties. If you use C, this is kind of like passing a pointer to a function so it can directly modify a variable rather than returning something.
class Test (var one: Int, var two: Int)
fun doSomething(result1: (Int) -> Unit, result2: (Int) -> Unit) {
result1(2)
result2(2)
}
fun main() {
val test = Test(1, 1)
doSomething(test::one::set, test::two::set)
}
There's an open (and mostly forgotten, it seems) feature request for what you suggested, destructuring assignment to existing variables.

Kotlin using apply in companion object throws an unexpected error

Let's say I'd want to instantiate an object of class A by copying values from class B which is a common practice when, for example, mapping DTO's. To accomplish this in Java or Groovy I'd create a static method on the appropriate DTO with the signature of fromB(A a) and then copy values either with a.val = b.val... in Java or using a.with { val = b.val... } in Groovy.
In Kotlin I've noticed that instance.apply{} is very similar to Groovy's with in that it allows me to directly access the object variables without constantly refering to the object itself since the reference seems to be implied within the closure.
However I've ran into a weird and unexpected error when using apply within companion objects. If I use A().apply {} inside a function of A's companion object I get an error Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner Which is weird since I'm calling apply directly on an instance of an object and would thus expect that I should always be able to access it's public properties. Not to mention that it seems like companion objects cannot be set to be inner thus the suggestion in the error message isn't all too helpful.
Here's the full example code:
fun main(args: Array<String>) {
val b = B("Hello", "World")
val a = A.fromB(b)
print("$a.value1 $a.value2")
}
class A() {
var value1: String? = null
var value2: String? = null
companion object {
//This fails with "Expression is inaccessible from a nested class 'Companion', use 'inner' keyword to make the class inner"
fun fromB(b: B): A {
return A().apply {
value1 = b.value3
value2 = b.value4
}
}
}
}
class B(val value3: String, val value4: String) {}
//This works
fun bToA(b: B): A {
return A().apply {
value1 = b.value3
value2 = b.value4
}
}
What is going on here? What am I doing wrong?
This looks like a bug to me. Probably something to do with inline functions (e.g. apply) and companion objects. I suggest searching the JetBrains Bug & Issue Tracker and if you don't find something similar to this create a new issue.
In the meantime I see some alternatives:
Use this (not ideal):
fun fromB(b: B): A {
return A().apply {
this.value1 = b.value3
this.value2 = b.value4
}
}
Move value1 and value2 to A's primary constructor and change fromB(B) to use named arguments (this will still let you define defaults, skip properties when copying, etc.):
class A(var value1: String? = null, var value2: String? = null) {
companion object {
fun fromB(b: B): A {
return A(
value1 = b.value3,
value2 = b.value4
)
}
}
}
UPDATE: In addition to the above you can use b with with:
fun fromB(b: B) = with(b) {
A(
value1 = value3,
value2 = value4
)
}
#MrPlow
I think this is more straightforward way to do, what you want:
fun B.toA(): A {
val self = this;
return A().apply {
value1 = self.value3
value2 = self.value4
}
}
Compare with your example:
val b = B("Hello", "World")
val a = A.fromB(b)
// vs
val a = b.toA();

Function returning ad-hoc object in Kotlin

Currently I have a private function which returns a Pair<User, User> object. The first user is the sender of something, the second user is the receiver of that thing.
I think this Pair<User, User> is not enough self explanatory - or clean if you like - even though it's just a private function.
Is it possible to return with an ad-hoc object like this:
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper): Any {
return object {
val sender = userCrud.findOne(instanceWrapper.fromWho)
val receiver = userCrud.findOne(instanceWrapper.toWho)
}
}
and use the returned value like this:
// ...
val users = findUsers(instanceWrapper)
users.sender // ...
users.receiver // ...
// ...
?
If not, what's the point of ad-hoc object in Kotlin?
Since the type can not be denoted in the language, use return type inference:
class Example {
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper) =
object {
val sender = userCrud.findOne(instanceWrapper.fromWho)
val receiver = userCrud.findOne(instanceWrapper.toWho)
}
fun foo() = findUsers(ExceptionInstanceWrapper()).sender
}
Another option would be to devise a data class:
class Example {
private data class Users(val sender: User, val receiver: User)
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper): Users {
return Users(
sender = userCrud.findOne(instanceWrapper.fromWho),
receiver = userCrud.findOne(instanceWrapper.toWho)
)
}
fun foo() = findUsers(ExceptionInstanceWrapper()).sender
}
Simply define your function as a lambda.
Here's simple object I've just written as an example in another context:
private val Map = {
val data = IntArray(400)
for (index in data.indices) {
data[index] = index * 3
}
object {
val get = { x: Int, y: Int ->
data[y * 20 + x]
}
}
}
fun main() {
val map = Map()
println(map.get(12,1))
}
Unfortunately, you cannot assign a type name, so it can be used as a return value but not as an argument. Maybe they'll make this possible so we can finally do OOP JS style.
Alternatively, they could implement object types equivalent to function types but that could end up being too wordy. You could then do a typedef but that would actually just be a kind of class definition 😅
Another option is to have a generic class for return types:
data class OutVal<T>(private var v: T?) {
fun set(newVal: T) {
v = newVal
}
fun get() = v
}
Usage example:
private fun findUsers(instanceWrapper: ExceptionInstanceWrapper,
sender: OutVal<String>, receiver: OutVal<String>) {
sender.set(userCrud.findOne(instanceWrapper.fromWho))
receiver.set(userCrud.findOne(instanceWrapper.toWho))
}
val sender = OutVal("")
val receiver = OutVal("")
findUsers(instanceWrapper, sender, receiver)
sender.get() // ...
receiver.get() // ...