48
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RxJavaAdvent Calendar 2017

Day 5

RxJava の Observable と LiveData と ObservableField をいい感じで使おう

Posted at

LiveData について勘違いしていたことをいくつか からの続きです。

前記事では LiveData は、

  • 購読解除を自動でやってくれるので便利
  • DataBinding(=ObservableField)としては使えない
  • 最低限の合成しかできないので物足りない

という事を書きました。

今回の記事では、上で挙げた微妙な3つの点を解消すべく、RxJava と LiveData と DataBinding をいい感じで併用してみたいと思います。今回もコードは Kotlin です。

RxJava with Android DataBinding

RxProperty を使おう!

はい終了。

RxProperty について書くの何度目なんだ、自分。
作者の @k-kagurazaka@github さんにもお世話になりっぱなしだし、サイコーです、大好きです RxProperty、もっと :star2: を!!

RxProperty<T> は基本的には Observable<T>(というか Subject<T>)なのですが、 .value プロパティで ObservableField<T> に変換できます。

// MainViewModel.kt
class MainViewModel : ViewModel() {

    // GitHub ユーザー名。EditText.text から双方向(TwoWay)バインドされる。
    val user = RxProperty<String>()

    init {
        user.set("hogehoge")
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="net.amay077.livedatasample.view.MainActivity"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="GitHub user name"
            android:text="@={viewModel.user.value}"/>  ←-- user.value とすることで ObservableField に!
    </LinearLayout>
</layout>

このように レイアウトXMLに、viewModel.user.value と記述するとデータバインディングできちゃいます。上記例ではちゃんと双方向バインディングも効きます。

RxJava with LiveData

これは 「RxJava の Observable<T> から LiveData<T> に変換する拡張メソッド」を作ってやりましょう。

// ObservableExtensions.kt

/**
 * Observable<T> を LiveData<T> に変換
 */
fun <T> Observable<T>.toLiveData() : LiveData<T> {

    return object : MutableLiveData<T>() {
        var disposable : Disposable? = null;

        // ライフサイクルがActiveになったときに購読開始
        override fun onActive() {
            super.onActive()

            // Observable -> LiveData
            disposable = this@toLiveData.subscribe({
                this.postValue(it)
            })
        }

        // ライフサイクルが非Activeになったら購読停止
        override fun onInactive() {
            disposable?.dispose();
            super.onInactive()
        }
    }
}

ライフサイクルが非アクティブ(具体的には onPause)になったときに購読停止してあげればきっと大丈夫なはず。

次のような感じで使えます。

// MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        <いろいろ省略>

        // RxProperty を LiveData に変換。
        val liveDataUser = viewModel.user.toLiveData()

        // LiveData を購読
        liveDataUser.observe(lifecycleOwner, Observer { 
            editUserName.setTextKeepState(it)
        })
    }
}

通常は RxProperty.value で、DataBinding すればよいと思うんですが、 BindingAdapter を作るのが面倒とか、View側でちょっと手の込んだことをしたい場合には .toLiveData() で LiveData に変換して、安全な購読管理を享受できます。

LiveData を直接使った方がよいケース

RxProperty -> LiveData する時の注意点として、値が変更時しか通知されない、というものがあります。
昨日書いた ように、 LiveData の特性は、同値チェックは特になく値が設定されれば onChanged を通知するのですが、 RxProperty は「変更通知プロパティ」なので(RxJava 風に言うと distinctUntilChanged なので)、同じ値を連続で設定しても最初しか通知されません。

そのため、Model->ViewModel->Viewの方向へ、値をただ垂れ流して、View側で受信して何かしたい場合は、LiveData をそのまま使うのがよいでしょう。これは EventBus(Messenger) 的な使い方です。

// MainViewModel.kt
class MainViewModel : ViewModel() {

    // View 側から購読して Toast を表示するための LiveData。
    // 変更通知が必要ない(=EventBus的に使う)なら、LiveData をそのまま使うのがいいんじゃなイカ。
    private val _toast = MutableLiveData<String>()
    val toast : LiveData<String> = _toast

    fun showToast(view:View) {
        toast.set("トーストだよ")
    }
}
// MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        <いろいろ省略>

        // Toast を表示するために、 toast:LiveData を購読する。
        viewModel.toast.observe(this, Observer { message ->
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        })
    }
}

上記の例は、トーストを表示するために LiveData<String> を使用しています。
他にはダイアログボックスの表示や、画面遷移の要求メッセージを View 側に通知するためには LiveData をそのまま使うのが良いと思います(というかそこを Observable にする必要を感じない)。

完成系

前回、サンプルとして GitHub のリポジトリ一覧を検索するアプリを作っていたんでしたね。

それを、

  • DataBinding
  • LiveData
  • RxJava
  • RxProperty

全部使って書いてみたコードがこちら↓です。

スクリーンショットはこんなの。

Untitled.gif

AAC 時代でも Observable centric な考えでいいんじゃないでしょうか。
図にまとめるとこんな感じです。

snap.png

RxProperty が流行らない理由が分からないのです。みなさん使ってみてくださいね!

48
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
48
31

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?