

When LiveData and Kotlin don’t play well together
source link: https://medium.com/google-developer-experts/when-livedata-and-kotlin-dont-play-hand-in-hand-30149aa794ec
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

When LiveData and Kotlin don’t play well together

The idea of LiveData
was an interesting one. Based on the idea of reactive streams, that was on the peak at that time with RxJava plus adding automatic lifecycle handling — a problem on Android. LiveData
had bad timing though. It arrived just before Kotlin made its impact in the Android community and sometimes both don't play that nice together. Let’s explore why and what can happen!
The good and the bad
The idea of LiveData
was pretty simple: a lifecycle-aware implementation of the Observable pattern. In addition, if you resubscribe, you will get the last emitted value again. It could be compared to a typed version of an EventBus
with sticky messages.
This is one of the core features of LiveData
. But very soon after developers, including the authors, figured, you don’t always want that behavior. Let’s say you have an error value. Then you probably don’t want to show that error value again after resubscribing, like after rotation of a device — for instance.
One way to solve this is SingleLiveEvent
and it’s a good choice for one-off events like errors.
Both worlds
But what if you want a bit of both? You want to have the last value “sticky” plus not showing potential errors multiple times!
In that case, it would be good to remove our error value from LiveData
after they have been consumed, right?
Let’s look at the implementation ofLiveData
:
The current value is saved on a field:
private volatile Object mData;
Initially, it is set to NOT_SET
which.
static final Object NOT_SET = new Object();
Unfortunately, is not public
otherwise, we could use it to reset the value.
What now?
If you search for this problem, one of the most suggested solutions is simply setting it to null
.
Let’s explore this:
viewModel.results.observe(lifecycleOwner) { result ->
when (result) {
SomeResult.Error -> {
handleError()
viewModel.results.reset()
}
SomeResult.Result -> { handleResult(result) }
}
}
where the ViewModel’s implementation is:
class MyViewModel: ViewModel() {
private val mutableResults = MutableLiveData<SomeResult>()
val results: LiveData<SomeResult>
get() = mutableResults
fun reset() {
mutableResults.value = null
}
}
This works, right?
Congratulations! You just introduced a crash into your code!
But why?
You might not even notice and might roll it out. (If so, please add unit tests next time).
The issue here is that we declared LiveData
as non-nullable to the compiler!
Your observer will get notified with this null
value and then immediately crash once it gets a value with a KotlinNullPointerException
under the hood. There is nothing you can do anymore! It’s part of how Kotlin works!
LiveData
is written in Java. It does allow setting it’s value to null
. So the authors could add @Nullable
annotation as we have them on other Jetpack libraries, right? No, as then you always need to handle nullable types in getValue
or the Observer
although you clearly might have declared your LiveData
as non-null. The Generic is runtime information (for Java), annotations are compile-time information, they can’t solve this for us. And Java simply does not have null in the type system.
It’s not a bug - it’s a feature!
How can you solve this?
An easy one would be to mark your LiveData
nullable:
private val mutableResults = MutableLiveData<SomeResult?>()
val results: LiveData<SomeResult?>
get() = mutableResults
Now we can handle that in the observer and only react to non null values. And more important: the compiler forces us to!
Is there another way?
I said earlier that we couldn’t access the original NOT_SET
value. It’s not totally true. We could expose it by setting something into the jetpack package:
package androidx.lifecycle
fun <T> LiveData<T>.reset(){
this.value = LiveData.NOT_SETas T?
}
It’s a bit dirty though.
But other than that, is this actually allowed?
It’s at least not forbidden! Even by the newer stricter rules about private APIs for apps on Google Play, it should be fine.
The actual problem is a different one, but most importantly, you rely on an implementation detail of LiveData
that is not part of the public API and therefore could change anytime!
Ok, what now?
Another alternative is to wrap the results so you can have your own NOT_SET
:
sealed class ViewModelResult {
data class Result(val result: SomeResult): ViewModelResult()
object NotSet: ViewModelResult()
}
We would use NotSet
to clear of previous values and then could just ignore that in the Observer
.
It’s not the most beautiful but probably the cleanest way of solving this.
This is also recommended by Jose Alcérreca in this article.
Anything else?
Also, try to re-think if you need LiveData in the first place. With StateFlow
and ShareFlow
Kotlin provides a great alternative.
If you want to stick with LiveData, think if there is a way you can either have sticky or non sticky events instead of a mix?
In that case, think about updating your Linter to add a custom Lint check where our non-nullable live data cannot be set to null and the compiler in that case would cause an error.
Stay alert! This is not a LiveData issue, it could happen with other libraries too. We got very used to the Kotlin compiler warning us. But if it can’t, it might bring you into troubles you won’t even have without it.
Recommend
-
152
ViewModel 和 LiveData:为设计模式打 Call 还是唱反调?View 层和 ViewModel 层
-
99
-
184
Join GitHub today GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
-
136
README.md LiveDataBus Android消息总线,基于LiveData,具有生命周期感知能力 使用方法 Fork本项目 或者直接拷贝源码:
-
15
Migrating from LiveData to Kotlin’s FlowLiveData was something we needed back in 2017. The observer pattern made our lives easier, but options such as RxJava were too complex for beginners at the time. The...
-
6
LiveData 的历史要追溯到 2017 年。彼时,观察者模式有效简化了开发,但诸如 RxJava 一类的库对新手而言有些太过复杂。为此,架构组件团队打造了 LiveData: 一个专用于 Android 的具备自主生命周期感知能力的可观察的数据存...
-
8
下面是视频内容的脚本整理稿。如果你看了视频,那下面的文稿就不用看了,直接翻到底部评论区吧。 在今年(2021 年)的 Google I/O 大会中的 Jetpack Q&A 环节,Android 团队被问了一个很有意思的问题:LiveData 是要被...
-
3
点击上方蓝字关注我,知识会给你力量
-
7
点击上方蓝字关注我,知识会给你力量
-
4
AI can now play Minecraft just as well as you
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK