22
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Life is Tech ! Advent Calendar 2021Advent Calendar 2021

Day 2

【Android】Kotlin + Firestore + RecyclerViewを使ってTodoアプリを作る

Last updated at Posted at 2021-11-14

Cloud Firestoreとは

Cloud Firestore(以下Firestore)とはFirebaseのサービスの1つです。
サーバー上にデータを保存できるデータベースです。
またデータの変更をリアルタイムでアプリに反映してくれます。

参考:Cloud Firestore | Firebase Documentation

実際に使ってみる

それでは実際にFirestoreを使ってTodoアプリを作ってみましょう。
ソースコードはこちらにあります。(GitHub)

firestore-sample.gif

手順

  1. Firebaseの初期設定
  2. Firestoreの初期設定
  3. View Bindingの設定
  4. data classの作成
  5. カスタムAdapterの作成
  6. データの保存と一覧表示

1. Firebaseの初期設定

まずはFirebaseのプロジェクトを作成します。
公式ドキュメントに則って進めます。

Firebaseコンソールにログインし、プロジェクトを作成をクリックしましょう。
Firebase_console.png

プロジェクトに名前を付けましょう。
Firebase_console.png

次はGoogleアナリティクスの設定です。
今回は使用しないので無効にし、プロジェクトを作成をクリックしましょう。
Firebase_コンソール.png

続行をクリックしましょう。
Firebase_コンソール.png

次に、作成したFirebaseのプロジェクトに自分のAndroidアプリを登録していきます。
Android ロボットのアイコンをクリックしましょう。
Firestore_Sample_-概要-_Firebase_コンソール.png

自分のAndroidアプリのパッケージ名を入力し、アプリを登録をクリックしましょう。
Firestore_Sample_-概要-_Firebase_コンソール.png

google-services.json をダウンロードをクリックし、google-services.jsonをダウンロードしましょう。
Firestore_Sample_-概要-_Firebase_コンソール.png

ダウンロードできたら、自分のAndroidアプリプロジェクトのappフォルダにgoogle-services.jsonを追加しましょう。
FirestoreSample.png

次にFirebase SDKを追加します。
(Android Studio Bumblebee以降は追記を参照)

build.gradledependenciesに以下のコードを追加しましょう。

build.gradle
classpath 'com.google.gms:google-services:4.3.10'

app/build.gradlepluginsに以下のコードを追加しましょう。

app/build.gradle
id 'com.google.gms.google-services'

app/build.gradledependenciesに以下のコードを追加しましょう。

app/build.gradle
implementation platform('com.google.firebase:firebase-bom:29.0.0')

Sync Nowをクリックしましょう。
FirestoreSample_–build_gradle___app.png

ここまで完了したら次へをクリックしましょう。
Firestore_Sample_-概要-_Firebase_コンソール.png

最後にコンソールに進むをクリックしましょう。
Firestore_Sample_-概要-_Firebase_コンソール.png

以上でFirebaseの初期設定は完了です!

追記: 2022/02/13

Android Studio Bumblebeeにバージョンアップされ、依存関係の追加の仕方が変わりました。

build.gradlepluginsに以下のコードを追加しましょう。

build.gradle
id 'com.google.gms.google-services' version '4.3.10' apply false

app/build.gradlepluginsに以下のコードを追加しましょう。

app/build.gradle
id 'com.google.gms.google-services'

app/build.gradledependenciesに以下のコードを追加しましょう。

app/build.gradle
implementation platform('com.google.firebase:firebase-bom:29.0.0')

2. Firestoreの初期設定

これからFirestoreを使えるように設定していきます。
公式ドキュメントのスタートガイドに則って進めます。

まずは左のメニューからFirestore Databaseを選択し、データベースの作成をクリックしましょう。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

セキュリティ保護ルールを設定します。
今回はテストモードで開始します。
テストモードのデフォルトの設定で、30日間データの読み書きが有効となります。
このルールは後から変更できます。
アプリをリリースする際は本番モードに変更し、ルールを変更しましょう。

テストモードを開始するを選択して次へをクリックしましょう。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

続いてロケーションを設定します。
東京はasia-northeast1、大阪はasia-northeast2です。
その他の地域はこちらから確認してください。
ロケーションは一度設定すると変更できません。

ロケーションを選択したら有効にするをクリックしましょう。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

このような画面が表示されます。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

最後に、app/build.gradledependenciesに以下のコードを追加しましょう。

app/build.gradle
implementation 'com.google.firebase:firebase-firestore-ktx'

以上でFirestoreの初期設定は完了です!

3. View Bindingの設定

これからアプリを作成していきます。
まずはView Bindingの設定です。
公式ドキュメントに則って進めます。

まずはbuild.gradleandroidに以下のコードを追加しましょう。

build.gradle
android {
    ...
    buildFeatures {
        viewBinding true
    }
}

続いてMainActivity.ktで以下のように記述しましょう。

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のデータ構造は、画像のようにコレクションとドキュメントから構成されます。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

ドキュメントは保存されるデータです。
たくさんのドキュメントが集まってコレクションの中に入っているイメージです。
ドキュメントはKey-Valueペア形式のフィールドを持ちます。
フィールドは実際にデータの値が保存される部分です。
文字列や数値など様々なデータ型を使えます。

実際にデータが保存されると以下のようになります。
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

この場合はtasksという名前のコレクションに3つのドキュメントが保存されています。
ドキュメントはcreatedAttitleというフィールドを持っており、値がそれぞれ保存されています。
ドキュメントの欄に表示されているランダムな英数字の文字列は自動で生成されるIDです。
これをDocumentIdと呼びます。
DocumentIdは後で登場するので頭の片隅に置いておいてください。

Taskクラスの作成

それでは実際にTaskクラスを作成します。
タスクを識別するためのID、タイトル、作成した時間を扱います。

Task.kt
data class Task(
    @DocumentId
    val id: String = "",
    val title: String = "",
    var createdAt: Date = Date(System.currentTimeMillis()),
)

プロパティに@DocumentIdというアノテーションを付けると、先ほど登場したDocumentIdが自動的に入力されます。
したがって、データの識別子としてDocumentIdを利用できます。

参考:DocumentId | Firebase

5. カスタムAdapterの作成

リストアイテムのレイアウトの作成 (追記:2022/01/30)

RecyclerViewの各アイテムのレイアウトを作成します。

task_list_item.xml
<?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 と組み合わせて使う方法

TaskAdapter.kt
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. データの保存と一覧表示

レイアウトの作成

まずはレイアウトを作成しましょう。

activity_main.xml
<?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を押したらデータが保存されるようにします。

MainActivity.kt
    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コンソールを確認して、データが表示されていたら成功です!
Firestore_Sample_-Cloud_Firestore-_Firebase_コンソール.png

データの一覧表示

次に、先ほど保存したデータをアプリで取得して一覧表示してみましょう。
アプリ起動時にデータを取得するようにします。

MainActivity.kt
    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)
            }
    }

アプリを起動して、保存したデータがリスト表示されたら成功です!

データの変更をリアルタイムに反映する

今までのコードでは、データを追加、削除、更新してもその変更がリアルタイムにアプリに反映されません。
したがって、データの変更をリアルタイムで検知するためのリスナーを設定します。

MainActivity.kt
    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")
            }
        }
    }

データを追加したりして、リアルタイムでアプリに反映されたら成功です!

データの並び替え

工事中

参考:Cloud Firestore でデータを並べ替えたり制限する | Firebase Documentation

22
14
1

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
22
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?