Android Architecture ComponentsのLiveData、便利ですよね。
しかしながら(特にKotlinから)使う場合にいつも気になるのが、valueをNonNullとして扱えない点です。
運用上Null非許容として扱っているのにLiveDataをかませるとアンラップする必要があり、フォースアンラップしてもいいのですが精神衛生上良くありません。
なのでLiveDataをちょこっと拡張してNonNullに限定したクラスを作ってみました。
1. NonNullなObserverを作る
まずそもそものLiveDataのObserverはJavaでプラットフォーム型で定義されており、これをどうにかしないとNonNullとしては扱えません。
以下がオリジナルのObserverコードです
/**
* A simple callback that can receive from {@link LiveData}.
*
* @param <T> The type of the parameter
*
* @see LiveData LiveData - for a usage description.
*/
public interface Observer<T> {
/**
* Called when the data is changed.
* @param t The new data
*/
void onChanged(T t);
}
なのでNonNullなアノテーションをつけたNonNullObserverを作成します。
public interface NonNullObserver<T> {
void onChanged(@NonNull T t);
}
ちなみにJavaで書いているのはあえてで、SAM変換を出来るようにして既存のObserverと使い勝手をあわせるためです。
よりKotlinっぽく書きたい場合は独立したインターフェースとして定義せず、observer: ((T) -> Unit)
とかのブロック構文を使うといいと思います
2. lateinitなLiveDataクラスを作る
次に作るのがNonNull…ではなくlateinit版LiveDataです。(LateInit版が不要であればNonNull版とマージしてしまってもよいかと思います)
Kotlinのlateinit var
と同じように初期値を入れる前提でNonNullとして扱います。
また初期値を入れる前に取得しようとするとUninitializedPropertyAccessException()
を発生させるように記述しています
open class LateInitLiveData<T> : LiveData<T>() {
private val observers = mutableMapOf<NonNullObserver<T>, Observer<T>>()
@MainThread
@Deprecated(
message = "use observe for NonNull.",
replaceWith = ReplaceWith("observe(owner, NonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
super.observe(owner, observer)
}
@MainThread
@Deprecated(
message = "use observeForever for NonNull.",
replaceWith = ReplaceWith("observeForever(nonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun observeForever(observer: Observer<in T>) {
super.observeForever(observer)
}
@MainThread
@Deprecated(
message = "use observe for NonNull.",
replaceWith = ReplaceWith("removeObserver(nonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun removeObserver(observer: Observer<in T>) {
super.removeObserver(observer)
}
@MainThread
open fun observe(owner: LifecycleOwner, nonNullObserver: NonNullObserver<T>) {
val observer = Observer<T> { nonNullObserver.onChanged(it!!) }
observers[nonNullObserver] = observer
super.observe(owner, observer)
}
@MainThread
open fun observeForever(nonNullObserver: NonNullObserver<T>) {
val observer = Observer<T> { nonNullObserver.onChanged(it!!) }
observers[nonNullObserver] = observer
super.observeForever(observer)
}
@MainThread
open fun removeObserver(nonNullObserver: NonNullObserver<T>) {
val observer = observers[nonNullObserver]
if (observer != null) {
observers.remove(nonNullObserver)
super.removeObserver(observer)
}
}
override fun getValue(): T {
return super.getValue() ?: throw UninitializedPropertyAccessException()
}
@MainThread
public override fun setValue(value: T) {
super.setValue(value)
}
public override fun postValue(value: T) {
super.postValue(value)
}
}
使用するObserverをNonNullObserverにするにあたり、新たにNonNullObserver用のメソッドを生やしています。
既存のObserverメソッドは紛らわしいので@Deprecated
扱いにして見えなくしています。このへんはお好みです。
また、既存のObserverとNonNullObserverを紐付けてHashMapでメンバー変数として保持しているのはremoveObserverする際に実際のObserverを取り出すためです。
3. NonNullなLiveDataを作る
LateInit版を作ってしまえばNonNull版を作るのは簡単で、コンストラクタでの値代入を必須にするだけです
open class NonNullLiveData<T>(initialValue: T) : LateInitLiveData<T>() {
init {
value = initialValue
}
}
使い方
NonNullObserver, LateInitLiveData, NonNullLiveDataを使うことでKotlinのNonNull構文やlateinitに近い形でLiveDataを扱うことが出来ます
class MainViewModel(application: Application) : AndroidViewModel(application) {
val title = LateInitLiveData<String>() // or NonNullLiveData<String>("")
init {
title.value = "Main!" // Can't insert null
}
}
class MainActivity : AppCompatActivity() {
private val viewModel by lazy { ViewModelProviders.of(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.label.observe(this, NonNullObserver {
title = it // it is NonNull!
})
}
}
通常のMutableLiveDataだと常にNonNullかどうかを意識する必要があり、nullセーフに扱うことが出来ませんでしたがNonNullLiveDataを用意してあげることでよりLiveData内ではnullを完全に排除する事ができ、安全にLiveDataが使えるようになります。
Kotlin言語を使うのであればなるべくNonNullに寄せたほうが事故が少ないので、個人的にはNonNullLiveDataを作ってからはそっちを使っており、MutableLiveDataはやむを得ずnull許可の値を受け付けるときだけ使うと行った使い方をしています。
おまけ(NonNullLiveEvent)
以前「一度だけ通知するAndroid Architecture ComponentsのLiveDataを作る」というタイトルでLiveEventという自作クラスを紹介したのですが、こちらのクラスも似たようなクラスを作ることでNonNullとして扱うことも出来ます。
open class NonNullLiveEvent<T> : LiveData<T>() {
private val observers = mutableMapOf<NonNullObserver<T>, Observer<T>>()
private val dispatchedTagSet = mutableSetOf<String>()
@MainThread
@Deprecated(
message = "use observe for NonNull.",
replaceWith = ReplaceWith("observe(owner, \"\", nonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
observe(owner, "", NonNullObserver { observer.onChanged(it) })
}
@MainThread
@Deprecated(
message = "use observeForever for NonNull.",
replaceWith = ReplaceWith("observeForever(\"\", nonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun observeForever(observer: Observer<in T>) {
super.observeForever(observer)
}
@MainThread
@Deprecated(
message = "use observe for NonNull.",
replaceWith = ReplaceWith("removeObserver(nonNullObserver)"),
level = DeprecationLevel.HIDDEN
)
override fun removeObserver(observer: Observer<in T>) {
super.removeObserver(observer)
}
@MainThread
@Deprecated(
message = "should not use getValue() in LiveEvent.",
level = DeprecationLevel.HIDDEN
)
override fun getValue(): T? {
return super.getValue()
}
@MainThread
open fun observe(owner: LifecycleOwner, tag: String, nonNullObserver: NonNullObserver<T>) {
val observer = Observer<T> {
val internalTag = owner::class.java.name + "#" + tag
if (!dispatchedTagSet.contains(internalTag)) {
dispatchedTagSet.add(internalTag)
nonNullObserver.onChanged(it!!)
}
}
observers[nonNullObserver] = observer
super.observe(owner, observer)
}
@MainThread
open fun observeForever(tag: String, nonNullObserver: NonNullObserver<T>) {
val observer = Observer<T> {
if (!dispatchedTagSet.contains(tag)) {
dispatchedTagSet.add(tag)
nonNullObserver.onChanged(it!!)
}
}
observers[nonNullObserver] = observer
super.observeForever(observer)
}
@MainThread
open fun removeObserver(nonNullObserver: NonNullObserver<T>) {
val observer = observers[nonNullObserver]
if (observer != null) {
observers.remove(nonNullObserver)
super.removeObserver(observer)
}
}
@MainThread
open fun call(t: T) {
dispatchedTagSet.clear()
value = t
}
}