I've been using Kotlin to develop some apps in Android and what i want to do currently is to set a field value inside the defining class without calling the setter method.
Here is the code inside my class:
var projectList: List<Project>? = null
set(value) {
saveProjects(value as ArrayList<Project>)
field = value
}
//GO to the database and retrieve list of projects
fun loadProjects(callback: Project.OnProjectsLoadedListener) {
database.projectDao().getAll().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ success ->
callback.onProjectsLoaded(success)
//Here i don't want to save the projects, because i've loaded them from the database
this.projectList = success
},
{ error -> GenericErrorHandler.handleError(error,callback.retrieveContext())}
)
}
Does anybody know a way to do this without calling the set(value) method?
You can only gain access to the field directly from the setter. Inside a setter, the field can be accessed through the invisible field variable.
There are perhaps some other ways around your requirements though. Here are 2 examples. You wouldn't have to follow them exactly, but could instead also combine them to make whatever solution you want.
You could use another shell property to act as the setter for your actual property:
class Example1 {
var fieldProperty: Int = 0
var setterPropertyForField: Int
get() = fieldProperty
set(value) {
fieldProperty = value
}
}
You could use setters as you actually would in Java with a JVM field and a set method. The #JvmField is probably not necessary.
class Example2 {
#JvmField var fieldProperty: Int = 0
fun setField(value: Int) {
fieldProperty = value
}
}
You could probably access the field and change it through reflection, but I don't recommend that approach. That would likely only lead to problems.
Related
In my code i want to execute holder.task.setChecked() command with item.getStatus()) as a parameter.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
holder.task.text = item.getTask()
holder.task.setChecked(item.getStatus())
}
The problem is that setChecked requires type Boolean, but from getStatus function i get Boolean.Companion in return.
class Model(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var id = Int
private var status = Boolean
private var task = String
fun getStatus(): Boolean.Companion {
return status
}
When i try to change the return type of this function, i get thrown with an error.
Been thinking for a while about how to change the types to be equal, but i cannot seem to find the solution.
To be clear, setChecked function is a default function which im executing on Checkbox object
Your example code is very unusual, and I'm not sure you actually meant to do what you're doing here.
This for example
private var id = Int
doesn't hold an Int. id is not a number. It's actually the companion object for the Int class, which in this case holds the constants MAX_VALUE, MIN_VALUE, SIZE_BYTES and SIZE_BITS. Usually you'd access these directly on the Int class, like Int.MAX_VALUE - when you do that you're actually accessing them on the companion object, which is why assigning the value Int to a variable gives you that companion object.
So the same goes for var status = Boolean - it's Boolean's companion object. And that's why your function has to return Boolean.Companion, because it's returning status, and that's what status is. It's not a boolean value of true or false.
There may be reasons why you'd want to do this - it's very unlikely though, and the way you're trying to use this (wanting a Boolean from getStatus) suggests that this is a mistake, and you're not familiar with the language. I'd really recommend running through the basic intro stuff to get a feel for how you define variables and their types, but this is probably what you wanted:
class Model(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var id: Int = -1
private var status: Boolean = false
private var task: String = ""
fun getStatus(): Boolean {
return status
}
}
I've added default values to each because you have to initialise them to something - if you have an init block where that happens, you can omit the values. If you're assigning them right there, you can omit the types too (e.g. var id = -1) unless you need to be more specific than the value is (e.g. if you want it to be a nullable Int? type).
You could also put these values in the constructor:
class Model(
itemView: View,
private var id: Int,
private var status: Boolean,
private var task: String
)
which would require the caller to provide some initial values. It depends what you want!
If you wanted, you could also replace the getter function by doing this:
var status: Boolean = false
private set
which makes the status property public, but read-only from the outside.
And just FYI, your Model class looks like your data for your RecyclerView's Adapter - it should not be a ViewHolder, those are completely separate things. Just use a basic class for your Model (or even better, a data class) and use that in your data list.
ViewHolders are special objects that get reused to display different data items - and by definition there's usually more data items than there are ViewHolders (that's the whole point of a RecyclerView - it recycles them). You shouldn't be keeping them in a list, or storing individual items' state in them.
Keep your data items in a list, fetch the one at position in onBindViewHolder, then display it in the ViewHolder you're provided with. What you have right now isn't going to work, so you'll need to look at a tutorial for setting one up, e.g. this one from the docs. You need to store references to the Views you're using in the VH, like a TextView and a Checkbox, so you can do things like
holder.taskTextView.text = item.getTask()
The setChecked function requires a Boolean param, but the return type of getStatus is Boolean.Companion. This is because you have defined the status field as of Boolean.Companion type.
To fix this, you may define status as a normal class level field and either assign a default value, or provide a constructor to initialize the value appropriately.
private var status: Boolean = false // or true, depending on the default value that suits here
Then you can change the getStatus function to return a Boolean type instead of Boolean.Companion
fun getStatus(): Boolean {
return status
}
and then use it to call holder.task.setChecked(item.getStatus()).
In the code below, i'd like to generalize it so I instead of viewBinding.editText.text and viewModel.property.price can use the same method for e.g viewBinding.secondEditText.text and viewModel.property.income.
I'm thinking exchanging viewBinding.editText.text for a variable defined in the primary constructor, but then I'd need the variable to contain a reference to viewBinding.editText.text/viewBinding.secondEditText.text etc. instead of containing a value.
Is this possible? I've looked at lengths for this but can't find anything useful.
fun updateProperty() {
//... other irrelevant code
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
//... other irrelevant code
}
You can pass parameters into a function, yeah!
This is the easy one:
fun updateProperty(editText: EditText) {
val contents = editText.text.toString()
}
simple enough, you just pass in whatever instance of an EditText and the function does something with it.
If you're just using objects with setters and getters, you can just define the type you're going to be using and pass them in. Depending on what viewmodel.property is, you might be able to pass that in as well, and access price and income on it. Maybe use an interface or a sealed class if there are other types you want to use - they need some commonality if you're going to be using a generalised function that works with them all.
Properties are a bit tricker - assuming viewmodel.property contains a var price: Double, and you didn't want to pass in property itself, just a Double that exists somewhere, you can do it like this:
import kotlin.reflect.KMutableProperty0
var wow: Double = 1.2
fun main() {
println(wow)
setVar(::wow, 6.9)
println(wow)
}
fun setVar(variable: KMutableProperty0<Double>, value: Double) {
variable.set(value)
}
>> 1.2
>> 6.9
(see Property references if you're not familiar with the :: syntax)
KMutableProperty0 represents a reference to a mutable property (a var) which doesn't have any receivers - just a basic var. And don't worry about the reflect import, this is basic reflection stuff like function references, it's part of the base Kotlin install
Yes, method parameters can also be references to classes or interfaces. And method parameters can also be references to other methods/functions/lambdas.
If you are dealing with cases that are hard to generalize, consider using some kind of inversion of control (function as parameter or lambda).
You add a lambda parameter to your updateProperty function
fun updateProperty(onUpdate: (viewBinding: YourViewBindingType, viewModel: YourViewModelType) -> Unit) {
//... other irrelevant code
// here you just call the lambda, with any parameters that might be useful 'on the other side'
onUpdate(viewBinding, viewModel)
//... other irrelevant code
}
Elsewhere in code - case 1:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
Elsewhere in code - case 2:
updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Elsewhere in code - case 3:
updateProperty() { viewBinding, viewModel ->
// I am a totally different case, because I have to update two properties at once!
viewModel.property.somethingElse1 = viewBinding.thirdEditText.text.toString().toBoolean()
viewModel.property.somethingElse2 = viewBinding.fourthEditText.text
.toString().replaceAll("[- ]*", "").toInt()
}
You could then go even further and define a function for the first 2 cases, since those 2 can be generalized, and then call it inside the lambda (or even pass it as the lambda), which would save you some amount of code, if you call updateProperty() in many places in your code or simply define a simple function for each of them, and call that instead, like this
fun updatePrice() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.editText.text.toString() != "") {
viewModel.property.price = viewBinding.editText.text.toString().toDouble()
}
}
fun updateIncome() = updateProperty() { viewBinding, viewModel ->
if (viewBinding.secondEditText.text.toString() != "") {
viewModel.property.income = viewBinding.secondEditText.text.toString().toDouble()
}
}
Then elsewhere in code you just call it in a really simple way
updatePrice()
updateIncome()
I have a ViewModel class that looks like this:
class EditUserViewModel(
private val initUser: User,
) : ViewModel() {
private val _user = MutableLiveData(initUser)
val user: LiveData<User>
get() = _user
fun hasUserChanged() = initUser != _user.value
}
User can update some properties of the User data class instance through the UI.
To check if there are any changes when navigating from the fragment I use hasUserChanged method.
The problem is that is always false. I checked and it seems that the initialUser changes every time I change the _user MutableLiveData.
Why is that? Is the initial value of MutableLiveData passed by reference? I always thought that Kotlin is a "pass-by-value" type of language.
Update:
The problem seems to disappear when copying initUser before putting it inside the MutableLiveData.
private val _user = MutableLiveData(initUser.copy())
But it still doesn't make sense to me why I have to do that.
Kotlin is like java and they are pass-by-value. If you implement the equals function in User class, or make it as data class (which implements the equals function implicitly), it makes you sure that the content of the user objects is checked by != operator.
Update
If you are changing the value of LiveData directly, for example like this:
_user.value.name = "some name"
it means that you are changing the name property of the initUser, because _user.value exactly refers to the object that the initUser does. Consequently, the != operator always returns false, because we have one object with two references to it.
Now, when you are doing so:
private val _user = MutableLiveData(initUser.copy())
you are creating a deep copy of initUser (let's call it X) which is a new object in memory with the same property values of initUser.
Thus, by changing its properties like: _user.value.name = "some name", in fact, you are making this change on X, not initUser. It leads to preserving the initial values in initUser, meaning do not changing them, and solving the issue.
I have been looking at Kotlin official tutorial. I came across the topic called Backing Fields
It says,
Classes in Kotlin cannot have fields. However, sometimes it is necessary to have a backing field when using custom accessors. For these purposes, Kotlin provides an automatic backing field which can be accessed using the field identifier:
var counter = 0 // the initializer value is written directly to the backing field
set(value) {
if (value >= 0) field = value
}
I got the above from this official link
My question is, is the "field" pointing to counter variable ?
Can someone please provide me an example for the backing field or describe me in an understanding word ?
Consider this class
class SomeClass {
var counter: Int = 0
set(value) {
if (value >= 0) field = value
}
}
In Android Studio go to Main menu -> Tools -> Kotlin -> Show Kotlin Bytecode and click Decompile in the Kotlin bytecode panel.
What you see is the equivalent code in Java.
public final class SomeClass {
private int counter;
public final int getCounter() {
return this.counter;
}
public final void setCounter(int value) {
if(value >= 0) {
this.counter = value;
}
}
}
The field keyword allows you to assign a value inside a custom setter. In kotlin counter = 3 will call set(3). So if you would define
var counter=0
set(value){
counter = value
}
It would recursively call itself until your stack is full and your process crashes.
The field keyword assigns the value directly without calling the setter again.
A Backing Field is just a field that will be generated for a property
in a class only if it uses the default implementation of at least one
of the accessors
Backing field is generated only if a property uses the default implementation of getter/setter. If you see the following code with perspective of Java. It looks correct. However in "kotlin" it’ll throw Exception.
class User{
var firstName : String //backing field generated
get() = firstName
set(value) {
firstName = value
}
var lastName : String //backing field generated
get() = lastName
set(value) {
lastName = value
}
val name : String //no backing field generated
get() = "{$firstName $lastName}"
var address : String = "XYZ" //^because there is no default //^implementation of an accessor
}
In Kotlin the above code snippet will throw StackOverflow because when we access or set property "first-name" or "last name" the default accessor will be called. So in Kotlin "user.firstName = "value"” is same as Java’s "user.setFirstName("value")".
So when "set(value) {firstName = "value"} " is called, then a recursive callhappens and compiler throws a Exception exception because we are calling setter inside the setter.
Solution to this problem is to user backing fields. In Kotlin a backing field can be accessed using "field" keyword inside accessors. Take a look at corrected code snippet below.
class User{
var firstName : String get() = field
set(value) {
field = value
}
var lastName : String get() = field
set(value) {
field = value}
}
}
How it works , let's understand by an example , consider this
class Person {
var name: String = ""
}
If nothing is specified, the property(name) uses the default getter and setter. It can, of course,
be modified to run whatever custom behaviour you need, without having to change
the existing code:
So if want set custom behaviour to name property than we modify above class to this
class Person {
var name: String = ""
get() = field.toUpperCase()
set(value) {
field = "Name: $value"
}
}
If the property needs access to its own value in a custom getter or setter (as in this
case), it requires the creation of a backing field. It can be accessed by using field, a
reserved word, and will be automatically created by the compiler when it finds that
it’s being used.
I have the code below. It is Kotlin. Any idea why textToSpeech from textToSpeech.setLanguage(Locale.UK) is telling that there is no reference resolved for textToSpeech?
val textToSpeech = TextToSpeech(
applicationContext,
object : TextToSpeech.OnInitListener {
override fun onInit(status: Int) {
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
}
})
At first I assumed it is an Idea kotlin plugin bug, but it seems that it actually can't be compiled
Kotlin has hardened the variables initialization policy, and it's now prohibited to reference the variable inside its initializer, even in lambdas and object expressions, which seems reasonable: imagine that a lambda is called immediately before the variable assignment.
For your case, I can suggest as a workaround using an object expression in this quite cumbersome construct:
val textToSpeech = object {
val value: TextToSpeech get() = inner
private val inner = TextToSpeech(
applicationContext,
{ value.setLanguage(Locale.UK) }
)
}.value
This will initialize an anonymous object with inner inside that is acceptable through value property. Note that the inner initializer uses value property. Then the value is extracted and can be used.
But please keep in mind that this trick is unsafe: in runtime, using value before inner is assigned (e.g. in TextToSpeech constructor) will throw NullPointerException.
Also, I've replaced the OnInitListener with a lambda using SAM conversion to be short, but object expression can still be used there.
UPD: check this question for my effort to generalize this approach. Using it, you can write
val textToSpeech = selfReference {
TextToSpeech(
applicationContext,
{ self.setLanguage(Locale.UK) }
)
}
See the sources on Github.
This is a very readable and clear way to face that problem. First you should define this:
fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this
and later use
val valueName: ValueType by selfReferenced{
//here you can create and use the valueName object
}
Using your problem as example you can do:
val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
applicationContext,
TextToSpeech.OnInitListener { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
})
}
Inside the selfReferenced block you can use the outer object with no restrictions. The only thing you should take care of, is declaring the type explicitly to avoid recursive type checking issues.