Kotlin - Is there a way to pass property values to another object with properties of the same name? - kotlin

Is there a way to pass property values to another object with properties of the same name to avoid having "repeated" code?
For example, avoid having where CepReceiptsInfo is different class than value but they share some properties name and type :
val cepReceiptsInfo = CepRecepitsInfo()
cepReceiptsInfo.operationTimestamp = value.operationTimestamp
cepReceiptsInfo.sentDate = value.sentDate
cepReceiptsInfo.sentTime = value.sentTime
cepReceiptsInfo.concept = value.concept
cepReceiptsInfo.referenceNumber = value.referenceNumber
cepReceiptsInfo.amount = value.amount
cepReceiptsInfo.trackingKey = value.trackingKey
cepReceiptsInfo.bankTarget = value.bankTarget
cepReceiptsInfo.bankSource = value.bankSource
cepReceiptsInfo.sourceClienteName = value.sourceClienteName
cepReceiptsInfo.beneficiaryName = value.beneficiaryName
cepReceiptsInfo.accountNumberTarget = value.accountNumberTarget
cepReceiptsInfo.term = value.term
cepReceiptsInfo.authorizationNumber = value.authorizationNumber
cepReceiptsInfo.linkCep = value.linkCep
cepReceiptsInfo.status = value.status
cepReceiptsInfo.bankSourceRefund = value.bankSourceRefund
cepReceiptsInfo.causeRefund = value.causeRefund
cepReceiptsInfo.accountTargetRefund = value.accountTargetRefund
cepReceiptsInfo.currency = value.currency
cepReceiptsInfo.accountNumberSource = value.accountNumberSource
cepReceiptsInfo.accountTypeSource = value.accountTypeSource
cepReceiptsInfo.accountTypeTarget = value.accountTypeTarget
cepReceiptsInfo.indicatorRefund = value.indicatorRefund
cepReceiptsInfo.amountIntRefund = value.amountIntRefund
cepReceiptsInfo.operationRefundTimestamp = value.operationRefundTimestamp
cepReceiptsInfo.dateMovement = value.dateMovement
cepReceiptsInfo.timeMovement = value.timeMovement
cepReceiptsInfo.dateRefund = value.dateRefund
cepReceiptsInfo.timeRefund = value.timeRefund
to something like f.e.:
val cepReceiptsInfo = CepReceintsInfo()
cepReceiptsInfo.assignFrom(value)
both Classes are data classes.

I don't know of a way without reflection.
fun Any.assignFrom(other: Any) {
val thisProperties = this::class.memberProperties
.filterIsInstance<KMutableProperty<*>>()
.map { it.name to it }
.toMap()
for (property in other::class.memberProperties){
thisProperties[property.name]?.setter?.call(this, property.getter.call(other))
}
}

As Tenfour04 says, the language doesn't provide a direct way of doing this, so you're limited to using extra compile-time tools, or Reflection at runtime.
Since this is a fairly common problem, there's a Java library called ModelMapper which does this for you; and as with just about all Java libraries, you can use it in Kotlin too.
(I've used it on occasion.  It can avoid lots of boilerplate.  Though I didn't find it as refactoring-safe as the front page claims…  It also ‘hides’ references to fields in a way that IDEs won't track.  So it's not a perfect solution.)

Related

How does Kotlin's data class copy idiom look for nullable types?

I have some code which looks like this, where param is of a data class type:
val options = if (param.language == null) {
param.copy(language = default())
} else {
param
}
Now, however, the language object has been moved into a hierarchy of nullable objects, so the check must look like this:
if (param.subObj?.nextObj?.language == null) { ... }
How do I use the copy idiom in this case?
One way to do this is:
val newParam = when {
param.subObj == null -> param.copy(subObj = SubObj(nextObj = NextObj(language = Language())))
param.subObj.nextObj == null -> param.copy(subObj = param.subObj.copy(nextObj = NextObj(language = Language())))
param.subObj.nextObj.language == null -> param.copy(subObj = param.subObj.copy(nextObj = param.subObj.nextObj.copy(language = Language())))
else -> param
}
I agree that this doesn't look very clean but this seems to be the only way to me, because at each step you need to check if the current property is null or not. If it is null, you need to use the default instance otherwise you need to make a copy.
Could you do something like this?
// you could create a DefaultCopyable interface if you like
data class SubObj(val prop1: Double? = null, val nextObj: NextObj? = null) {
fun copyWithDefaults() =
copy(prop1 = prop1 ?: 1.0, nextObj = nextObj?.copyWithDefaults() ?: NextObj())
}
data class NextObj(val name: String? = null) {
fun copyWithDefaults() = copy(name = name ?: "Hi")
}
I think you need a special function because you're not using the standard copy functionality exactly, you need some custom logic to define defaults for each class. But by putting that function in each of your classes, they all know how to copy themselves, and each copy function that works with other types can just call their default-copy functions.
The problem there though is:
fun main() {
val thing = SubObj(3.0)
val newThing = thing.copyWithDefaults()
println("$thing\n$newThing")
}
> SubObj(prop1=3.0, nextObj=null)
> SubObj(prop1=3.0, nextObj=NextObj(name=null))
Because nextObj was null in SubObj, it has to create one instead of copying it. But the real default value for name is null - it doesn't know how to instantiate one with the other defaults, that's an internal detail of NextObj. You could always call NextObj().copyWithDefaults() but that starts to look like a code smell to me - why isn't the default value for the parameter the actual default value you want? (There are probably good reasons, but it might mean there's a better way to architect what you're up to)

Visual Basic With Equivalent in Kotlin

In Visual Basic we can use With Expression like this:
With theCustomer
.Name = "Coho Vineyard"
.URL = "http://www.cohovineyard.com/"
.City = "Redmond"
End With
I'm looking for something like this. Is it possible in Kotlin?
Kotlin provides multiple, so called, scope functions. Some of them make use of a function literal with receiver, which make it possible to write similar code as provided by you in Visual Basic. Both, with and apply are suitable for this case. It's interesting to note that with returns some arbitrary result R while apply always returns the concrete receiver on which the function has been invoked.
For your example, let's consider both functions:
with
Using with, we can write the code as follows:
val customer = Customer()
with(customer) {
name = "Coho Vineyard"
url = "http://www.cohovineyard.com/"
city = "Redmond"
}
The last expression of the lambda passed to with here is an assignment, which, in Kotlin, returns Unit. You could assign the result of the with call to some new variable which would then be of type Unit. This is not useful and the whole approach is not very idiomatic since we have to separate the declaration from the actual initialization of customer.
apply
With apply on the other hand, we can combine declaration and initialization as it returns its receiver by default:
val customer = Customer().apply {
name = "Coho Vineyard"
url = "http://www.cohovineyard.com/"
city = "Redmond"
}
As you can see, whenever you want to initialize some object, prefer apply (extension function defined on all types). Here's another thread on the differences between with and apply.
You can use with function from the Kotlin Standard library, e.g.:
with(theCustomer) {
name = "Coho Vineyard"
url = "http://www.cohovineyard.com/"
city = "Redmond"
}
with() returns some result. It makes code cleaner.
Also you can use apply extension function:
theCustomer.apply {
name = "Coho Vineyard"
url = "http://www.cohovineyard.com/"
city = "Redmond"
}
apply - declared on Any class, it could be invoked on instances of all types, it makes code more readable. Use when need to utilize an instance of the object (modify properties), express the chain of calls.
It differs from with() in that it returns Receiver.
Something like this?
with(theCustomer) {
Name = "Coho Vineyard"
URL = "http://www.cohovineyard.com/"
City = "Redmond"
}
But with requires non-nullable parameter. I suggest using let or apply instead.
theCustomer?.apply{
Name = "Coho Vineyard"
URL = "http://www.cohovineyard.com/"
City = "Redmond"
}
or
theCustomer?.let{ customer ->
customer.Name = "Coho Vineyard"
customer.URL = "http://www.cohovineyard.com/"
customer.City = "Redmond"
}

How should I define these complex initializer for a property

Although I checked all tests in the kotlinpoet code, but I didn't find a proper way to implement below target codes, or I am not sure whether I used the best approach to do that. If anyone can provide some comments about this, that would be highly appreciate.
These properties are defined in the function of a class
Target Code 1
val outputState = StateType1(iouValue, ourIdentity, otherParty)
I used below codes to generate above code
.addCode(CodeBlock.of("%L",
PropertySpec.builder("outputState", ClassName("","StateType1"))
.initializer(CodeBlock.of("%T(%L, %L, %L)", ClassName("","StateType1"), "iouValue", "ourIdentity", "otherParty"))
.build()))
But question would be this outputState might be from different types, for example, StateType1 has 3 parameters, but StateTyp2 might only has 1 parameter, how should I dynamically define my kotlinpoet code to generate correct target code.
Target Code 2
val txBuilder = TransactionBuilder(notary = notary)
.addOutputState(outputState, TEMPLATE_CONTRACT_ID)
I didn't find a reference test case which has this scenario, after property's initializer then invoke it's function directly.
Use CodeBlock.Builder for the first example, it gives you more flexibility in constructing CodeBlocks:
fun createConstructorCall(type: TypeName, vararg args: String): CodeBlock {
val argsCode = args
.map { CodeBlock.of("%L", it) }
.joinToCode(separator = ", ", prefix = "(", suffix = ")")
return CodeBlock.Builder()
.add("%T", type)
.add(argsCode)
.build()
}
val className = ClassName("", "StateType1")
val codeBlock = CodeBlock.of("%L", PropertySpec.builder("outputState", className)
.initializer(createConstructorCall(className, "iouValue", "ourIdentity", "otherParty"))
.build())
assertThat(codeBlock.toString()).isEqualTo("""
|val outputState: StateType1 = StateType1(iouValue, ourIdentity, otherParty)
|""".trimMargin())
In the second example, we don't really provide anything special, pass your code as a String and feel free to use placeholders to parameterize if needed:
val className1 = ClassName("", "TransactionBuilder")
val codeBlock1 = CodeBlock.of("%L", PropertySpec.builder("txBuilder", className)
.initializer(
"%T(notary = notary)\n.addOutputState(outputState, TEMPLATE_CONTRACT_ID)",
className1)
.build())
assertThat(codeBlock1.toString()).isEqualTo("""
|val txBuilder: StateType1 = TransactionBuilder(notary = notary)
| .addOutputState(outputState, TEMPLATE_CONTRACT_ID)
|""".trimMargin())

Fixing Some Kotlin Syntax

I just finish built a simple android music app with Java, then I convert the java files to Kotlin with Kotlin plugin for Android Studio.
There are some error, in MainActivity.kt
private fun display() {
val mySongs = findSong(Environment.getExternalStorageDirectory())
items = arrayOf(mySongs.size.toString())
for (i in mySongs.indices) {
items[i] = mySongs[i].name.toString().replace(".mp3", "").replace(".wav", "")
}
val adp = ArrayAdapter(this, android.R.layout.simple_list_item_1, items!!)
listView!!.adapter = adp
listView!!.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, position, l -> startActivity(Intent(applicationContext, PlayerActivity::class.java).putExtra("pos", position).putExtra("songs", mySongs)) }
}
this line : items[i] = mySongs[i].name.toString().replace(".mp3","").replace(".wav", "")
showing an error: Smart cast to 'Array' is a mutable property that could have been changed by this time.
and on the PlayerActivity.kt
val i = intent
val b = i.extras
mySongs = b.getParcelableArrayList<Parcelable>(mySongs.toString())
position = b.getInt("pos", 0)
val u = Uri.parse(mySongs!![position].toString())
mediaPlayer = MediaPlayer.create(applicationContext, u)
the b.getParcelableArrayList<Parcelable>(mySongs.toString()) has a problem says Type mismatch.
Anyone can help me fix this? Thank You
This line
items = arrayOf(mySongs.size.toString())
creates an array of 1 element containing a string with the size of my songs. ex: ["23"]
You could use this instead: arrayOfNulls(mySongs.size)
For the other question:
mySongs = b.getParcelableArrayList<Parcelable>(mySongs.toString())
should return the same type as (I assume that it was the same code than in main activity because they are in 2 differents files and the context of mySongs is not provided)
val mySongs = findSong(Environment.getExternalStorageDirectory())
mySongs will have be the same type as the result of findSong.
Also you should use var instead of val because val are immutable

how to use serialization package

I want to convert my class to a Map so I'm using Serialization package. From the example it looks simple:
var address = new Address();
address.street = 'N 34th';
address.city = 'Seattle';
var serialization = new Serialization()
..addRuleFor(Address);
Map output = serialization.write(address);
I expect to see an output like {'street' : 'N 34th', 'city' : 'Seattle'} but instead it just output something I-don't-know-what-that-is
{"roots":[{"__Ref":true,"rule":3,"object":0}],"data":[[],[],[],[["Seattle","N 34th"]]],"rules":"{\"roots\":[{\"__Ref\":true,\"rule\":1,\"object\":0}],\"data\":[[],[[{\"__Ref\":true,\"rule\":4,\"object\":0},{\"__Ref\":true,\"rule\":3,\"object\":0},{\"__Ref\":true,\"rule\":5,\"object\":0},{\"__Ref\":true,\"rule\":6,\"object\":0}]],[[],[],[\"city\",\"street\"]],[[]],[[]],[[]],[[{\"__Ref\":true,\"rule\":2,\"object\":0},{\"__Ref\":true,\"rule\":2,\"object\":1},\"\",{\"__Ref\":true,\"rule\":2,\"object\":2},{\"__Ref\":true,\"rule\":7,\"object\":0}]],[\"Address\"]],\"rules\":null}"}
Serialization is not supposed to create human-readable output. Maybe JSON output is more what you look for:
import dart:convert;
{
var address = new Address();
..address.street = 'N 34th';
..address.city = 'Seattle';
var encoded = JSON.encode(address, mirrorJson);
}
Map mirrorJson(o) {
Map map = new Map();
InstanceMirror im = reflect(o);
ClassMirror cm = im.type;
var decls = cm.declarations.values.where((dm) => dm is VariableMirror);
decls.forEach((dm) {
var key = MirrorSystem.getName(dm.simpleName);
var val = im.getField(dm.simpleName).reflectee;
map[key] = val;
});
return map;
}
The new Address() creates a full prototype object which is what you are seeing. That being said, they could have done something to avoid part of those, but if you want to restore the object just the way it is, that's necessary.
To see the full content of an object you use the for() instruction in this way:
for(obj in idx) alert(obj[idx]);
You'll see that you get loads of data this way. Without the new Address() it would probably not be that bad.
Serialization won't help you here...
You might give a try to JsonObject library, and maybe go through this in depth explanation how to do what you are trying to do using mirrors.