Firebase リスナーを RxJava / RxKotlin に変換する Extension集
今回はみんな大好きFirebaseを使用する際に、
簡単にRxに変換する方法をご紹介したいと思います。
Firebaseといえど、認証系のAuth、データベースのFirestore、ストレージのStorageなど様々な機能があります。
そのすべてのリスナーをRxで流すような実装はよくあります。
/** * FirebaseのTaskをFlowableに変換 * * @param T オブジェクト * @param strategy BackpressureStrategy * @return Flowable */ @CheckResult inline fun <reified T> Task<T?>.toFlowable(strategy: BackpressureStrategy = BackpressureStrategy.LATEST): Flowable<T> { return toObservable().toFlowable(strategy) } /** * FirebaseのTaskをObservableに変換 * * @param T オブジェクト * @return Observable */ @CheckResult inline fun <reified T> Task<T?>.toObservable(): Observable<T> { return Observable.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener if (it != null) emitter.onNext(it) else emitter.onComplete() } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } addOnCompleteListener { if (emitter.isDisposed) return@addOnCompleteListener emitter.onComplete() } } } /** * FirebaseのTaskをSingleに変換 * * @param T オブジェクト * @return Single */ @CheckResult inline fun <reified T> Task<T?>.toSingle(): Single<T> { return Single.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener if (it != null) emitter.onSuccess(it) else emitter.onError(Exception("T is null.")) } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } } } /** * FirebaseのTaskをMaybeに変換 * * @param T オブジェクト * @return Maybe */ @CheckResult inline fun <reified T> Task<T?>.toMaybe(): Maybe<T> { return Maybe.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener if (it != null) emitter.onSuccess(it) else emitter.onComplete() } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } addOnCanceledListener { if (emitter.isDisposed) return@addOnCanceledListener emitter.onComplete() } } } /** * FirebaseのTaskをFlowableに変換 * * @param strategy BackpressureStrategy * @return Flowable */ @CheckResult inline fun <reified T> Task<T?>.toFlowableUnit(strategy: BackpressureStrategy = BackpressureStrategy.LATEST): Flowable<Unit> { return toObservableUnit().toFlowable(strategy) } /** * FirebaseのTaskをObservableに変換 * * @return Observable */ @CheckResult inline fun <reified T> Task<T?>.toObservableUnit(): Observable<Unit> { return Observable.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener emitter.onNext(Unit) } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } addOnCompleteListener { if (emitter.isDisposed) return@addOnCompleteListener emitter.onComplete() } } } /** * FirebaseのTaskをSingleに変換 * * @return Single */ @CheckResult inline fun <reified T> Task<T?>.toSingleUnit(): Single<Unit> { return Single.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener emitter.onSuccess(Unit) } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } } } /** * FirebaseのTaskをMaybeに変換 * * @return Maybe */ @CheckResult inline fun <reified T> Task<T?>.toMaybeUnit(): Maybe<Unit> { return Maybe.create { emitter -> addOnSuccessListener { if (emitter.isDisposed) return@addOnSuccessListener emitter.onSuccess(Unit) } addOnFailureListener { if (emitter.isDisposed) return@addOnFailureListener emitter.onError(it) } addOnCanceledListener { if (emitter.isDisposed) return@addOnCanceledListener emitter.onComplete() } } }
ポイントは3つ
- FirebaseのリスナーはRxがすでに購読解除されている場合は流さないようにしています
- toSingleはオブジェクトがnullだった場合はエラーとして扱うのに対し、toSingleUnitはオブジェクトがnullであってもエラーとして扱わないようにしています。理由として、Firebaseの通信が成功してaddOnSuccessListenerが呼ばれるけど、通知するオブジェクトがない場合はVoidが通知されるからです。JavaまたはKotlinではVoidを生成できないため、代わりにUnitを返してあげています。
たった1行でFirebaseからRxに変換できてしまう
Auth
/** * Emailでサインアップ * * @param email メールアドレス * @param password パスワード * @return Firebaseユーザー */ fun signUpWithEmail(email: String, password: String): Maybe<AuthResult> { return FirebaseAuth.getInstance() .createUserWithEmailAndPassword(email, password) .toMaybe() }
Firestore
/** * すべてのユーザーを取得 * * @return すべてのユーザー */ fun findAll(): Single<QuerySnapshot> { return FirebaseFirestore.getInstance() .collection("users") .get() .toSingle() } /** * IDでユーザーを取得 * * @param id ユーザーID * @return ユーザー */ fun findById(id: String): Single<DocumentSnapshot> { return FirebaseFirestore.getInstance() .collection("users") .document(id) .get() .toSingle() }
Storage
/** * プロフィール画像をStorageから取得 * * @param id ユーザーID * @param fileName プロフィール画像 * @return Uri */ fun fetchImage(id: String, fileName: String): Single<Uri> { return FirebaseStorage.getInstance() .reference .child("users") .child(id) .child(fileName) .downloadUrl .toSingle() }