When you have an object already initialized, what is the best kotlin scope function to use for setting multiple properties to this object (I don't need to return the instance of the object but only setting properties)
myObject.run {
text = "",
id = 0,
color = "#111111"
}
or
with(myObject) {
text = "",
id = 0,
color = "#111111"
}
or
myObject.apply {
text = "",
id = 0,
color = "#111111"
}
or maybe "let" ? I'm little confused because all scope functions can be used to achieve that but which is the must appropriated ?
I don't need to return the instance of the object but only setting properties
All scope functions return something. Some return the context object (apply/also) while others return lambda result (let/with/run). If you don't need the return value you can ignore them.
Here you want to mutate the properties of myObject, so apply would make the most sense. It provides the context object as this inside lambda and returns the object itself after executing the lambda.
Using with or run will return Unit (which is the result of lambda). Since you don't care about the result, you go with any of these but as I said earlier, apply makes the most sense here.
Quoting the docs,
Use apply for code blocks that don't return a value and mainly operate on the members of the receiver object. The common case for apply is the object configuration. Such calls can be read as “ apply the following assignments to the object.”
run is useful when your lambda contains both the object initialization and the computation of the return value.
We recommend with for calling functions on the context object without providing the lambda result. In the code, with can be read as “ with this object, do the following.”
Btw, if myObject is a data class, I would suggest using the copy function for creating a new instance by modifying some properties of myObject.
val newObject = myObject.copy(text = "", id = 0, color = "#111111")
Related
I am updating the old mutableStateOf() object data by modifying it but new data is not reflecting on it.
variable: as
val offTime = mutableStateOf<List<OffTime>>(emptyList())
update code
fun updateOffTime(newOffTime: OffTime, index: Int){
val updatedOffTime = offTime.value.mapIndexed { i, offTime ->
var result = offTime
if(index == i) result = newOffTime
result
}
offTime.value = updatedOffTime
Log.d(TAG, "updateOffTime: $updatedOffTime")
Log.d(TAG, "updateOffTime: ${offTime.value}")
}
Note: It works when the object OffTime is without id
i.e. OffTime(fromTime, toTime) :- works
OffTime(id,fromTime, toTime) :- doesn't works
I don't know exactly your use-case, neither your entire code implementation, but if I may ask why are you using an ordinary collection list instead of a SnapshotStateList or an extension of its new instance, mutableStateListOf when its part of your requirement to perform list operations?
Have you tried converting your offtime as a SnapshotStateList like this?
val offTime = mutableStateListOf<OffTime>(mutableStateListOf())
and performing updates liks this?
offTime.add( ... )
//or
offtime.remove(...)
//or
offTime[index] = offtime copy
//or
var offTimeIterator = offTime.lisIterator() // where you can safely modify indeces
SnapshotStateList is created exactly for such use-cases in compose, where you can perform normal list operations such as (add, remove, update, or batch updates) and guarantees re-composition.
Your call
offTime.value = updatedOffTime // if this is a new instance of a list
will trigger an entire re-composition as the entire list reference had been changed, but with SnapshotStateList, any changes to the structure is guaranteed to match a specific re-composition, say if you modify an item at index 7, and if this is observed by say a LazyColumn only LazyColumn's 7th index will re-compose
Also I don't know if your Offtime is a data class or a standard class, I would recommend it to be a data-class so you can easily copy() a certain instance of it, pass a new value to a certain property of it and re-assign it in a SnapshotStateList.
When setting a value to MediatorLiveData that reacts to a source added in the constructor of a viewModel or activity onCreate observer in the ViewModel , like this for example:
showingMethodLiveData.addSource(stateChangeLiveData) {
when (it) {
ConfigurationState.CURRENT -> showingMethodLiveData.value = commMethod[it]
ConfigurationState.PENDING -> showingMethodLiveData.value = commMethod[it]
}
}
The value isn't set to the observing view, although the set method is called.
I can work around this by either adding the source in onStart (which creates other problems of registering observer more than once), or using postValue instead of setValue.
The debug of setValue method leads me to following code, where there is an interesting comment that tells the story, the method returns without setting the value to the binded view.
in androidx.databinding package of lifecycle dependency:
class ViewDataBinding:
private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
if (mInLiveDataRegisterObserver) {
// We're in LiveData registration, which always results in a field change
// that we can ignore. The value will be read immediately after anyway, so
// there is no need to be dirty.
return;
}
boolean result = onFieldChange(mLocalFieldId, object, fieldId);
if (result) {
requestRebind();
}
}
The value is not set afterwards either, but only when the mediatorlivedata is invoked again by change in it's source.
Why this situation occurs?
Thank you for the help
PS
I think it may be an android library bug
The use of Mediatorlivedata is to compare two values and then provide a result.
If you want to change the value of a variable, you can simply use MutableLiveData and to assign a new value, write variableName.value = newValue
Should be even easier to achieve like this:
val showingMethodLiveData = Transformations.map(stateChangeLiveData) { commMethod[it] }
I have an ObjectBoxLiveData object with a query that is set at runtime:
private ObjectBoxLiveData<MyObject> myObjectLiveData;
public ObjectBoxLiveData<MyObject> getMyObjectLiveData(Box<MyObject> myObjectBox, String filterTerm)
{
if (myObjectLiveData == null)
myObjectLiveData = new ObjectBoxLiveData<>(myObjectBox.query().equal(MyObject_.filterProperty, filterTerm).build());
return myObjectLiveData;
}
But I also need to be able to change the filterTerm at runtime. My thinking is that I can make a private String currentFilterTerm; object in MyViewModel to see if I need to update the filter term in the LiveData object, but is there a correct way to update the filter term? I worry that setting myObjectLiveData = new ObjectBoxLiveData<> again will leave a memory leak for the previously defined myObjectLiveData or anything tied to it, but I don't see any graceful way to dispose of it or update the query once defined. Is there a way to redefine my query once defined?
The Code A is common usage in java.
I havn't understanded completely the key let of Kotin. Which one should I use between Code A and Code B in kotlin? Thanks!
Code A
if (data!=null){
initAndBindAdapter()
mCustomAdapter.setSelectedIndex(data.getIntExtra("index",-1))
}
Code B
data?.let {
initAndBindAdapter()
mCustomAdapter.setSelectedIndex(it.getIntExtra("index",-1))
}
And more, which one should I choose between Code C and Code D in kotlin if the fun do_NoNeed_data_par doesn't need data parameter ?
Code C
if (data!=null){
do_NoNeed_data_par()
}
Code D
data?.let {
do_NoNeed_data_par()
}
I (personal opinion) think it's a good idea to use simple, regular null checks where you can, although the ?.let method has been listed under the main Kotlin Idioms page of the documentation (which is open for the community to contribute) - so basically, this will be up to your personal preferences of which one is more readable.
The more interesting question is what are the differences, and when you can use each: the main difference is that let holds on to the value of the variable as it was when the let call on it started, and any subsequent uses of it within the let block will reference that same value. If you use a simple null check with if, your variable's value might be changed while the body of the if block is being executed.
So for example, this won't compile, because x can be accessed by multiple threads, and it might be non-null when you read its value first for the null check, but it might become null by the time you read it again for the println parameter - this would be unsafe:
class Foo {
var x: Int? = null
fun useX() {
if (x != null) {
println(x + 10) // (...) 'x' is a mutable property that could have been changed by this time
}
}
}
However, a let will work in the same situation, because it will use whatever the initial value of x had all throughout its execution, even if the x property in the class gets reassigned in the meantime:
class Foo {
var x: Int? = null
fun useX() {
x?.let {
println(it + 10)
}
}
}
You can think of the ?.let statement above of basically performing this, creating a temporary copy of your variable:
fun useX() {
val _x = x
if (_x != null) {
println(_x + 10)
}
}
Operating on this copy is safe, because even if the x property changes its value, this _x copy will either stay null for this entire function, or it's non-null and safe to use.
"should" is opinionated. It all depends on your preference.
If you prefer more functional style code then .let is your answer. If you prefer more procedural code then == null is your answer.
Sometimes, using let() can be a concise alternative for if. But you have to use it with sound judgment in order to avoid unreadable “train wrecks”. Nevertheless, I really want you to consider using let().
val order: Order? = findOrder()
if (order != null){
dun(order.customer)
}
With let(), there is no need for an extra variable. So we get along with one expression.
findOrder()?.let { dun(it.customer) }
Why Android Studio show error when I use No.2 script.
I found no different between 1 and 2.
class Adapter {
var nameList : ArrayList<String>? = null
}
class Program {
private fun send() {
val list: ArrayList<String> = ArrayList()
val adapter = Adapter()
// Case 1
var otherList = adapter.nameList
if (otherList != null) {
list.addAll(otherList) // <--- no error
}
// Case 2
if (adapter.nameList!=null) {
list.addAll(adapter.nameList) // <--- Error here
// Smart cast to 'kotlin.collections.ArrayList<String> /* = java.util.ArrayList<String> */' is impossible, because 'adapter.nameList' is a mutable property that could have been changed by this time
}
}
}
Please explain this case
The IDE should give you a warning, explaining that after the null check, it's possible that adapter.nameList was changed by another thread, and that when you call list.addAll(adapter.nameList), adapter.nameList could actually be null by that point (again, because a different thread could have changed the value. This would be a race condition).
You have a few solutions:
Make nameList a val, which makes its reference final. Since it's final, it's guaranteed another thread can't change it. This probably doesn't fit your use case.
class Adapter {
val nameList : ArrayList<String>? = null
}
Create a local copy of name list before you do the check. Because it's a local copy, the compiler knows that another thread can't access it, and thus it can't be changed. The local copy could be defined with either a var or a val in this case, but I recommend val.
val nameList = adapter.nameList
if (nameList != null) {
list.addAll(nameList)
}
Use one of the utility functions that Kotlin provides for just such a case as this. The let function copies the reference it's called on as a parameter using an inline function. This means that it effectively compiles down to be the same as #2, but it's a bit more terse. I prefer this solution.
adapter.nameList?.let { list.addAll(it) }
Your adapter.nameList is mutable property so please convert it to immutable.
Use this
val nameList : ArrayList<String>? = null
Instead of this
var nameList : ArrayList<String>? = null
Or you can also solve this problem by assert of non null Assert
list.addAll(adapter.nameList!!)
Note :- !! is evaluated at runtime, it's just an operator.
The expression (x!!)
throws a KotlinNullPointerException if x == null,
otherwise, it returns x cast to the corresponding non-nullable type (for example, it returns it as a String when called on a variable with type String?).
adapter.nameList is a mutable property that could have been changed`
The reason for this check and error message is threads. What you have is called a race-condition. In many similar cases it is possible for another thread to change the value of adapter.namelist between the nullity check and the list.addAll call. Clearly this can not happen in your case as the adapter is not leaked from the send function, but I guess the compiler isn't smart enough to know that.
In contrast there is no race condition in case 1 as the namelist is only accessed once.
Also this can not happen if namelist is val rather than var - since the compiler then knows it can not change - so it can not change from non-null to null.