15
15

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.

SansanAdvent Calendar 2015

Day 17

Subskription - KotlinでRxJavaを便利にするライブラリ

Last updated at Posted at 2015-12-17

この記事は Sansan Advent Calendar 2015の17日目の記事です。

RxJavaにおいてシンプルにSubscriptionのunsubscribe()を行うためのライブラリSubskriptionというライブラリを作りましたので、その紹介をしたいと思います。

解決したい問題

これまで、RxJavaを用いて非同期処理などを行う場合は、非同期処理が完了する前に利用する側のクラスが破棄される場合など、適切にその処理をunsubscribe()する必要がありました。
そのためにCompositeSubscriptionがよく使用されます。

例えばAndroidのActivityでは通信中に画面遷移が起こり、画面上のObjectが破棄されることがあるため、onPause()などでunsubscribe()する必要があります。このボイラープレート的に必要となるCompositeSubscriptionの定義がRxJavaをAndroid等で扱う場合に厄介になります。

CompositeSubscriptionを使ったunsubscribe()
class SomeActivity : Activity() {

    var compositeSubscription = CompositeSubscription()
    
    fun onClick() {
        val subscription = loadData() // 非同期でデータを取得
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    // Do something
                }
        compositeSubScription.add(subscription)
    }

    override fun onPause() {
        super.onPause()
        compositeSubscription.unsubscribe()
        // CompositeSubscriptionは一度unsubscribe()すると
        // 再度subscriptionの追加ができなくなるので
        // 新しいインスタンスを作りなおす
        compositeSubscription = CompositeSubscription()
    }
}

Androidではこの問題を解決するためのライブラリとして、RxLifeCycleがありますが、RxLifeCycleを使う場合はActivity側でRxActivityクラスを継承しなければならず、他のActivityクラスを継承したい場合などには使えません。

こんなときにCompositeSubscriptionにまつわるボイラープレートの問題を解決してくれるのがこのライブラリです。

インストール

guild.gradleで以下のように指定してください。

build.gradle
dependencies {
    compile 'com.github.yamamotoj:subskription:0.2.1'
}

repositories {
    maven { url "https://dl.bintray.com/yamamotoj/maven" }
}

使い方

Overview

まず、RxJavaを使用するSomeActiviryクラスでは

AutoUnsubscribable by AutoUnsubscribableDelegate()

のように記述し、AutoUnsubscribableインターフェイスをAutoUnsubscribableDelegateのclass delegateを使って実装するようにします。

そうすることで、AutoUnsubscribableDelegateクラスの内部でCompositeSubscriptionが保持されるため、SomeActivityクラスでCompositeSubscriptionを定義する必要はありません。

そして、Observable<T>の拡張関数であるautoUnsubscribe(key:Any)をsubscribe時に呼んでおくことで、同じkeyに合致するunsubscribe(key:Any)の呼び出しタイミングで登録されたsubscriptionがunsubscribe()されます。

Subskriptionを使った例

class SomeActivity : Activity(), AutoUnsubscribable by AutoUnsubscribableDelegate() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun onClick() {
        loadData()
                // このsubscriptionはunsubscribe("onPause")が呼ばれた時にunsubscribeされる
                .autoUnsubscribe("onPause")
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    // Do something
                }

    }

    override fun onPause() {
        super.onPause()
        unsubscribe("onPause")
    }

    fun loadData(): Observable<String> = Observable.just("")
}

このautoUnsubscribe()拡張関数はObservable<T>の他にSubscriptionクラスの拡張関数としても定義されているので、以下のような実装が可能です。

Subscriptionの拡張関数として
    fun onClick() {
        loadData()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    // Do something
                }
                .autoUnsubscribe("onPause") // subscribe()後にもくっつけられる

    }

AutoUnsubscribableの階層構造

AutoUnsubscribableインターフェイスは子要素を持つことができます。AutoUnsubscribableが子要素をもったとき、に親のAutoUnsubscribableのunsubscribe()を呼ぶと、子要素のunsubscribe()も呼び出されます。

AutoUnsubscribableの子要素を持たせる
class Parent: AutoUnsubscribable by AutoUnsubscribableDelegate() {
    val child = Child()
    init{
        // childを子階層として追加する
        addAutoUnsubscrivable(child)
    }
    fun doUnsubscribe(){
        // ここでunsubscribe()するとchildオブジェクトのsubscriptionもunsubscribeされる
        unsubscribe()
    }
}

class Child: AutoUnsubscribable by AutoUnsubscribableDelegate() {
    fun onClick() {
        loadData()
                .autoUnsubscribe()
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    // Do something
                }
    }
}

keyありのunsubscribe(key:Any)とkeyなしのunsubscribe()

今までの例でautoUnsubscribe拡張メソッドとunsubscribeメソッドには引数にkeyを持つものと、引数を持たないものの2つがあることに気づいた方もいらっしゃるかと思います。

autoUnsubscribe/unsubscribeの定義

/** keyを指定せずにautoUnsubscribe登録 */    
fun <T> Observable<T>.autoUnsubscribe() :Observable<T>
fun Subscription.autoUnsubscribe():Unit
fun unsubscribe():Unit

/** keyを指定してautoUnsubscribe登録 */    
fun <T> Observable<T>.autoUnsubscribe(key:Any) :Observable<T>
fun Subscription.autoUnsubscribe(key:Any):Unit
fun unsubscribe(key:Any):Unit

この時、subscriptionを引数keyを指定してautoUnsubscribe(key)した場合はそれに対応するkeyでunsubscribe(key)することができます。

それに対して引数なしのunsubscribe()が呼ばれた時は、keyが指定されたautoUnsubscribe(key)だろうが引数なしのautoUnsubscribe()だろうが、すべてのsubscriptionに対してunsubscribe()が実行されます。また、このunsubscribe()が呼ばれたAutoUnsubscribableがあるAutoUnsubscribableの子要素だった場合、unsubscribe()された子要素は親から切り離されます。もしunsubscribe()されたAutoUnsubscribableが子要素を持っていた場合はそれらの子要素すべてにunsubscribe()を呼び出し、結果的にすべての子要素が切り離されます。

この機能は、例えばListViewのような画面で個々のListItemが何かしらのObservableに対してsubscribeしなければならない状況に有効です。このListItemのSubscriptionがonPause()などのタイミングでunsubscribe()したい場合は親のunsubscribe()が伝播されますが、ListItemそのものが破棄される場合は、自分自身でunsubscribe()することで親要素から切り離され、ListItemそのもののメモリは適切に開放されます。

あとがき

このライブラリの実装に関しての大まかな解説は
この記事はKotlinアドベントカレンダー2015に書いた、KotlinでAndroidのRxJavaをちょっと便利にを参照してください。

ちなみに始めてAndroidのライブラリを公開してmavenから取得できるようにしたのですが、けっこう大変でした。

15
15
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
15
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?