Cloud Firestoreとは
Cloud Firestore(以下Firestore)とはFirebaseのサービスの1つです。
サーバー上にデータを保存できるデータベースです。
またデータの変更をリアルタイムでアプリに反映してくれます。
参考:Cloud Firestore | Firebase Documentation
実際に使ってみる
それでは実際にFirestoreを使ってTodoアプリを作ってみましょう。
ソースコードはこちらにあります。(GitHub)
手順
- Firebaseの初期設定
- Firestoreの初期設定
- View Bindingの設定
- data classの作成
- カスタムAdapterの作成
- データの保存と一覧表示
1. Firebaseの初期設定
まずはFirebaseのプロジェクトを作成します。
公式ドキュメントに則って進めます。
Firebaseコンソールにログインし、プロジェクトを作成
をクリックしましょう。
次はGoogleアナリティクスの設定です。
今回は使用しないので無効にし、プロジェクトを作成
をクリックしましょう。
次に、作成したFirebaseのプロジェクトに自分のAndroidアプリを登録していきます。
Android ロボットのアイコンをクリックしましょう。
自分のAndroidアプリのパッケージ名を入力し、アプリを登録
をクリックしましょう。
google-services.json をダウンロード
をクリックし、google-services.json
をダウンロードしましょう。
ダウンロードできたら、自分のAndroidアプリプロジェクトのapp
フォルダにgoogle-services.json
を追加しましょう。
次にFirebase SDKを追加します。
(Android Studio Bumblebee以降は追記を参照)
build.gradle
のdependencies
に以下のコードを追加しましょう。
classpath 'com.google.gms:google-services:4.3.10'
app/build.gradle
のplugins
に以下のコードを追加しましょう。
id 'com.google.gms.google-services'
app/build.gradle
のdependencies
に以下のコードを追加しましょう。
implementation platform('com.google.firebase:firebase-bom:29.0.0')
以上でFirebaseの初期設定は完了です!
追記: 2022/02/13
Android Studio Bumblebeeにバージョンアップされ、依存関係の追加の仕方が変わりました。
build.gradle
のplugins
に以下のコードを追加しましょう。
id 'com.google.gms.google-services' version '4.3.10' apply false
app/build.gradle
のplugins
に以下のコードを追加しましょう。
id 'com.google.gms.google-services'
app/build.gradle
のdependencies
に以下のコードを追加しましょう。
implementation platform('com.google.firebase:firebase-bom:29.0.0')
2. Firestoreの初期設定
これからFirestoreを使えるように設定していきます。
公式ドキュメントのスタートガイドに則って進めます。
まずは左のメニューからFirestore Database
を選択し、データベースの作成
をクリックしましょう。
セキュリティ保護ルールを設定します。
今回はテストモードで開始します。
テストモードのデフォルトの設定で、30日間データの読み書きが有効となります。
このルールは後から変更できます。
アプリをリリースする際は本番モードに変更し、ルールを変更しましょう。
続いてロケーションを設定します。
東京はasia-northeast1
、大阪はasia-northeast2
です。
その他の地域はこちらから確認してください。
ロケーションは一度設定すると変更できません。
最後に、app/build.gradle
のdependencies
に以下のコードを追加しましょう。
implementation 'com.google.firebase:firebase-firestore-ktx'
以上でFirestoreの初期設定は完了です!
3. View Bindingの設定
これからアプリを作成していきます。
まずはView Bindingの設定です。
公式ドキュメントに則って進めます。
まずはbuild.gradle
のandroid
に以下のコードを追加しましょう。
android {
...
buildFeatures {
viewBinding true
}
}
続いてMainActivity.kt
で以下のように記述しましょう。
class MainActivity : AppCompatActivity() {
// バインディングクラスの変数
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// レイアウトを読み込み
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
以上でView Bindingの設定は完了です!
4. data classの作成
今回作成するTodoアプリではタスクを保存します。
保存するデータを扱うために、これからTaskクラスを作成していきます。
Firestoreのデータ構造
実際にTaskクラスを作成する前にFirestoreのデータ構造を説明します。
Firestoreのデータ構造は、画像のようにコレクションとドキュメントから構成されます。
ドキュメントは保存されるデータです。
たくさんのドキュメントが集まってコレクションの中に入っているイメージです。
ドキュメントはKey-Valueペア形式のフィールドを持ちます。
フィールドは実際にデータの値が保存される部分です。
文字列や数値など様々なデータ型を使えます。
この場合はtasks
という名前のコレクションに3つのドキュメントが保存されています。
ドキュメントはcreatedAt
とtitle
というフィールドを持っており、値がそれぞれ保存されています。
ドキュメントの欄に表示されているランダムな英数字の文字列は自動で生成されるIDです。
これをDocumentId
と呼びます。
DocumentId
は後で登場するので頭の片隅に置いておいてください。
Taskクラスの作成
それでは実際にTaskクラスを作成します。
タスクを識別するためのID、タイトル、作成した時間を扱います。
data class Task(
@DocumentId
val id: String = "",
val title: String = "",
var createdAt: Date = Date(System.currentTimeMillis()),
)
プロパティに@DocumentId
というアノテーションを付けると、先ほど登場したDocumentId
が自動的に入力されます。
したがって、データの識別子としてDocumentId
を利用できます。
5. カスタムAdapterの作成
リストアイテムのレイアウトの作成 (追記:2022/01/30)
RecyclerViewの各アイテムのレイアウトを作成します。
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
card_view:cardElevation="2dp">
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="48dp"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/title_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:text="やること"
android:textSize="18sp" />
<TextView
android:id="@+id/date_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|end"
android:text="2022/01/01" />
</LinearLayout>
</androidx.cardview.widget.CardView>
カスタムAdapterの作成
RecyclerViewで使用するカスタムAdapterを作成します。
タスクをリスト表示するので、TaskAdapter.kt
と名付けました。
task_list_item.xml
で定義したViewを操作するため、ここでもView Bindingを使用しています。
参考:[Android]RecyclerView の ListAdapter を viewBinding と組み合わせて使う方法
class TaskAdapter : ListAdapter<Task, TaskViewHolder>(diffUtilItemCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder {
val view = TaskListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TaskViewHolder(view)
}
override fun onBindViewHolder(holder: TaskViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
class TaskViewHolder(
private val binding: TaskListItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(task: Task) {
binding.titleTextView.text = task.title
binding.dateTextView.text =
SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.JAPANESE).format(task.createdAt)
}
}
private val diffUtilItemCallback = object : DiffUtil.ItemCallback<Task>() {
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem == newItem
}
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean {
return oldItem.id == newItem.id
}
}
6. データの保存と一覧表示
レイアウトの作成
まずはレイアウトを作成しましょう。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:id="@+id/title_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:ems="10"
android:hint="title"
android:inputType="textPersonName"
android:minHeight="48dp" />
<Button
android:id="@+id/add_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="Add" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
データの保存
続いてMainAtivity.kt
にコードを書いていきます。
EditTextにタスクを入力し、Buttonを押したらデータが保存されるようにします。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Firestoreをインスタンス化
val db = Firebase.firestore
// ボタンを押したときの処理
binding.addButton.setOnClickListener {
// Taskをインスタンス化
val task = Task(
title = binding.titleEditText.text.toString(),
)
db.collection("tasks")
.add(task)
.addOnSuccessListener { documentReference ->
Log.d(ADD_TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
}
.addOnFailureListener { e ->
Log.w(ADD_TAG, "Error adding document", e)
}
}
}
add()
メソッドを使用することでドキュメントのIDは自動的に生成されます。
参考:Cloud Firestore にデータを追加する | Firebase Documentation
適当な値を入力してデータが保存されることを確認しましょう。
Firebaseコンソールを確認して、データが表示されていたら成功です!
データの一覧表示
次に、先ほど保存したデータをアプリで取得して一覧表示してみましょう。
アプリ起動時にデータを取得するようにします。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 省略
// RecyclerViewの設定
val taskAdapter = TaskAdapter()
binding.recyclerView.adapter = taskAdapter
binding.recyclerView.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
// アプリ起動時に、保存されているデータを取得する
db.collection("tasks")
.get()
.addOnSuccessListener { tasks ->
val taskList = ArrayList<Task>()
tasks.forEach { taskList.add(it.toObject(Task::class.java)) }
taskAdapter.submitList(taskList)
}
.addOnFailureListener { exception ->
Log.d(READ_TAG, "Error getting documents: ", exception)
}
}
アプリを起動して、保存したデータがリスト表示されたら成功です!
データの変更をリアルタイムに反映する
今までのコードでは、データを追加、削除、更新してもその変更がリアルタイムにアプリに反映されません。
したがって、データの変更をリアルタイムで検知するためのリスナーを設定します。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// 省略
// データの変更をリアルタイムでアプリに反映する
db.collection("tasks").addSnapshotListener { tasks, e ->
if (e != null) {
Log.w(READ_TAG, "Listen failed.", e)
return@addSnapshotListener
}
if (tasks != null) {
val taskList = ArrayList<Task>()
tasks.forEach { taskList.add(it.toObject(Task::class.java)) }
taskAdapter.submitList(taskList)
} else {
Log.d(READ_TAG, "Current data: null")
}
}
}
データを追加したりして、リアルタイムでアプリに反映されたら成功です!
データの並び替え
工事中