はいさい、ちゅらデータぬオースティンやいびーん!
概要
FirebaseのPWAでonValueなどのListenerを使っている時に、ユーザーが画面を見ていない時にそのListenerを取り外す方法を紹介します。
なぜ取り外すべきか?
PWAでは、アプリのように動くことを想定して作ります。そして、基本的に携帯・スマホをベースに考えてコードを書いていくものなのですが、スマホアプリ開発で破ってはならぬ鉄則があります。
バッテリー使用を抑えろ。
という鉄則です。
そこで、スマホアプリだと、ページが見えていない時はService Worker以外、何もしないようにしたいですね。
FirebaseのRealtime Database - onValueのListenerについて
FirebaseのJavaScript SDKには、Realtime Databaseのとあるところのデータ変更を全てリアルタイムで監視するonValueという関数があります。以下のような使い方をします。
import { getDatabase, ref, onValue } from "firebase/database";
const db = getDatabase() // initializeAppで作ったappをここに入れることを勧めます。
const chatRef = ref(db, "/chat"); // ユーザーのチャットルームなどのメッセージが入っている場所
onValue(chatRef, (allMessages) => {
const data = allMessages.val();
updateDOM(data); // DOMを更新する関数
}, (error) => console.error(error));
非常に便利な関数ですが、気にするべきことが多々あります。主に以下の3点。
- 入ってくるデータが大量になり、アプリのメモリ使用が膨大になりうる
- 常にchatRefの場所を監視しているので、バッテリー使用が大
- スマホのSuspendと相性が悪く、そのままにすると、Suspendから戻ったページ・アプリがDBから情報を取得しなくなることがある。
onValueのListenerを取り外す方法
本題の解決に入る前に、onValueのListenerを外す方法をあらかじめ紹介しておきたいです。
以下のように、onValueが返す関数を定数・変数の保存しておけば、Listenerを取り外すことができます。
import { getDatabase, ref, onValue } from "firebase/database";
const db = getDatabase() // initializeAppで作ったappをここに入れることを勧めます。
const chatRef = ref(db, "/chat"); // ユーザーのチャットルームなどのメッセージが入っている場所
const disconnect = onValue(chatRef, (allMessages) => {
const data = allMessages.val();
updateDOM(data); // DOMを更新する関数
}, (error) => console.error(error));
disconnect(); // onValueのListenerが止まり、なくなる
解決法:Page Visibility API
上記の問題を解決するために、Page Visibility APIを使用することができます。
以下のようにdocumentにEventListenerを追加します。
document.addEventListener("visibilitychange", () => {
const visibilityState = document.visibilityState; // "hidden" | "visible";
});
onValueと一緒に使えば、以下のような仕組みになります。
import { getDatabase, ref, onValue } from "firebase/database";
const db = getDatabase() // initializeAppで作ったappをここに入れることを勧めます。
const chatRef = ref(db, "/chat"); // ユーザーのチャットルームなどのメッセージが入っている場所
let disconnect = () => {} // ここに最新のonValueのListenerを取り外す関数を入れておく
const establishDatabaseConnection = () => {
disconnect = onValue(chatRef, (allMessages) => {
const data = allMessages.val();
updateDOM(data); // DOMを更新する関数
}, (error) => console.error(error));
}
establishDatabaseConnection(); // 初回
document.addEventListener("visibilitychange", () => {
const visibilityState = document.visibilityState; // "hidden" | "visible";
if (visibilityState = "visible") establishDatabaseConnection(); // ユーザーが戻ったときにListenerを付け直す
if (visibilityState = "hidden") disconnect(); // onValueのListenerが止まり、なくなる
});
これで、ユーザーが見ていない時にむやみにバッテリーを使わないようにできます!
ボーナス:Web Component APIとの円滑の組み込み方
Web ComponentsでFirebaseアプリを作るのはおすすめです!相性抜群です。
上記の仕組みを綺麗にWeb Componentのclassでまとめることができます。
import { getDatabase, ref, onValue } from "firebase/database";
class ChatBox extends HTMLElement {
#disconnect;
#chatRef;
constructor() {
super();
this.#disconnect = () => {};
const db = getDatabase();
this.#chatRef = ref(db, "/chat");
}
connectedCallback() {
this.#establishDatabaseConnection(); // Web ComponentがDOMに入った時に一度実行される
document.addEventListener("visibilitychange", this.#handleVisibilityChange);
}
disconnectedCallback() {
this.#disconnect(); // Web ComponentがDOMから消えた時に実行される
document.removeEventListener("visibilitychange", this.#handleVisibilityChange);
}
#handleVisibilityChange = () => {
const visibilityState = document.visibilityState;
if (visibilityState = "visible") establishDatabaseConnection();
if (visibilityState = "hidden") disconnect();
});
#establishDatabaseConnection() {
this.#disconnect = onValue(this.#chatRef, (allMessages) => {
const data = allMessages.val();
this.#updateDOM(data);
}, (error) => console.error(error));
}
#updateDOM() {...}
}
#updateDOMのところに、以前の記事で紹介したLit-htmlを使うことをお勧めします!
まとめ
以上、Page Visibility APIでonValueの悪い側面を少し減らす方法を紹介しました!
筆者の推測ですが、onValueでListenerをつけて、disconnectで取り外すのにもバッテリー使用が予想されるので、もし頻繁に開けては閉じるようなアプリ(どんなアプリかな?想像がつかん)を開発されているのであれば、このような仕組みは不要なのかもしれません。