【Firebase】 Realtime Database 初トライ前に知っておくといいかも？な５つの箇所(スクリプト編)

## 概要
androidアプリ　[ツケ台帳](https://play.google.com/store/apps/details?id=com.chelak.tsuke_daicho)　を作る際にFirebase realtimedatabaseを初めてさわったのですが、
特に詰まった部分を、備忘録かねて初心者的な目線でメモしてみます　(スクリプト編)
 アプリの解説、過去の投稿は→[こちら](https://qiita.com/n_ku/items/de1675af32ca53acc562)
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1344513/ce99295a-44d5-457a-c365-f2eee1345031.png" width="160" height="326" class="jop-noMdConv"><a href='https://play.google.com/store/apps/details?id=com.chelak.tsuke_daicho&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1'><img alt='Google Play で手に入れよう' src='https://play.google.com/intl/en_us/badges/static/images/badges/ja_badge_web_generic.png'/ width="300"></a>

DB設計編はそのうち。


## 解決方法
**Firebaseのリスナー内でcontextを参照する場合**
`this` だけでは取得できなかった。8行目のようにcontextの後に　`@○○Activity`　の明示が必要。

```kotlin:hogeActivity.kt
        val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {

                // xmlにて実装したListViewの取得
                val listView = findViewById<ListView>(R.id.list_view)

                // ArrayAdapterの生成
                val adapter = DetailAdapter(this@DelDetailActivity, (mpostlist.tsuke?: mapOf()).toList())

                // ListViewに、生成したAdapterを設定
                listView.adapter = adapter
                
           override fun onCancelled(databaseError: DatabaseError) {}
       }database.child("fuga").addListenerForSingleValueEvent(postListener)
```

※コピペでやっているとハマりやすい

<br><br>
**読み取り時のSnapshotの参照先**
できるだけ狭い範囲を指定すべき。
後からセキュリティルールを定める時に困るし、一度に読むデータが多くなるのでユーザが増えると重くなる原因となる。

少なくともルート(下記画像では`database`)を参照するのはやめる(セキュリティルールが全く設定できなくなる)。
NG例：`database.addListenerForSingleValueEvent(Listener)`
よい例：`database.child("messages").addListenerForSingleValueEvent(Listener)`


これをやるとルート直下のノードを複数読み込みたい場合に困るが、私の場合はリスナーを入れ子にした。下記の場合、３重になっている。その際、リスナー・スナップショットの名前は変えること。
(他にもっといい方法があるかもしれない。。)

```kotlin:hogeActivity.kt
val postListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                val postListener_teams = object : ValueEventListener {
                    override fun onDataChange(dataSnapshot_teams: DataSnapshot) {
                        val postListener_users = object : ValueEventListener {
                            override fun onDataChange(dataSnapshot_users: DataSnapshot) {

                                // 本文

                            }
                            override fun onCancelled(databaseError: DatabaseError) {}
                        }
                        database.child("Users").addListenerForSingleValueEvent(postListener_users)
                    }
                    override fun onCancelled(databaseError: DatabaseError) {}
                }
                database.child("Teams").child(teamKey).addListenerForSingleValueEvent(postListener_teams)
            }
            override fun onCancelled(databaseError: DatabaseError) {}
        }
        database.child("Posts").child(postKey).addListenerForSingleValueEvent(postListener)
```


<br><br>
**リスナー内スクリプトの実行タイミング**
リスナー内のスクリプトは、配置に関わらずリスナー以外のスクリプトがすべて実行された後に実行される。
例：onCreate内に　命令文１→リスナー内命令文→命令文２　の順番で記入されていた場合、実際には
命令文１→命令文２→リスナー内命令文　の順番で実行される。
Firebaseの読み取りは１回限りの場合もリスナーを使わないといけないので実行順に注意が必要。後から読み取りのリスナーを実装するとハマりやすい罠。

対策として、読み取りの命令文だけでなくその前後のスクリプトもまとめてリスナー内に入れていた。

<br><br>
**リスナーの種類**
特殊な事情がない限り
`.addValueEventListener`
でなく
`addListenerForSingleValueEvent`（１回だけ実行して消えるリスナー）
を使うべき。リスナーが残っていて変な挙動をする例がよくある。特に初心者（私を含む）の場合はリスナーを活用した開発は難しいので`.addValueEventListener`を原則使うようにしたほうが余計なトラブルがないだろうと思われる。
これはクエリを使ったリスナーでも同様。

<br><br>
**TextViewなどの初期値**
Firebaseだけの問題ではないかもしれないですが、DBから読み取って表示するデータは画面表示の際に読み込みのラグが発生するためxmlに初期値を入れておくとTextViewがチラついて見栄えが悪いです。