🪜

Jetpack ComposeでFirestoreを使う

2023/11/01に公開

Overview

最近、Jetpack Composeをキャッチアップしていて、NativeのAndroidでFirestoreを使うにはどうすればいいのかアウトプットするために簡単だけど、難しかったサンプルアプリを作ってみました。

https://firebase.google.com/docs/android/setup?hl=ja
公式の動画の通りにやれば、google-service.jsonは追加できる。

https://www.youtube.com/watch?v=jbHfJpoOzkI

今回やることは、Firestoreにデータを追加することと、追加したデータを表示することです。


summary

2箇所のbuild.gradleにパッケージのコードを追加する。

/Users/JJ_APP/MyFirestore/build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '8.0.2' apply false
    id 'com.android.library' version '8.0.2' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
    // 公式のコードを追加する
    id("com.google.gms.google-services") version "4.4.0" apply false
}

こちらだと、パッケージを追加したら、画面右上のsync nowのボタンを押す。

/Users/JJ_APP/MyFirestore/app/build.gradle
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    // 公式のコードを追加する
    id("com.google.gms.google-services")
}

android {
    namespace 'com.example.myfirestore'// このidをFirebaseに設定する
    compileSdk 33

    defaultConfig {
        applicationId "com.example.myfirestore"
        minSdk 24
        targetSdk 33
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary true
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    buildFeatures {
        compose true
    }
    composeOptions {
        kotlinCompilerExtensionVersion '1.3.2'
    }
    packagingOptions {
        resources {
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
        }
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.8.0'
    implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
    implementation 'androidx.activity:activity-compose:1.5.1'
    implementation platform('androidx.compose:compose-bom:2022.10.00')
    implementation 'androidx.compose.ui:ui'
    implementation 'androidx.compose.ui:ui-graphics'
    implementation 'androidx.compose.ui:ui-tooling-preview'
    implementation 'androidx.compose.material3:material3'
    implementation 'com.google.firebase:firebase-common-ktx:20.1.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
    androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
    debugImplementation 'androidx.compose.ui:ui-tooling'
    debugImplementation 'androidx.compose.ui:ui-test-manifest'

    // 公式のコードを追加する
    implementation(platform("com.google.firebase:firebase-bom:32.4.1"))
    // Firestoreのパッケージを追加する
    implementation("com.google.firebase:firebase-firestore-ktx")
}

MVVMでコードを書いていく。

MVVMぽくしてるだけですが😅
フォルダ分けは今回はやらなくても良くて、ファイルだけ分けてソースコードを書くだけでもいいですよ。

data classを作成する。

package com.example.myfirestore.model

// 初期値がいるらしい?
data class User(
    val born: Int = 0,
    val first: String = "",
    val last: String = ""
)

Repositoryはないけど、MVVM必要だったので作りました。

package com.example.myfirestore.viewmodel

import android.util.Log
import androidx.lifecycle.ViewModel
import com.example.myfirestore.model.User
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow

class UserViewModel : ViewModel() {
    // StateFlowは値を保持するためのクラス
    private val _users = MutableStateFlow<List<User>>(emptyList())
    // usersはStateFlowの値を公開するためのプロパティ
    val users: StateFlow<List<User>> = _users.asStateFlow()
    // Firestoreのインスタンスを取得
    private val db = FirebaseFirestore.getInstance()
    // usersコレクションのデータをリアルタイムに取得
    init {
        db.collection("users")
            .addSnapshotListener { snapshot, e ->
                if (e != null) {
                    Log.w(TAG, "Listen failed.", e)
                    return@addSnapshotListener
                }

                val userList = mutableListOf<User>()
                // 取得したデータをusers変数に格納
                for (doc in snapshot!!) {
                    doc.toObject(User::class.java)?.let {
                        userList.add(it)
                    }
                }
                // StateFlowの値を更新
                _users.value = userList
            }
    }
    // Firestoreにデータを追加する関数
    companion object {
        private const val TAG = "UserViewModel"
    }
}

データを追加する機能

ボタンを押すと実行できる機能です。使わないときは、コメントアウトしてください。
これを参考に追加機能を実装しました
https://firebase.google.com/docs/firestore/quickstart?hl=ja#java_3

package com.example.myfirestore

import com.example.myfirestore.model.Users
import com.example.myfirestore.model.users
import com.google.firebase.firestore.ktx.firestore
import com.google.firebase.ktx.Firebase

// Firebase Firestoreのインスタンスを取得
val db = Firebase.firestore

// 追加するデータをMapで定義
val user = hashMapOf(
    "first" to "Ada",
    "last" to "Lovelace",
    "born" to 1815,
)

// Firestoreにデータを追加する関数
fun addData() {
    // usersコレクションにデータを追加
    db.collection("users")
        .add(user)
        .addOnSuccessListener { documentReference ->
            println("DocumentSnapshot added with ID: ${documentReference.id}")
        }
        .addOnFailureListener {
            println("Error adding document")
        }
}

// usersコレクションのデータをリアルタイムに取得する関数
fun getData() {
    // usersコレクションのデータをリアルタイムに取得
    db.collection("users")
        .addSnapshotListener { value, e ->
            if (e != null) {
                println("Listen failed.")
                return@addSnapshotListener
            }

            // 取得したデータをusers変数に格納
            if (value != null) {
                users = value.toObjects(Users::class.java)
            }
        }
}

🔥データを追加するボタンはこれ

ハードコーディングですが、ボタンを押すとデータを追加するメソッドを実行できます。

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
    ) {
        Text(text = "Hello $name!")
        // Firestoreのデータを取得して表示する
        // 縦に余白を作る
        Spacer(modifier = Modifier.weight(1f))
        // usersコレクションからリアルタイムにデータを取得して画面に表示する
        LazyColumn {
            items(users.size) { index ->
                Text(text = users[index].first)
            }
        }
        Spacer(modifier = Modifier.weight(1f))
        // Firestoreにデータを追加するボタン
        Example {
            addData()
        }

    }
}

@Composable
fun Example(onClick: () -> Unit) {
    FloatingActionButton(
        onClick = { onClick() },
    ) {
        Icon(Icons.Filled.Add, "Floating action button.")
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    MyFirestoreTheme {
        Greeting("Android")
    }
}

アプリを実行するコードはこれです。

ここで必要なコードをimportしてビルドすればアプリの機能は完成です。追加ボタンを使うときは、コメントを外して、画面に表示して使ってください。

package com.example.myfirestore

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import com.example.myfirestore.ui.theme.MyFirestoreTheme
import com.example.myfirestore.view.UserList
import com.example.myfirestore.viewmodel.UserViewModel

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyFirestoreTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    color = MaterialTheme.colorScheme.background
                ) {
                    UserList(
                        viewModel = UserViewModel()
                    )
                }
            }
        }
    }
}

//@Composable
//fun Greeting(name: String, modifier: Modifier = Modifier) {
//    Column(
//        modifier = modifier
//    ) {
//        Text(text = "Hello $name!")
//        // Firestoreのデータを取得して表示する
//        // 縦に余白を作る
//        Spacer(modifier = Modifier.weight(1f))
//        // usersコレクションからリアルタイムにデータを取得して画面に表示する
//        LazyColumn {
//            items(users.size) { index ->
//                Text(text = users[index].first)
//            }
//        }
//        Spacer(modifier = Modifier.weight(1f))
//        // Firestoreにデータを追加するボタン
//        Example {
//            addData()
//        }
//
//    }
//}

//@Composable
//fun Example(onClick: () -> Unit) {
//    FloatingActionButton(
//        onClick = { onClick() },
//    ) {
//        Icon(Icons.Filled.Add, "Floating action button.")
//    }
//}
//
//@Preview(showBackground = true)
//@Composable
//fun GreetingPreview() {
//    MyFirestoreTheme {
//        Greeting("Android")
//    }
//}

Firestoreにデータを保存しておきましょう!

ビルドした画面
先ほど保存したデータが表示されれば成功です🙌

thoughts

今回は、NativeのAndroidでFirestoreを使って躓いたのは、あまり情報がなかったので、試行錯誤をしたところでした。Udemyの講座を参考にしようと思ったのですが、なぜかリアルタイムデータベースしかなかった???

こちらが完成品のソースコードです
https://github.com/sakurakotubaki/JetpackFirebase

Jboy王国メディア

Discussion