【Kotlin/Firebase】Realtime Databaseの導入と実装方法!クラウドデータベース
この記事からわかること
- Kotlin/Firebaseで作成したAndroidアプリにRealtime Databaseを導入する方法
- データベースの作成と書き込み/読み取りなどの操作方法
- setValueメソッドやupdateChildren、addValueEventListener、getの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
Firebaseの概要や登録方法については下記記事を参考にしてください。
Realtime Databaseとは?
Firebase Realtime Databaseとはリアルタイムでデータを保存してユーザー間で同期することができるクラウドホスト型NoSQLデータベースです。
Webアプリとモバイルアプリに同じデータを表示させたり、異なるユーザー同士に同じデータを表示させることで共有可能なアプリを作成することができるようになります。
またFirebase Realtime Databaseはデータをサーバーとローカルの2箇所に保持します。ローカルにデータをキャッシュすることでオフラインになってもデータ表示を継続して行えるようになっており、再びオンラインに戻った際に自動でデータを同期してくれます。
アプリからの操作は基本的にはローカルに書き込み処理を行い、サーバーと接続できるタイミングでサーバーと同期します。
データの管理方法
データの保存先は3箇所から選択できるようになっています。同じプロジェクト内でも異なるロケーションにデータベースを構築することができるようです。
- 米国
- ベルギー
- シンガポール
そして実際のデータはJSON形式で蓄積、管理されていきます。テーブルやレコードではなく、ツリー上になったJSON形式の構造でデータは管理されます。そのため入れ子にできるのは32レベルまでと制限が設けられています。
AndroidアプリにRealtime Databaseを導入する流れ
公式ドキュメント:Realtime Databaseのセットアップ方法
流れ
- Firebaseプロジェクトを作成
- データベースの作成
- Androidアプリの登録
- google-services.jsonの追加
- gradleにSDK追加
- 完了
ここでは通常の「Firebaseプロジェクトを作成」や「Androidアプリの登録」、「google-services.jsonの追加」などの手順は割愛しています。詳細は以下の記事を参考にしてください。
【Android Studio】Firebaseの導入方法!Firebase Analytics/Google Analytics
2.データベースの作成
Firebaseプロジェクトを作成したら、プロジェクト内にデータベースを作成しておきます。左側メニューの「Realtime Database」>「データベースを作成」をクリックします。
続いてロケーションを問われるので「米国」にして進めていきます。(※あれば米国ではなく東京(asia-northeast1)か大阪(asia-northeast2)のが良いかもです。)
データベースの設定は本番環境であればロックモードで、練習であればテストモードにチェックを入れて「有効にする」をクリックします。
ロックモードを指定した場合はクライアントがデータの読み取り/書き取りができない状態になっているので「ルール」のfalse
をtrue
に変更しておきます。
ここまできたらプロジェクトにAndroidアプリを登録しておき、「google-services.json」を設置しておいてください。
5.gradleにSDK追加
続いて「build.gradle」にRealtime DatabaseのFirebase SDK(Software Development Kit)を追加していきます。
dependencies {
// 〜〜〜〜〜〜〜〜〜〜
// Firebase Realtime Database
implementation("com.google.firebase:firebase-database-ktx")
}
これで準備は完了です。
使用方法
ここからはAndroidアプリ内でRealtime Databaseを操作する方法をまとめていきます。Androidアプリから操作するとリアルタイムでデータベース内のデータが変更されていくのを「データ」の中から確認することができます。
データベースへの参照
まずはDatabaseReference
インスタンスを生成してRealtime Databaseへ参照できるようにする必要があります。Firebase.database
でデータベースをreference
で参照を取得することができます。
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
val ref = Firebase.database.reference
データの書き込み処理
データの書き込み処理はsetValue
メソッドを使用します。引数に書き込みたいデータを渡します。child
メソッドを使用することで書き込む階層をネストさせることができます。
ref.child("users").setValue("Hello, World!")
Map
型を渡すことでキーと値の関係で保存することもできます。
val user = mapOf("username" to "ame")
ref.child("users").setValue(user)
例えば上記のコードを実行するとデータベース内は以下のようになります。
書き込みに使用できるデータ型
setValue
メソッドに引数に渡してデータの書き込みができるのは以下のデータ型のようです。
- String
- Long
- Double
- Boolean
- Map<String, Object>
- List <Object>
独自のデータクラスも書き込みができるようで、その際はプロパティ名がキー部分になるようです。
データの更新処理
データの更新処理はsetValue
メソッドを使用してそのまま上書きするだけです。また先ほどと同様の値を変更するには以下のようにchild
をメソッドチェーンでつなぐ方法やchildに渡すパスを「/」で区切って指定することも可能です。以下は全て同じ深さのデータを変更しています。
val user = mapOf("username" to "ame")
ref.child("users").setValue(user)
ref.child("users").child("username").setValue("ame")
ref.child("users/username").setValue("ame")
しかしsetValue
メソッドは指定した階層の値を新規で追加(あれば上書き)するためその階層より下に階層があっても消えてしまいます。指定した階層の値だけ書き換えるにはupdateChildren
メソッドを使用します。
updateChildrenメソッド
updateChildren
メソッドは任意の深さでメソッドを呼び出すことで引数に指定したMapの値に更新することができます。
val user = mapOf("username" to "test")
ref.child("users").updateChildren(user)
Map
に複数のキーと値のペアを保持させることで一度に複数箇所のデータを更新することも可能です。
val post = mapOf(
"author" to "ame",
"title" to "Test",
"body" to "Message"
)
ref.child("users").updateChildren(post)
データの削除処理
データの削除処理はremoveValue
メソッドを使用します。指定した値より子要素があった場合はその子要素も全て削除されます。
ref.child("users").removeValue()
また書き込み処理ができるsetValue
やupdateChildren
のMap
の値にnull
を渡すことで削除することも可能です。その場合は一度に複数箇所のデータを削除することもできます。
成功/失敗リスナーを実装する
書き込みや削除などが成功したかどうかを取得したい場合はaddOnCompleteListener
/addOnFailureListener
を使用します。
ref.child("users").setValue(null)
.addOnCompleteListener {
Log.d("Realtime Database", "書き込み成功")
}.addOnFailureListener {
Log.d("Realtime Database", "書き込み失敗")
}
データの読み取り処理
データの読み取り処理はRealtime Databaseではサーバー側とローカルのキャッシュ側にデータを保持しているのでどちらのデータを読みに行くかを指定することができるようになっています。
以下のようなデータベースの中身の状態で読み取り処理を行なってみます。
getメソッド
get
は1回だけデータを読み取るメソッドです。基本的にはサーバーの値を読みにいき、オフライン環境などによってサーバーの値の取得に失敗した場合はローカルキャッシュの値を読みにいきます。
実際の値を参照するにはaddOnSuccessListener
を使用します。引数ではDataSnapshot
型のvalue
プロパティで値を取得することができます。(スナップショットとは「その時点のもの」といった意味の英単語です)
エラーはaddOnFailureListener
でキャッチできます。
ref.child("users").get()
.addOnSuccessListener {
Log.d("Realtime Database", "取得した値: ${it.value}")
}.addOnFailureListener{
Log.d("Realtime Database", "エラー: ${it}")
}
取得した値を扱いやすいようにMap
型に変換するには以下のように実装します。
ref.child("users").get()
.addOnSuccessListener {
val result: Any? = it.value // nullの可能性もある
if (result is Map<*, *>) {
val map = result as Map <String, Any>
Log.d("Realtime Database", "取得した値: $map")
} else {
Log.d("Realtime Database", "データが Map 型ではありません")
}
}.addOnFailureListener{
Log.d("Realtime Database", "エラー: ${it}")
}
addValueEventListenerメソッド
データベースに格納されているデータの変更を観測しつつデータを取得したい場合はaddValueEventListener
メソッドを使用します。このメソッドは初回の呼び出し時とサーバーの値が更新されたタイミングに実行されます。しかしツリー状のルートから呼び出すと全ての変更を観測することになるため、必要な深さまでを指定しアタッチするのが推奨されています。
addValueEventListener
メソッドにはValueEventListener
オブジェクトを渡します。オーバーライドしたonDataChange
からデータを参照でき、onCancelled
では観測をキャンセルされた際のエラーを取得できます。
val userListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val name = dataSnapshot.value
Log.d("Realtime Database", "取得した値: $name")
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("Realtime Database", "キャンセル: ${databaseError.toException()}")
}
}
ref.child("users").addValueEventListener(userListener)
観測を停止させる
観測を停止させたい場合はremoveEventListener
メソッドを使用します。引数にデタッチしたいリスナーオブジェクトを渡します。
ref.child("users").removeEventListener(userListener)
addListenerForSingleValueEventメソッド
ローカルのデータを明示的に取得したい場合はaddListenerForSingleValueEvent
を使用します。
val userListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val name = dataSnapshot.value
Log.d("Realtime Database", "取得した値: $name")
}
override fun onCancelled(databaseError: DatabaseError) {
Log.d("Realtime Database", "キャンセル: ${databaseError.toException()}")
}
}
ref.child("users").addListenerForSingleValueEvent(userListener)
オフライン環境でデータをキャッシュする
オフライン環境でもデータを永続的に表示できるようにするためには明示的に設定をする必要があります。
Firebase.database.setPersistenceEnabled(true)
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。