Like
class A {
public var tip : String = ""
}
class B {
val tip2 = A().tip
println(tip2)
}
class C {
tiper("abc")
tiper("def")
tiper("ghi")
fun tiper(txt) {
A().tip = txt
B.showTip()
}
}
To be brief, I have a class B, which outputs a 'tip'. There is class C, which creates the text for the 'tip'. And I need to send a value from class C to class B. I tried doing it through class A, sending the value there and then reading it in class B to display it.
But in this case it just displays the value "", i.e. by default from class A.
Why isn't the value passed by class C taken instead?
The above code is greatly simplified and is a hypothetical description of what I actually have in my code. This is just an abstract example.
I'm a terrible Kotlin programmer, I was just asked to do this one job, so I don't know much about it, hope for your help.
You're creating a new object from type A every time you call it's constructor A().
Thus, inside tiper, you're creating an object of type A and setting the tip value on that object instance.
Then however, you create an object of type B which creates a new object of type A internally. This has no link to the first object of type A you've created. Thus, it does not contain the value you wanted to set but rather the default you've set, which is the empty string "".
Keeping close to our example, you can instead adjust the value on the object of type A that is embedded in the object of type B.
class A {
var tip: String = ""
}
class B() {
val tipHolder = A()
fun showTip() {
println(tipHolder.tip)
}
}
fun tiper(txt: String) {
val tipPrinter = B()
tipPrinter.tipHolder.tip = txt
tipPrinter.showTip()
}
fun main() {
tiper("abc")
tiper("def")
tiper("ghi")
}
However, without more details on the actual problem, it's hard to help you with the underlying problem you're trying to solve, as written by #aSemy in the comment section.
Related
class C(val string: String) {
init {
println(string)
}
}
abstract class A {
abstract var string: String
val c = C(string)
}
class B : A() {
override var string = "string"
}
fun main() {
B()
}
kotlin playground for the problem
This code crash in runtime due to string var not initialized, how to do it right?
It's not a good practice and dangerous to use an abstract or open variable in the initialization of your class. And if you write this code in Android Studio or IntelliJ IDEA you will get this warning: Accessing non-final property string in constructor.
So what's happening here ? Well the super class which is A is going to be initialized first before totally initializing B, so this line of code val c = C(string) is going to run before even giving a value to string and that's what causing the error and you will get a NullPointerException because string is null.
How to fix this ? You can use lazy to initialize c like that:
val c by lazy { C(string) }
Now c is not going to be initialized only if you call it, so now it's safe
because you can't call it only if B is fully initialized.
You are initialising A's properties using non-final properties - in this case, you are initialising c with the abstract property string.
abstract var string: String
val c = C(string)
This in general could be unsafe. Subclasses could override the non-final property in such a way that it is initialised at a later point, which means any initialisation that depends on the non-final property in the superclass will get an undefined value.
In this case, this is exactly what happens. B overrides string so that it is initialised after A's primary constructor is called. As a result, when A's primary constructor is run, and c is initialised, string has the value of null.
To fix this, you can either make c lazy:
val c by lazy { C(string) }
This will only initialise c when you first access it, with whatever the value of string is at that time.
Alternatively, make c computed:
val c get() = C(string)
This will make a new C every time you access c, with the current value of string.
Let's say I have the following class constructor:
class Car(val brand: Brand,val modelName: String, val version: Int){}
If for example, I want the version number to always start with 1. Is there a way to manipulate it in the class body to achieve this ?
Meaning:
val firstdigit:Int = abs(version).ToString().Substring(0,1)
And then parse it to Int. But how to replace the original first digit after that?
I'm just learning Kotlin and I got a bit stuck with this
Is this what you had in mind?
class Car(val brand: Brand, val modelName: String) {
val version = getNextVersion()
companion object {
private var nextVersion = 0
private fun getNextVersion(): Int {
nextVersion++
if (nextVersion.toString()[0] != '1') {
nextVersion = (10.0.pow(ceil(log10(nextVersion.toDouble())))).toInt()
}
return nextVersion
}
}
}
You already said in the comments that you want the number to increment per instance, so the caller shouldn't be providing that number in the first place really! But just generally, here's two approaches to sanitising your input parameters:
1) Make it the caller's responsibility to provide valid data
init {
require(version.toString().first() == '1') { "Needs to start with 1 thanks" }
}
require throws an IllegalArgumentException if it fails, which is the standard exception for "the value of this argument is invalid". Should the class be responsible for taking bad data and trying to "fix" it, or should the caller be handling that - and maybe not constructing an instance at all if it doesn't have valid data?
2. create a newInstance function that uses valid data, and keep the constructor private
class Thing private constructor(val number: Int){
companion object {
fun newInstance(num: Int): Thing {
return Thing(abs(num))
}
}
}
fun main() {
Thing.newInstance(-2).let { println(it.number)}
}
If it makes sense for the class itself to sanitise the input parameters, you can delegate construction to a function that takes care of that, and prevent things from calling the constructor directly with potentially bad data.
This can cause issues with e.g. serialisation libraries (which want to call the constructor directly) but in that case you could leave the constructor public, and just advise callers to call newInstance instead. Not ideal, but it's an option!
I've read this thread, it doesn't address my specific case.
Here's my minimal case:
open class BaseCase() {
lateinit var txtNode: UiObject
enum class TextType(val field: UiObject) {
PlainText(txtNode)
}
}
But there's error:
I was wondering if it is possible in Kotlin?
The problem is that txtNode is an instance variable. Different BaseCase instances could have different values. So the enum cannot know which one of them to take.
Let's for simplicity say txtNode is a String instead of an UiObject
Then how would the following code work?
val a = BaseCase()
a.txtNode = "test"
val b = BaseCase()
b.txtNode = "test2"
val c = BaseCase.TextType.PlainText
would c have "test" or "test2" as field? It simply isn't possible.
that's because enum class are final static classes ---> you cannot access non static variables.
for testing -> if you move your variable to companion object the code will work
I have the following code setup;
abstract class GenericQuestionEditor() {
protected abstract var data: GenericQuestionData
}
but then when I create EditorSimple() it throws an error when I try to set data to DataSimple(), why?
class EditorSimple(): GenericQuestionEditor() {
override var data = DataSimple()
}
my GenericQeustionData and DataSimple() are setup like this;
abstract class GenericQuestionData {}
class DataSimple: GenericQuestionData() {}
it doesn't complain if I create this function in GenericQuestionEditor()
fun test() {
data = DataSimple()
}
Why do I get an error on data in EditorSimple()? It should recognize it as a subtype and it should be allowed as I understand.
I feel like the answer is found in the kotlin documentation but i'm not sure how to configure it in this case since they are not passed values or part of a collection.
You need to specify the type explicitly:
class EditorSimple(): GenericQuestionEditor() {
override var data: GenericQuestionData = DataSimple()
}
Without the type annotation, the type of data would be inferred to be DataSimple, which doesn't match the type of its super class' data. Even though the types are related, you can't override writable a property with a subtype. Imagine if I did:
class SomeOtherData: GenericQuestionData()
val editor: GenericQuestionEditor = EditorSimple()
editor.data = SomeOtherData() // data is of type GenericQuestionData, so I should be able to do this
But, editor actually has a EditorSimple, which can only store DataSimple objects in data!
for example , I want to change all setters this way:
this.a = StringUtils.trim(a);
If it's a java bean, I can do this by modifying the code generating template of the ide. But Intellij seems not support to atomically add getter/setter for kotlin data class.
Is there a way to do this?
There is not a way to do this as of Kotlin 1.1.
A Kotlin data class, for the most part, is a class "to do nothing but hold data".
I think the closest you can get is to validate your data upon class initialization and make your data class properties read-only values. e.g.:
data class Data(val a: String) {
init {
require(a == a.trim())
}
}
The following won't throw an exception:
val a = Data("ab")
val b = a.copy(a = "abc")
While the following will:
val c = a.copy(a = "abc ")
It looks like if you declare the property as private, you can create your own getter/setters for accessing it. This example works for me.
fun main(args: Array<String>) {
var t = test("foo")
t.setHello("bar")
println(t)
}
data class test(private var hello: String) {
fun setHello(blah: String) {
this.hello = blah
}
}
But you will still have an issue when the property is passed in to the constructor. You will probably need to rethink how you are doing this, either declaring the field private and trimming it in the getter, or not using a data class for this instance.