1週間で作るnostrクライアント with Flutter

# なんだこれは

[Nostr (2) Advent Calendar 2023](https://adventar.org/calendars/8880)の11日の記事になります。
別日の記事や、第一会場の記事もぜひご覧ください。

# nostrについて

本題に入る前に、まずnostrとはなんぞやという方がいらっしゃると思いますので、その点について説明します。知っている方は読み飛ばしていただいて大丈夫です。

<details>
<summary>nostrについて</summary
## 概要

Nostrとは、TwitterがXに変わった頃に各所で見られた代替のうちの一つです。非中央集権型であり、他snsにおけるアカウントが存在しないこと、プロトコルのみが定義されている点などが大きな特徴といえます。

## nostrのざっくりした仕組み

従来のsnsにおけるサーバーがリレーと呼ばれるものに置き換わっています。
ユーザがリレーにできる操作は大きく２つです。

1. 投稿やプロフ変更（まとめてイベントという）を投げつける
2. リレーからのイベント通知を受け取る

イベント通知は他の人の投稿となります。つまり、やフォローする＝イベント通知を受け取るように設定する」ということになります。
なお、ほとんどの場合ユーザは複数のリレーに接続することになります。こうすることで中央集権的になることを防止するという仕組みです。

雑に例えるならば、nostrは現実世界における掲示板みたいな感じです。
掲示板のユーザは複数のお気に入りの掲示板を見に行くことでフォローしている人のポスター（＝投稿）を見ることができ、また自分がポスターを貼ることもできます。
掲示板Aで自分のポスターが撤去されても、掲示板Bではそうなるとは限らず、ここが非中央集権・検閲耐性の所以です。

この記事より良質な記事はいくらでもあります。
詳しくは別のページを読んだほうがいいと思われます。

</details>


# 作ったもの

簡易的なnostrクライアントをflutterで作成しました。flustrという名前のアプリです。
現状主に使用可能な機能は以下の通りとなっております。

- プロフィール確認
- 各ユーザーの投稿表示
- タイムライン表示
- テキスト投稿

現状動作確認ができているのはAndroid（エミュレータ）とWindowsで、理論的にはiOSやmacOS、Linuxでも動作するはずです。webでは動きません。

こちらで公開しておりますのでぜひ動かして遊んでみてください。

https://github.com/uchijo/flustr

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/a4d6e7db-46ef-51ab-e6d4-199c145471d2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/292c9703-e97b-1628-d779-6abfdbd9b590.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/709d90d1-d8c3-2263-6e60-3ace1b47a564.png)


# 動機

なんかみんなアドカレ書いてていいな〜と思い自分でも書きたくなったのですが、ちょうどいいネタがありませんでした。とりあえず何か書くことを決めてから今日まで1週間あり、元々興味もあってボリュームとしてはちょうどいいだろうということで、flutterでのnostrクライアント作成にチャレンジすることに決めました。
いわばアドカレ駆動開発です。


# 方針

このような背景での開発ですので、まず制約として「1週間で終わらせられる機能に絞る」というものがあります。
これを踏まえた上で、今回のflutterアプリ作成の目標を以下のように定めました。

- リアルタイム更新が可能。明示的に再読み込みしなくても新しい投稿が表示される。
- 複数リレーに接続できるようにする
- 最近のナウいdartやriverpodのキャッチアップ

また、沼らないために、今回は手を出さないことも決めておきました。

- パフォーマンス改善 キリがないため
- NSFW対応 俺はなくても困らないため
- プッシュ通知 俺はなくても困らないため
- nip07的な配慮 俺は俺がデータを抜かないことを確信できるため
- フォロー フォローリストを吹き飛ばしそうで怖いため※

# 語りたいところ

### 目標「複数リレー云々」について

2つ目の目標ですが、今回使用したdart-nostrというパッケージには、go-nostrにおけるSimplePoolのような複数リレー接続時の重複削除などの機能がありません。
Nostrの非中央集権を担保する特徴は消しちゃいかんだろうということで、これに関しては自分で実装することにしました。

具体的には、ConnectionPoolというクラスを作成して、このクラスに複数リレー接続周りのごちゃごちゃを隠蔽させています。EOSEまでのイベントを持ってくるメソッド、EOSE以降のイベントをstreamで提供するメソッドが生えており、リレーのurlの配列を渡すだけで重複イベントの削除なども含めて全て自動で行なってくれるようになっています。

### 最近のDart、flutter、riverpod周り

去年終わり頃からflutterのキャッチアップが止まっていたのですが、どうやらその間に様々なことがflutter周りでは起こっていたようです。色々と驚かされました。

まずびっくりしたのはdartのアップデートです。
あの超保守的なDart開発チームが、なんと代数的データ型（records、sealed class）を採用していたのです！
Dartの進化はそれだけにとどまらず、関数型言語ではお馴染みのパターンマッチや分割代入なども採用されており、時代の変化を感じました。

また、riverpodはもはや一年前とは完全に別物のパッケージに進化を遂げていました。以前のようにProviderを自分で定義することはほぼなくなり、基本的には関数を書けばあとはriverpod generatorがボイラープレートを生成してくれます。
またstate notifierに関してはほぼ使われないようになっており、こちらはNotifierと呼ばれる新たな仕組みを使うようになったみたいです。
Notifierに関してもriverpod generatorがいい感じに非本質的な部分を生成してくれるため、本当にbuild runner様々です。
一方でまだ開発体験としては改善の余地があるように感じられた点もいくつかあります。それは、build runnerにドップリ依存している点です。
watchさせておけばコマンドを打たずとも自動で生成されるとはいえ、補完やエラーが出たり消えたりを繰り返すのは非常にストレスが溜まります。
これに関してはやはりDartのメタプログラミング機能がまたれるところです。

### テスト

最近急にテストが大好きになったので、（比較的余裕があった前半では）ある程度はテストを書くという方針で進めてみました。デバッグ入出力をキャッシュできるとかいうの最高ですね。なんで今までテストを書くのに消極的だったんだろう…

なお、一点気になった箇所はwebsocketのテストのしづらさです。
イベントの送受信を行う都合上、モックを作成する場合に関数呼び出し後にコールバックを呼び出したりなどの手間が必要になります。
これが非常に大変で、結局テスト中にwebsocketサーバーを作成するという強引な方法でテストを行うことにしました。
これに関しては何か良いアイデアがあれば教えて欲しいです。

### パフォーマンス

なぜかさらに投稿を読み込むと非常にアプリの動作が重くなります。
今回はパフォーマンスは考慮しないつもりで作っていましたが、あまりにひどいので今後直していきたい点です。

# 今後の目標など

今回の開発で、色々と気になる点をそのまま無視して進んでしまった感は否めません。
特に取り組むべきなのは以下の点かなぁという感じです。

- 重い。特に投稿読み込みが異様に重いのでなんとかしたい
- TLでプロフィールが見えない箇所がある
- リレー取得時の処理
  - 現状、読み込みも書き込みも許可しているリレーにしか接続していない
- 機能不足の解消
  - 上で言及した、今回はやらない箇所など
  - その他、画像やリポスト、リアクションなども

なんにせよ、flustrの開発は細々とという感じにはなりそうですが、続けていくかもしれないし、飽きてやめるかもしれないです。

おわり