おじゃまぷよ系エンジニアメモ

アプリエンジニアからサーバーとインフラエンジニアに転身しました

CloundFirestoreの基本的なCRUD使い方メモ

Android開発においていわゆるRepository層にcloud firestoreを使うときのメモです。
昨日のNANAMemoのときのデータストアに活躍してもらいました。 その時の使用した様子を特に詳しい説明もなく書いてます。 RxJavaと使うと相性がよさそうですね。

gradleにfirestore追加

AndroidStudioのToolメニューからfirebaseの連携をした後にgradleにfiresotreを追加します。
だいたい以下のような感じになります

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation "com.google.firebase:firebase-firestore:11.6.0" //firestore追加
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

apply plugin: 'com.google.gms.google-services' 

CRUDサンプル

data class User(
        val name: String = "",
        val age: Long,
        val email: String = "",
        val createdAt: Date = Date()
)
class UsersRepository(val db: FirebaseFirestore = FirebaseFirestore.getInstance()) {

    private val path = "users"

    fun findAll(onSuccess: (List<User>) -> Unit, onError: (Exception?) -> Unit) {
        db.collection(path).get().addOnCompleteListener {
            if (it.isSuccessful) {
                onSuccess(it.result.toObjects(User::class.java))
            } else {
                onError(it.exception)
            }
        }
    }

    fun findOne(id: String, onSuccess: (User) -> Unit, onError: (Exception?) -> Unit) {
        db.collection(path).document(id).get().addOnCompleteListener {
            if (it.isSuccessful) {
                onSuccess(it.result.toObject(User::class.java))
            } else {
                onError(it.exception)
            }
        }
    }

    fun create(user: User, onResult: (Boolean) -> Unit?) {
        db.collection(path).add(user).addOnCompleteListener {
            onResult(it.isSuccessful)
        }
    }

    fun update(id: String, user: User, onResult: (Boolean) -> Unit?) {
        db.collection(path).document(id).set(user).addOnCompleteListener {
            onResult(it.isSuccessful)
        }
    }

    fun delete(id: String, onResult: (Boolean) -> Unit?) {
        db.collection(path).document(id).delete().addOnCompleteListener {
            onResult(it.isSuccessful)
        }
    }
}

上記のやりたかというか、firestoreのtoObjectメソッドは結構融通が利かず、key名と変数名が一致していないといけません。 また型にも厳しく、Intをもたせることが出来ません
さらにidの情報はaddOnCompleteListenerのDocumentSnapShotが持ってるので、idがわかりません。
なので独自のマッピングメソッドを用意してあげてもいいかもしれません
Userのentityにidをもたせると、次はfirestoreにaddしたときにkeyのid情報と、userのドキュメントidとで情報が重複しますがあまり気にしない。

data class User(
        val id: String? = null,
        val name: String = "",
        val age: Int,
        val email: String = "",
        val createdAt: Date = Date()
) {
    companion object {
        fun mapping(result: DocumentSnapshot): User {
            return User(
                    result.id,
                    result.getString("name"),
                    result.getLong("age").toInt(),
                    result.getString("email"),
                    result.getDate("createdAt")
            )
        }

        fun mapping(result: QuerySnapshot): List<User> {
            val users = mutableListOf<User>()
            result.forEach {
                users.add(mapping(it))
            }
            return users.toList()
        }
    }
}
    fun findAll(onSuccess: (List<User>) -> Unit, onError: (Exception?) -> Unit) {
        db.collection(path).get().addOnCompleteListener {
            if (it.isSuccessful) {
                onSuccess(User.mapping(it.result)) //こんな感じで置き換える
            } else {
                onError(it.exception)
            }
        }
    }

Presenterから使う

class SamplePresenter(val mainView: MainView,val usersRepository: UsersRepository = UsersRepository()){

    fun loadUsers(){
        usersRepository.findAll({
            mainView.showUser(it)
        },{
            mainView.showError()
        })
    }
}

RxJavaを使うVersion

class UsersRepository(val db: FirebaseFirestore = FirebaseFirestore.getInstance()) {

    private val path = "users"

    fun findAll(): Single<List<User>> {
        return Single.create<List<User>> { emitter ->
            db.collection(path).get().addOnCompleteListener({
                if (it.isSuccessful) {
                    emitter.onSuccess(User.mapping(it.result))
                } else {
                    emitter.onError(Throwable(it.exception))
                }
            })
        }
    }

    fun findOne(id: String): Single<User> {
        return Single.create<User> { emitter ->
            db.collection(path).document(id).get().addOnCompleteListener({
                if (it.isSuccessful) {
                    emitter.onSuccess(User.mapping(it.result))
                } else {
                    emitter.onError(Throwable(it.exception))
                }
            })
        }
    }
}
class SamplePresenter(val mainView: MainView, val usersRepository: UsersRepository = UsersRepository()) {

    fun loadUsers() {
        usersRepository.findAll().observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).doOnSuccess {
            mainView.showUser(it)
        }.doOnError {
            mainView.showError()
        }.subscribe()
    }
}