Can someone please help me work this out? My object had 3 main parameters, all Strings. I now want to add a secondary constructor with an single emum parameter, which in turn would set each of the 3 original params, as follows;
class MyObject(var string1: String, var string2: String, var string3: String)
{
constructor(presetCode: PresetCode ) : this("", "", "")
{
when (presetCode)
{
PresetCode.Code1 ->
{
string1 = "aaa"
string2 = "bbb"
string3 = "ccc"
}
}
}
var anotherObject = AnotherObject(string1)
}
The issue is AnotherObject(string1) isn't working, as string1 is just an empty string.
how can I set the 3 params using this secondary constructor and then successfully call AnotherObject(string1)?
thanks
One option is to replace the secondary constructor with an invoke() method on the companion object:
class MyObject(var string1: String, var string2: String, var string3: String) {
companion object {
operator fun invoke(presetCode: PresetCode) = when (presetCode) {
PresetCode.Code1 -> MyObject("aaa", "bbb", "ccc")
else -> MyObject("", "", "")
}
}
var anotherObject = AnotherObject(string1)
}
In use, it looks just like a constructor call, e.g.:
val o = MyObject(PresetCode.Code1)
This approach is simple, concise, and very flexible. (For example, it could return a cached instance instead of creating a new one.)
Here is a solution I found based on your class design which requires single AnotherObject construction per MyObject instance:
class MyObject
{
constructor(s1: String, s2: String, s3: String)
{
string1 = s1
string2 = s2
string3 = s3
anotherObject = AnotherObject(string1)
}
constructor(presetCode: PresetCode)
{
when (presetCode)
{
PresetCode.Code1 ->
{
string1 = "aaa"
string2 = "bbb"
string3 = "ccc"
}
else ->
{
string1 = ""
string2 = ""
string3 = ""
}
}
anotherObject = AnotherObject(string1)
}
var string1: String
var string2: String
var string3: String
var anotherObject: AnotherObject
}
Hope this helps.
Related
Let say I have a data class called Class A
data class ClassA {
val x0 = ""
val x1 = "some string"
val x2 = "some string"
val x3 = "some string"
val x4 = "some string"
val x5 = ""
val x6 = ""
val y = ""
val z = ""
}
I can retrieve the value of these class member thru its class object
val obj = ClassA()
// if the properties has prefix x and not empty then concatenate to this x variable
val x = obj.x1 + obj.x2 + etc...
...
Let say if this data class has 50+ x(n) in it and I want to retrieve any member that does not have an "empty string or null" and match the prefix then how do I do it dynamically (can be a for-loop), instead of type out statically every single properties that I want to retrieve, is there an alternative way to do it?
You could solve that with Reflection:
data class ClassA (
val x0: String = "",
val x1: String = "some string 1",
val x2: String = "some string 2",
val x3: String = "some string 3",
val x4: String = "some string 4",
val x5: String = "",
val x6: String = "",
val y: String = "",
val z: String = ""
)
val obj = ClassA()
val result = ClassA::class.java.declaredFields // or:
.filter { it.name.startsWith("x") }
.onEach { it.isAccessible = true }
.map { it.get(obj) }
.joinToString("; ")
println(result)
This will print:
; some string 1; some string 2; some string 3; some string 4; ;
If you want to omit x0, x5, and x6, which all are empty strings, and you want to concatenate without semicolon:
val result = Class::class.java.declaredFields
.filter { it.name.startsWith("x") }
.onEach { it.isAccessible = true }
.map { it.get(obj) }
.filter { it != "" }
.joinToString("")
I'm going to answer you question and provide a way of doing it, however, I highly recommend against doing it this way simply because while it works, it is seen as hacky and you'd be better off solving this by changing the multiple fields/properties into a collection of some sort instead.
You can use reflection to do it without having to type out all of the values manually.
To do it via reflection, you will have to do something like this.
fun getNonEmptyStrings(input: A): List<String> {
val members = A::class.memberProperties
return members
.filter { it.name.startsWith("x") }
.map { it.get(input) }
.filterIsInstance<String>()
.filter { it.isNotBlank() }
}
There might be some other form of meta-programming that is better suited to this, but I'm not sure what that would be.
I'm looking for a Kotlin way to do a dynamic values substitution into a string.
It is clear how to implement it, just want to check if there is something similar in standard library.
Could you help me to find a function which given template and data map returns a resulting string with all template keys replaced with their values?
fun format(template: String, data: Map<String, Any>): String { /* magic */ }
format("${a} ${b} ${a}", mapOf("a" to "Home", "b" to "Sweet)) // -> "Home Sweet Home"
fun format(template: String, data: Map<String, String>): String {
var retval = template
data.forEach { dataEntry ->
retval = retval.replace("\${" + dataEntry.key + "}", dataEntry.value)
}
return retval
}
// The $ signs in the template string need to be escaped to prevent
// string interpolation
format("\${a} \${b} \${a}", mapOf("a" to "Home", "b" to "Sweet"))
Not shorter than lukas.j's answer, just different (using Regex):
val regex = "\\\$\\{([a-z])}".toRegex()
fun format(template: String, data: Map<String, String>) =
regex.findAll(template).fold(template) { result, matchResult ->
val (match, key) = matchResult.groupValues
result.replace(match, data[key] ?: match)
}
I did not find any thing standard to solve the problem.
So here is a balanced (readability/performance/extensibility) solution also handling cases when some substitutions are undefined in dataMap.
makeString("\${a} # \${b} # \${c}", mapOf("a" to 123, "c" to "xyz")) // => "123 # ??? # xyz"
--
object Substitutions {
private val pattern = Pattern.compile("\\$\\{([^}]+)\\}")
fun makeString(
template: String,
dataMap: Map<String, Any?>,
undefinedStub: String = "???"
): String {
val replacer = createReplacer(dataMap, undefinedStub)
val messageParts = splitWithDelimiters(template, pattern, replacer)
return messageParts.joinToString("")
}
private fun createReplacer(dataMap: Map<String, Any?>, stub: String): (Matcher) -> String {
return { m ->
val key = m.group(1)
(dataMap[key] ?: stub).toString()
}
}
private fun splitWithDelimiters(
text: String,
pattern: Pattern,
matchTransform: (Matcher) -> String
): List<String> {
var lastMatch = 0
val items = mutableListOf<String>()
val m = pattern.matcher(text)
while (m.find()) {
items.add(text.substring(lastMatch, m.start()))
items.add(matchTransform(m))
lastMatch = m.end()
}
items.add(text.substring(lastMatch))
return items
}
}
I am learning Kotlin and writing code to check my understanding. I'm trying to use a toString override to print the values of a hashMap that is a property of a class. I can't get it to work. Instead I get output like "kotlin.Unit() -> kotlin.Unit". Also, I don't understand why the values of the hashMap ARE printing out before the toString output. I don't know where that output is coming from. Please help me. Thanks. Below is my code and the output I'm getting.
Code:
package ch07.ExpandoObject
import java.beans.PropertyChangeListener
import java.beans.PropertyChangeSupport
import kotlin.properties.Delegates
import kotlin.reflect.KProperty
class Person(
val name: String = "",
age: Int? = null,
var isMarried: Boolean? = null ,_attributes: kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
)
:PropertyChangeAware()
{
var _attributes : kotlin.collections.HashMap<String,String>? = hashMapOf<String, String>()
fun setAttribute(attrName: String, value: String) {
_attributes!!.set(attrName, value)
_attributes!!.set("name", this.name)
}
override fun toString() = "Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) " +
"${_attributes?.get("name")} " + "$name " +
this._attributes!!.forEach { (attrName, value) -> println("$attrName = $value") } +
{
for ((attrName, value) in this._attributes!!) {
println("attribute $attrName = ${this._attributes!![attrName]}")
}
}
val _age = ObservableProperty(propName = "age", propValue = age, changeSupport = changeSupport)
private val observer = {
prop: KProperty<*>, oldValue: Int, newValue: Int ->
changeSupport.firePropertyChange(prop.name, oldValue, newValue)
}
var age: Int by Delegates.observable(initialValue = age?:99999,onChange = observer)
}
class ObservableProperty(val propName: String,
var propValue: Int?, val changeSupport: PropertyChangeSupport
) {
fun getValue(): Int? = propValue
fun setValue( newValue: Int) {
val oldValue = propValue
propValue = newValue
changeSupport.firePropertyChange(propName, oldValue, newValue)
}
}
open class PropertyChangeAware {
val changeSupport = PropertyChangeSupport(this)
fun addPropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.addPropertyChangeListener(listener)
}
fun removePropertyChangeListener(listener: PropertyChangeListener) {
changeSupport.removePropertyChangeListener(listener)
}
}
fun main(args: Array<String>) {
val p = Person("Bob", 89, isMarried = false)
val data = mapOf("lastname" to "Jones", "company" to "JetBrains")
for ((attrName, value) in data)
p.setAttribute(attrName, value)
println(p)
}
Here is the current output:
name = Bob
company = JetBrains
lastname = Jones
Person(name="Bob", age=89, isMarried=false) Bob Bob kotlin.Unit() -> kotlin.Unit
Thanks, again, for any help.
You should not use print() or println() functions inside toString() because they output their arguments to the standard output immediately instead of appending them to the string returned to the caller.
Let's examine the output kotlin.Unit() -> kotlin.Unit you're getting. It consists of two parts:
kotlin.Unit is the string representation of attributes!!.forEach { ... } expression. forEach function returns without value, and in Kotlin it's expressed by returning the Unit object value. Its string representation is appended to the string you're returning.
the second part, () -> kotlin.Unit, is also the string representation of the lambda function expression { for((attrName, value) in ...) }. This function takes no parameters, and returns without value, which means that its type is () -> Unit. Note that in Kotlin the block { ... } declares a local lambda function. If you instead want to run the code inside of that block, use the run function: run { ... }
The goal of toString function is to build the string representation of an object. And for that you can use buildString function:
override fun toString() = buildString {
append("Person(name=\"${name?:""}\", age=${age?:99999}, isMarried=$isMarried) ")
append("${_attributes?.get("name")} ").append("$name ")
this._attributes!!.forEach { (attrName, value) -> append("$attrName = $value") }
for ((attrName, value) in this._attributes!!) {
append("attribute $attrName = ${this._attributes!![attrName]}")
}
}
This function creates a StringBuilder and passes it as a receiver to its functional argument, where you call append or appendln on that receiver. Then buildString converts that string builder to a string and returns it.
I try to use Firebase Firestore in a Kotlin project. Everything is going fine except when I want to instantiate an object with DocumentSnapshot.toObject(Class valueType).
Here is the code :
FirebaseFirestore
.getInstance()
.collection("myObjects")
.addSnapshotListener(this,
{ querySnapshot: QuerySnapshot?, e: FirebaseFirestoreException? ->
for (document in querySnapshot.documents) {
val myObject = document.toObject(MyObject::class.java)
Log.e(TAG,document.data.get("foo")) // Print : "foo"
Log.e(TAG, myObject.foo) // Print : ""
}
}
})
As you can see, when I use documentChange.document.toObject(MyObject::class.java), my object is instantiated but the inner fields are not set.
I know that Firestore needs the model to have an empty constructor. So here is the model :
class MyObject {
var foo: String = ""
constructor(){}
}
Can somebody tell me what I'm doing wrong?
Thank you
You forgot to include the public constructor with arguments, or you can also just use a data class with default values, it should be enough:
data class MyObject(var foo: String = "")
In my case I was getting a NullPointerException because there wasn't a default constructor.
Using a data class with default values fixed the error.
data class Message(
val messageId : String = "",
val userId : String = "",
val userName : String = "",
val text : String = "",
val imageUrl : String? = null,
val date : String = ""
)
class MyObject {
lateinit var foo: String
constructor(foo:String) {
this.foo = foo
}
constructor()
}
I have an array of objects of a class derived from NSObject in Swift that I would like to add to a NSComboBox.
For example :
class MyItem : NSObject
{
var data = "Hello"
var value = 1.234
}
var listOfItems = [MyItem]();
var item1 = MyItem()
var item2 = MyItem()
listOfItems.append( item1)
listOfItems.append( item2)
myNSComboBox.addItemsWithObjectValues(listOfItems)
Is there something I can add to or override in MyItem that will return a string to be displayed in the NSComboBox?
You need to create an array of strings with the appropriate strings in it.
func transform(sequence: [MyItem], inout output: [String], pred: (MyItem) -> String) {
var g = sequence.generate()
while let obj = g.next() {
output.append(pred(obj))
}
}
var listOfItems: [String] = []
transform(listOfItems, &listOfItems) {
$0.data
}
myNSComboBox.addItemsWithObjectValues(listOfItems)
The transform function above can be made into a generic function that will work for any sequence type. I will leave that as an exercise for the reader. :-)