1
0

More than 3 years have passed since last update.

Rxを使ってIllegalStateExceptionを回避する

Last updated at Posted at 2021-02-17

はじめに

こちらの記事を参考にさせていただきました。
Androidで非同期処理を実装していると、IllegalStateExceptionというExceptionが発生することがあります。
これは、onSaveInstanceStateより後のタイミングでFragmentTransactionをcommitしていることが原因で発生するものです。
例えば、APIを叩いてそのレスポンスを受けての処理やタイマーで時限式に動く処理など、アプリがバックグランドにある間に処理が走って、ダイアログを表示したり、次のFragmentに遷移したりするケースがこれに相当するかと思います。

回避方針の詳細説明は他にも記事がたくさん出てくるのでそちらに譲りますが、簡単に言うとonPause ~ onResumeの間でFragmentTransactionをcommitしないようにすればこの問題を回避できます。
この記事ではRxを使って、onPause以降に走る処理をonResumeまで遅延させる方法について書きます。

実装

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val canCommitFragmentTransaction: BehaviorSubject<Boolean> = BehaviorSubject.createDefault(true)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Observable
            .timer(5, TimeUnit.SECONDS)
            .delay { canCommitFragmentTransaction.filter { it } }
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                supportFragmentManager.beginTransaction()
                    .replace(R.id.container, BlankFragment.newInstance())
                    .commit()
            }
    }

    override fun onPause() {
        super.onPause()
        canCommitFragmentTransaction.onNext(false)
    }

    override fun onResume() {
        super.onResume()
        canCommitFragmentTransaction.onNext(true)
    }
}

MainActivityを起動して5秒後にBlanFragmentを作成するシンプルなアプリです。(関係ない部分は省略)
onPause ~ onResumeの間はFragmentTransaction.commit()できないので、canCommitFragmentTransactionというBehaviorSubjectにfalseを持たせておきます。
この部分はフラグをSubjectにしている点以外は、参考記事と同じです。

では、Rxでどのようにしてタイミングをずらすか。それはdelayを使って実装できます。

.delay { canCommitFragmentTransaction.filter { it } }

ここで使っているのは以下のdelayメソッドです。


public final <U> Observable<T> delay(Function<? super T,? extends ObservableSource<U>> itemDelay)

ドキュメントを読むと、このメソッドは

Returns an Observable that delays the emissions of the source ObservableSource via another ObservableSource on a per-item basis.

だそうです。
また、itemDelayについては、

a function that returns an ObservableSource for each item emitted by the source ObservableSource, which is then used to delay the emission of that item by the resulting ObservableSource until the ObservableSource returned from itemDelay emits an item

となっています。
itemDelayは前段までの処理から流れてくるTを受け取って、ObservableSourceを返すようなFunctionです。このFunctionがObservableSourceを返すまでdelayはストリームを遅延させてくれるということです。

上記の実装では、delayの引数に


{ canCommitFragmentTransaction.filter { it } }

を渡しています。
canCommitFragmentTransactionにtrueが流れてくるまではこのラムダ式はObservableSourceを返さないので、アプリがバックグランドにある間は処理を保留し、FragmentTransaction.commit()をonResume以降にずらすことができるということになります。実装だけみるととても簡単ですね。

FragmentTransaction.commit()をする用事がなくても、例えばポーリング処理などアプリがフォアグラウンドの間だけ定期的にAPIを叩き、バックグランドにある間は保留したい、みたいな場合にも同じ方法で遅延処理を実現することができます。その場合はonResumeでフラグがtrueになるとバックグラウンドで保留されていた処理が一気に流れるので扱いに注意する必要があります。

また、もう一つの注意点として、delayメソッドはソースとなるObservableSourceからのonErrorの通知はすぐさま伝播させるため、エラー時にもdelayを使ってハンドリングしたい場合(エラーが起きたらダイアログを表示するとか)はretryWhenの中とかで使ってあげる必要があります。

実装については以上です。

まとめ

Rxを使うとメソッド一つで遅延処理ができてとても便利ですね。
どなたかの参考になれば幸いです。
また、改善点や間違いなどありましたらコメントいただけると有り難いです。

参考記事

1
0
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
1
0