I am new to LiveData thing in general and I am having a hard time understanding the difference between LiveData<String>() and LiveData<String?>(). I used them interchangeably and nothing seams to break. I know that LiveData.getValue() is marked with #Nullable in Java, so we end up getting String? anyway. So what makes LiveData<String?>() different from LiveData<String>()?
This ended up a bit long but I hope it covers everything!
A LiveData is meant to be observed. The observer receives data, and the LiveData's type says what type that data is. A LiveData<String> will only supply non-null Strings to its observers. A LiveData<String?> can supply Strings and nulls.
Which of those you want depends on what you're doing! Do you need to supply nulls, e.g. for some kind of missing value or whatever? Should they be part of your data? If not, like in any other situation, avoid making the type nullable unless it needs to be.
When an observer first observes a LiveData, it receives the current value. That way it can immediately handle the current data, update to display the current state, etc. But it's possible for a LiveData to have no value initially:
// non-null
val liveDataWithValue = MutableLiveData<String>("hi")
val emptyLiveData = MutableLiveData<String>()
// nullable
val nullableLiveDataWithValue = MutableLiveData<String?>(null)
val emptyNullableLiveData = MutableLiveData<String?>()
The first one there has an initial value. If you observe it, and that value hasn't been updated, the observer will immediately be called with "hi" for its parameter.
The second one has no value. If you observe that, the observer won't be called until a value is set on it. This is useful when you don't actually have any initial data - you can still set up your observer, and nothing will happen until some data is actually pushed.
The third one is the same as the first - it's a nullable String? but with a value of null. That's still a value so if you observe it, the observer will immediately be called with that null. It's still a piece of data your observer has to react to and process.
The last one is nullable but with no initial value. Like the second one, this means there's nothing for the observer to receive at first - but when it does have a value set on it, it could be a null. null is just another kind of value!
But if you go poking around at the LiveData's value property, instead of interacting with it through observe, then that no value state is represented internally by null. Java (or at least the version Android targets) doesn't really have a representation of no value separate from null, so that's just how they have to do things. It just doesn't publish anything until you explicitly set a value on it.
So for each of these:
val emptyLiveData = MutableLiveData<String>()
val nullableLiveDataWithValue = MutableLiveData<String?>(null)
val emptyNullableLiveData = MutableLiveData<String?>()
if you read their value in this state, it will be null for all of them. One explicitly has a value of null set on it, the others are both empty. This also means that even though emptyLiveData's type is non-null, its value property can be null, just because of this "can be empty" situation which is true for all LiveData objects. The nullability of the type is purely about what gets passed to observers.
You generally shouldn't be reading value anyway, except internally (wherever you're actually setting the value). Everything else should be interacting with that LiveData by observing it and reacting to the values that are published, and those values will be whatever type (nullable or not) that you specified
LiveData<String?>() meens that livedata can store null, read please this article to be fully informed: https://kotlinlang.org/docs/null-safety.html
Related
How can I get the latest value of a Flow? I don't have a StateFlow where I need that latest value. This is the condensed scenario:
There is a repository exposing a StateFlow
val repositoryExposedStateFlow: StateFlow<SomeType> = MutableStateFlow(...)
Additionally there are mappers transforming that StateFlow like
val mappedFlow: Flow<SomeOtherType> = repositoryExposedStateFlow.flatMapLatest { ... }
mappedFlow is no StateFlow anymore, but just a Flow. Thus, I cannot get the latest/current value as I can when there's StateFlow.
Anyhow, I need the latest value in that Flow at some point. Since this point is not in a ViewModel, but some Use Case implementation, I cannot simply perform a stateIn and hold the latest value in the ViewModel all the time the ViewModel is alive -- otherwise I had to pass on the value to all Use Cases. Actually, within a Use Case I trigger a network refresh which leads to emitting of new values on the StateFlow and thus on the mappedFlow, too.
In the Use Cases I have CoroutineScopes though. So I came up with
suspend fun <T> Flow<T>.getState(): T {
return coroutineScope {
val result = stateIn(
scope = this
).value
coroutineContext.cancelChildren()
result
}
}
Without using coroutineContext.cancelChildren() the method will never return, because coroutineScope blocks the caller until all child coroutines have finished. As stateIn never finishes, I manually cancel all children.
Apparently this is a bad thing to do.
But how can I solve this problem in a better way? In my perception the problem arises from StateFlow mapping resulting in regular Flow instances.
Yes, all you need is to call first() on the flow. Since it is backed by a StateFlow upstream, the first() call will get the current value of that backing StateFlow, run it through whatever transformations happen from the downstream operators, and return that value.
This effectively gets you the same result as your attempt above.
The downside is that all the downstream operators must be run, so it is potentially expensive.
This is only possible if there is an upstream StateFlow. Otherwise, there is no concept of a latest value for you to be able to retrieve.
I would challenge your need to get the latest value, though. Typically, you collect flows, so you're already working with a current value. Flows are intended for reactive programming.
I have an object where I wish to create hot StateFlow objects from a filtered cold SharedFlow. The intent is that the SharedFlow is an event channel of data changes, but all data can be retrieved to get the current state. This means for a given field, I can find the current state, and then monitor the SharedFlow to get state changes.
I would like to provide an API that (as an example) converts the SharedFlow into a StateFlow in a manner as follows:
var myVariable = DEFAULT_VALUE
val mySharedFlow = MutableSharedFlow<Int>()
val myStateFlow = mySharedFlow
.filter { it < 42 }
.asStateFlow(myVariable)// <- Convert to a StateFlow given a default value
This is obviously an overly simplified example, but my situation is more complex, and currently I have to invoke a function when ever a field changes, but currently I do the following:
myObj.onChange.collect(handler)
handler(myObj.getCurrentValue)
fun handler(data: Int) {
// Handle data change
}
But I would prefer to use a Hot StateFlow and remove the need for the second function call. Especially since many consumers of this are small bits of code (mostly just a single expression) that do not need to be in their own function context, and should just be simple lambdas.
Tenfour04 answered my question in his comment. The function I needed is called stateIn().
This question already has answers here:
Smart cast to 'Type' is impossible, because 'variable' is a mutable property that could have been changed by this time
(12 answers)
Closed 1 year ago.
I am studying Android and I am also studying Kotlin.
While writing Android code, I was curious about using it in a let function.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private var curFrag: Fragment? = null
curFrag = fm.primaryNavigationFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// curFrag?.let { transaction.hide(curFrag) } // error.
curFrag?.let { transaction.hide(it) }
}
}
ERROR
Smart cast to 'Fragment' is impossible, because 'curFrag' is a mutable property that could have been changed by this time
In the lambda expression of let(), T is curFrag and the type is Fragment? is.
And T(curFrag) can be replaced by it.
But the moment I used curFrag instead of it, the IDE displayed an error message.
Later, when I checked the type of it, it was Fragment? It was not a Fragment type.
Honestly, I don't understand well.
I don't know why it is automatically smart cast and should only be used for immutable variables.
Kotlin is a null safe language, it tries to eliminate every possible null references from the code. You can perform a nullability check on the variable and then can use it like this
if(curfrag != null) { transaction.hide(curFrag)
This too will only work if variable curfrag is immutable (that means a local variable which is not modified between the check and the usage or a member val which has a backing field and is not overridable), because otherwise it might happen that curfrag changes to null after the check from some other thread.
But Safe calls ?. with let always gives us non nullable result, what Safe calls operator ?. does is, it only performs any operation following it, only if the variable is not-null otherwise it returns null.
It works with all mutable types or member var, It check for the null once and then provides the result. If value is non null it performs the defined operation otherwise skips it. it refers to the copy of that non-null value.
So when you do this
curFrag?.let { transaction.hide(curFrag) }
curFrag can be null as you are directly passing a nullable value.
But in this case
curFrag?.let { transaction.hide(it) }
it only passes value if it's a non-null value.
The let function basically creates a new variable with the same value as whatever you called it on, so it is not really smart-casting the original property.
If you use ?.let, let isn't even called if the value was null. The safe call means the receiver let is being called on is not a nullable value to begin with because otherwise let isn't called at all. The it inside let is just a reference to what it was called on.
Effectively, though it is conceptually similar to smart-casting. There is not really a way to write equivalent Kotlin code that does what ?.let is doing because the ?. safe call is a special operator that has no expanded form.
I must be doing something wrong with Kotlin implementation of view models
I have a view model that has a function to retrieve youtube video id from url.
fun getYoutubeVideoId(url: String): String?{
return "([a-zA-Z0-9_-]{11})".toRegex().find(url)?.value
}
I feel like I'm always in catch 22 because I use this function in a fragment inside with LiveData observable, which forces me to to ? on objects, which then forces me to have return type with ?, which then tirggers if statements to check if objects aren't null.
Here is the vm var
val streamUrl= mainState.getOrNull { it?.account?.streamUrl ?: 0}.distinctUntilChanged()
Here is my shortened observable
streamUrl.observe{
playVideo(getYoutubeVideoId(it))
}
The error from above statement is that it
Requires a String and I'm passing Any
Return should be String and its String?
I'm running around to make sure the types match and its always something not matching or being right. I think I could setup another streamUrl variable under the viewModel besides the observable, but I feel like I should be able to just do it of a single variable.
I hope this makes sense.
So the first thing to embrace with kotlin is: Null Safety.
Null Safety does not mean that you do not get nulls.
It means, that if something is possibly null, the compiler forces you to think about it and handle it at a point that makes sense. If you don't, you potentially get the notorious NullPointerException at an unexpected and possibly ugly point of execution.
So, to eliminate the ? think about where you want to handle the possibility of it being null -> check it -> handle it in an elegant way, and then safely pass the checked result without a ? to the rest of your code.
I'm learning Data Binding by reading up on the official docs. Everything makes sense expect the possible infinite loops in the two-way binding. As per the official docs on two-way binding:
Be careful not to introduce infinite loops when using two-way data binding. When the user changes an attribute, the method annotated using #InverseBindingAdapter is called, and the value is assigned to the backing property. This, in turn, would call the method annotated using #BindingAdapter, which would trigger another call to the method annotated using #InverseBindingAdapter, and so on.
I understand first part of the statement that the method annotate with #InverseBindingAdapter will be called if the attribute is changed and new value is assigned to the backing property.
But what I don't understand is why #InverseBindingAdapter method is called again when #BindingAdapter method is called in this process and how it leads to infinite loops?
Better late than never I guess :) The reason why an infinite loop can happen is InverseBindingAdapter is a basically an observer for changes. So when a user changed something the onChanged observer in InverseBindingAdapter is triggered and executes some logic. So then BindingAdapter also reacts to the change in the field and updates value again so the change listener in InverseBindingAdapter is triggered again and not we are in a loop.
So here is some visual for that
User -> Types in their name "Joe"
InverseBindingAdapter -> triggered by the update
ObservableField/LiveData -> also updated with 2 way binding and now contains value "Joe"
As ObservableField/LiveData was updated BindingAdapter is triggered to set the new value into the filed.
InverseBindingAdapter -> detected another change in the field and got triggered.
step 3, 4, 5 on repeat....
Check my article on Medium on advanced DataBinding it actually describes this case with the ViewPager and 2 way binding example. (Yes, shameless self-plug disclaimer)
This issue can be resolved by checking the old and new values before setting the new value to the target view.
Example:
#BindingAdapter("android:text")
fun setText(editText: EditText, value: Int) {
val newVal = if (value == 0) "" else value.toString()
val oldVal = editText.text.toString()
if (oldVal == newVal) {
return
}
editText.setText(newVal)
if (newVal.isNotEmpty()) {
editText.setSelection(newVal.length)
}
}