Cloud Firestoreのリアルタイム機能を使ってAndroidでお絵かき共有アプリを作る

# 今回作るもの
Cloud Firestoreにリアルタイムでデータ同期を行う機能があるとのことだったので、それを使った何かしらのサンプルを作ろうと思った。

複数の端末で共有して絵を描いて見られるようなアプリを作ります。

# Firestoreのデータ構成
今回は描いた絵のViewをそのまま画像として出力し、それをbase64にしてFirestoreに保存しようと思います。

データの構成としては
コレクションID: Rooms
ドキュメントID: 番号
フィールド名をdataとしてstringの値を用意します。

![firestore.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245797/036f6b4d-509e-e039-aacd-be5bca94c856.png)

# 実装
Cloud Firestoreでリアルタイムにデータを取得するにはsnapshot機能を利用します。
[Cloud Firestore でリアルタイム アップデートを入手する](https://firebase.google.com/docs/firestore/query-data/listen?hl=ja)

下記のコードをセットすることでデータが更新されるたびにaddSnapshotListener内に入ってきます。
```kotlin:MainActivity.kt
    private var firestoreSnapshot: ListenerRegistration? = null
// 省略
    private fun setFirestoreSnapshot(){
        val db = Firebase.firestore
        val docRef = db.collection("Rooms").document("0001")
        firestoreSnapshot = docRef.addSnapshotListener { snapshot, e ->

            if (e != null) {
                Log.w("Log", "Listen failed.", e)
                return@addSnapshotListener
            }

            if (snapshot != null && snapshot.exists()) {
                Log.d("Log", "Current data: ${snapshot.data}")

                val snapshotData = snapshot.data
                val value = snapshotData?.get("data") as String

                // base64→bitmap
                val decodedString: ByteArray = Base64.decode(value, Base64.DEFAULT)
                val bitmapData = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)

                // backgroundに設定するためにBitmapDrawableに変換
                val bitmapDrawable = BitmapDrawable(resources, bitmapData)
                binding.paintView.background = bitmapDrawable
            }
        }
    }

```

次に画面をタッチしてなぞると絵がかけるようなものをAndroid側で実装するのですが、お絵かき部分についての実装の詳細は省きます。検索すると以下など色々でてくると思いますのでそちらを参考にしてください。
[【Androidプログラミング】ペイントアプリ(お絵描きアプリ)の作り方実況解説](https://howcang.com/2021/05/30/kt-paint/)


保存するタイミングはonTouchEventのACTION_DOWN、ACTION_MOVE、ACTION_UPにしますが、書き込みにいく回数が多くなりすぎてしまうので処理を軽くしたいのであれば、タイマーを使って何秒おきに呼び出すなどの工夫をする必要があります。

```kotlin:PaintView.kt
// 省略
    override fun onTouchEvent(event: MotionEvent): Boolean {
        val x = event.x
        val y = event.y
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                path.moveTo(x, y)
                saveImage()
                invalidate()
            }
            MotionEvent.ACTION_MOVE -> {
                path.lineTo(x, y)
                saveImage()
                invalidate()
            }
            MotionEvent.ACTION_UP -> {
                path.lineTo(x, y)
                saveImage()
                invalidate()
            }
        }
        return true
    }

    private fun saveImage(){
        val bitmap = loadBitmapFromView(this)
        mActivity!!.updateRecordFireStore(bitmap!!)
    }

    // ViewをBitmapに変換
    private fun loadBitmapFromView(v: View): Bitmap? {
        val b = Bitmap.createBitmap(v.width, v.height, Bitmap.Config.ARGB_8888)
        val c = Canvas(b)
        v.layout(v.left, v.top, v.right, v.bottom)
        v.draw(c)
        return b
    }
```

```kotlin:MainActivity.kt
    fun updateRecordFireStore(imageBitmap: Bitmap){
        // Bitmapをbase64に変換
        val byteArrayOutputStream = ByteArrayOutputStream()
        imageBitmap.compress(Bitmap.CompressFormat.PNG, 50, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()
        val encoded: String = Base64.encodeToString(byteArray, Base64.DEFAULT)

        // データのアップデート
        val record = hashMapOf(
            "data" to encoded
        )

        val db = FirebaseFirestore.getInstance()
        db.collection("Rooms")
            .document("0001")
            .set(record)
            .addOnSuccessListener {
                Log.d("Log", "success")
            }
            .addOnFailureListener { e ->
                Log.d("Log", e.toString())
            }
    }
```

# できあがったもの
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Cloud Firestoreのをリアルタイム機能使ったサンプルを作ってみた。 <a href="https://t.co/zRiQwgprLZ">pic.twitter.com/zRiQwgprLZ</a></p>&mdash; razuma (@razuma4) <a href="https://twitter.com/razuma4/status/1697637384731676940?ref_src=twsrc%5Etfw">September 1, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

# 感想
自前でサーバを用意することなく、また特に難しい実装などもいらずにリアルタイム通信ができるのが素晴らしい。

ゲーム性を持たせるなど複雑なことをしようとするなどすると工夫が必要そう、またはサーバ側でプログラムを書く必要があるかもしれない。
読み書きの回数が多すぎるものには向いていないかもしれない（今回のケースもあまり向いていない？Realtime Databaseの方が良い？など）
チャットとかの使い方であれば全く問題なさそうではある。

何か面白い案があったらまた使ってみようと思います。