FirebaseとReact Nativeでオンラインホワイトボードを作る

この記事は [Firebase Advent Calendar 2020](https://qiita.com/advent-calendar/2020/firebase) の4日目の記事となります。

FirebaseとReact Nativeを用いて、リアルタイムで手書きを共有できるオンラインホワイトボードを作成したので、その設計や実装方法をご紹介します。



![861e3469b84c0b1f76eb9dd3ef90aa09.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29775/06651e3d-9060-aa80-8b40-9a20b0c78c13.gif)


# モチベーション

オンライン会議中にiPadの手書きをサクッと共有したかったことです。
類似のサービスはいくつかあったけど、閲覧者側にログインが必要だったり、機能が過多だったりしたので、もっとシンプルに共有できるアプリが欲しいと思いました。

# 要求仕様

- iPadの手書きをURLで共有できる
- 共有された側はブラウザ or アプリで手書きを閲覧できる
- 手書きの更新はリアルタイムで共有される
- 複数人で同時編集することができる

# 設計の検討

リアルタイムでデータを共有するためにFireStoreを使うのがよさそうです。

難しいのは双方向で編集するために、手書き情報をどのように保持するかです。

手書き情報を一枚の画像として保持すると、同時に複数の人が線を書いたときのデータの合体が困難になります。

そこで、線の一本づつをdocumentとして保持するようにしました。

そのためには線の情報を、FireStoreに保存できるデータとして表現する必要があります。そこで今回はsvgのpath情報として線の情報を保持することとしました。
svgを使うと線を以下のような数値にシリアライズして表現できます。

```
285,156.5 288,156.5 294.5,156.5 301,156.5 305,156.5 307.5,156.5 308.5,157.5
```

# FireStoreの構成

一枚のホワイトボードを`board`ドキュメントで管理します。  

`board`の下に線(`polyline`)のサブコレクションを持たせます。

![Untitled (11).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29775/bd38cfb0-d4f5-1343-f200-14a759a31162.png)


アプリ側では`polylines`コレクションを`onSnapShot`で監視することで、リアルタイムのデータ更新を実現します。

チャットアプリがメッセージをやりとりする要領で、このアプリでは線(`polyline`)をやり取りするイメージです。


![Untitled(1) (5).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29775/92b4cea3-f7cb-5f47-d43f-6491337a2933.png)



# React Native側の実装

React Nativeで手書きをどう実現するか考えたいと思います。

## 線の表示

React Nativeではsvg情報を描画するために`react-native-svg`というコンポーネントが提供されているので、これを利用します。

背景の四角形と、線分を1本を描画するコードは以下になります。


![スクリーンショット 2020-11-29 8.51.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29775/178cc8b9-1a88-622a-8a70-f4a87e73cd25.png)

```tsx
import Svg, { Polyline, Rect } from "react-native-svg";

export const BoardScreen () => {

  return (
      <Svg height={height} width={width} viewBox={`0 0 ${width} ${height}`}>
        {/* 背景 */}
        <Rect
          x={0}
          y={0}
          width={width}
          height={height}
          stroke="#000"
          strokeWidth="1"
          fill="#fff"
        />
        {/* 線 */}
        <Polyline
          points="99.5,151.5 104.5,151.5 119,153.5 135,156 167.5,161 194.5,164.5 218,167.5 236,171 244.5,174.5 249,176 255.5,180 260.5,183.5 267,185.5 271,187.5 274.5,189 276.5,190 277.5,190.5 279,192 279.5,192 281.5,193 283,195.5"
          fill="none"
          stroke="#f00"
          strokeWidth="3"
        />
      </Svg>
  );
}
```

## タッチの検出

お馴染みの`View`コンポーネントでタッチを検出できます。

![1b8cde535391233a750a0315b799b041.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29775/23fb9b14-b395-d3f7-431b-c334ad9e1e93.gif)



```tsx

export type Point = {
  x: number;
  y: number;
};


export const BoardScreen () => {
  // 描画中の線分の点情報を配列で保持する
  const [points, setPoints] = useState<Point[]>([])

  const onTouchMove = (event: GestureResponderEvent) => {
    const { locationX, locationY, touches } = event.nativeEvent;
    if (touches.length === 1) {
      // 描画中の線分の情報を追加する
      setPoints([...points, { x: locationX, y: locationY }]);
    }
  }

  const onTouchEnd = async (event: GestureResponderEvent) => {
      if (points.length > 0) {
        /* TODO: ここでFireStoreにpolylineドキュメントを追加する処理 */

        // 描画中の線分の情報をクリア
        setPoints([]);
      }
    } 
  };

  return (
     <View
        onTouchMove={onTouchMove}
        onTouchStart={onTouchStart}
        onTouchEnd={onTouchEnd}
        onTouchCancel={onTouchCancel}
      >
        <Svg>
          /* 略 */
        </Svg>
      </View>
  );
}
```

実際には他にも細々したことをやっていますが、基本の部分は上記のようなコードで実現できます。


# 課題

線の描画の度にドキュメントを作成するので、通信量がそこそこ多くなります。

費用のことを考えるとFireStoreでなくてRealTime　Databaseのほうが良いのかしれません。
FireStoreは書き込み/読み込みの度に課金されますが、RealTime　Databaseはダウンロード/アップロードの容量ごとに課金されます。

今回の場合小さいデータを頻繁に読み書きするので、もしかするとRealTime　Databaseのほうが費用的には安くなるのかもしれません。

ただデータの取扱いはFireStoreのほうがやりやすいし悩みどころです…


# できたもの

いちおうストアで公開しています。

「air board - ホワイトボードをサクッとシェア」
https://apps.apple.com/jp/app/id1524182843

# (余談)

先述のような「iPadの手書きをサクッと共有したい」というモチベーションで開発をしましたが、開発してからGoogleのJamBoardというサービスを知りました。
限定URLで簡単に手書きを共有できたりします。

正直JamBoardのほうがイケてます（笑）
Googleのサービスでこんなのがあったとは…灯台下暗し。