未来の自分へ知識貯金

ゆーきのエンジニアブログ

約8分 アプリ開発

Flutter で作る漫画管理アプリの要件と構成 —— ほんだな開発記 1

Flutter で作る漫画管理アプリの要件と構成 —— ほんだな開発記 1

Flutter で作っている漫画管理アプリ「ほんだな」の開発記、第1回です。

第0回では、作り始めた理由と、最初に決めた「何を持たないか」を書きました。

今回は、その反対側の「何を作るか」と「どう作ったか」です。前半は要件(誰のどんな場面のためのアプリか、何を優先して何をやらないか)、後半は Flutter・Riverpod・Drift で組んだ全体の構成です。個々の実装は次回以降に書きます。

何を作るか

作り始めるときに出したのは、ざっくりとした要件でした。いま要件の文書として残っているのは「プロダクト定義」(PRODUCT.md)で、これは Claude に下書きを書かせて、自分で直したものです。開発を進めながら、フェーズが変わるたびに更新していました。

この節の内容は、この文書から引いています。

誰の、どんな場面のためのアプリか

想定したのは、数十〜数百タイトル、数百〜数千巻を持っている漫画のコレクターです。「あの巻、持ってたっけ?」「最新巻はどこまで出てる?」が日常的に起きる人。ライトユーザーも入口としては歓迎しつつ、中心の体験はコレクター向けに合わせる、としています。

使う場面は6つ挙げていました。

場面頻度
A. 書店で「この巻、持ってたっけ?」(最重要)毎週
B. 買って帰った本をまとめて登録する毎週
C. 今週末に何を買いに行くか決める毎週
D. ジャンル別に本棚を整理する月1
E. スマホの機種変更でデータを移すまれ・ただし重要
F. どのシリーズの新刊が出ているか確かめる週1

最重要としていたのは A です。書店の棚から本を取ってバーコードを読み、持っている巻数と最新巻をその場で確かめて、買うか戻すかを決める場面です。

何を優先するか

優先する価値には、順番を付けていました。

  1. 速さ・手軽さ —— スキャンから登録まで、最小のタップ数で終わらせる
  2. 正確さ —— シリーズ名と巻数を、複数の書誌サービス(ISBN からタイトル・著者・巻数などを引けるネット上のデータベース)で補い、ユーザーが直さなくて済む精度を目指す
  3. オフライン対応 —— 書店など電波の弱い場所でも使える
  4. 見た目の満足感 —— 表紙の色や本棚の表示で「自分のコレクション」らしさを出す

ISBN は、本ごとに振られた識別番号です。バーコードの中身もこれです。

3つ目は、文書では「全機能が使える」と書いていました。ただ実際には、ISBN から本の情報を引くところはネットワークが要ります。掲げた目標であって、全部が達成できていたわけではありません。

何をやらないか

やらないことも、文書に並べていました。

  • 巻ごとの個別管理(飛び飛びに持っている、同じ巻を2冊持っている、など)—— 持っている巻数は「何巻まで持っているか」の整数1つで足りるとした
  • 多言語対応 —— 日本語のユーザーに集中する
  • ソーシャル機能(他の人との共有・ランキング)
  • 電子書籍の管理 —— 紙の本に絞る
  • 通販サイトとの深い連携(在庫確認・カート追加など)—— 購入リストから Amazon の検索を開くところまで

ソーシャル機能と電子書籍を外した理由は、第0回に書いたとおり「ユーザーのデータを持たない」ためです。巻ごとの管理を外した経緯は、データベースの回で書きます。

機能を足すかどうかの判断基準

新しい機能を足すか迷ったときに見る基準も、4つ決めていました。

  1. 想定ユーザーが週1回以上使うか
  2. スキャンから管理までの主な流れを単純に保てるか
  3. オフラインで動くか(ネットワークに頼る機能には、つながらないときの代わりを用意する)
  4. 既存ユーザーのデータを壊さないか(データベースの形を変えるときは、既存のデータを安全に移せること)

課題と解き方

文書には、課題ごとに「どう解くか」も書いていました。以下は文書の記述そのままです。

課題解決方法
所持確認が面倒(記憶・メモに頼っている)バーコードスキャン 1 回で volumes テーブルを照合し、同シリーズの別巻でも即座にヒット
書誌情報の手入力が大変OpenBD + Google Books の 2 API 並列取得・巻数合意ロジックで自動補完
どの巻が未購入か把握しにくい未購入フラグ・購入リスト自動生成・各行から Amazon 検索へ直接遷移
本棚の整理・分類ができていないグループ機能・3 つの表示モード(リスト/グリッド/シェルフ)
機種変更でデータが消える不安JSON バックアップ・リストア(isbn → volumes も同時復元)

volumes や「2 API 並列取得」が何かは、次の節で書きます。なお1行目の「別巻でも即座にヒット」は、初めて読み取る巻では書誌サービスへの問い合わせを挟みます(これも次の節で書きます)。

どう作ったか —— Flutter・Riverpod・Drift

ここからは設計書(DESIGN.md)の内容です。設計書も Claude で書いたものです。

全体の構成

UI(画面)
  │
状態管理(Riverpod)
  │
Repository(データの出し入れの窓口)
  │
データベース(Drift / SQLite・端末の中)

書誌サービスへの問い合わせ(BookApiService)
  └ OpenBD と Google Books に同時に問い合わせて、結果をまとめる

画面(UI)は表示と操作の受け付けを受け持ちます。いま画面に出すべき内容は状態管理(Riverpod というライブラリ)が持ち、変わったら画面に知らせます。データベースとのやり取りは Repository という窓口にまとめ、書誌サービスへの問い合わせは BookApiService という窓口にまとめています。

決めていた原則は次のとおりです。

  • 呼び出す向きは一方向(画面 → Repository → データベース)。画面はデータベースを直接触らない
  • Repository は抽象クラス(中身を持たない「型」だけの窓口)を挟む。テストのときに、本物のデータベースの代わりに偽物を差し込めるようにする。実際に、画面のテストでは偽物の Repository に差し替えている
  • 書誌サービスへの問い合わせは1つの窓口に集める。画面側は、どのサービスを呼んでいるかを知らなくてよい

本棚の記録はすべて端末の中の SQLite に置きます。記録を預かるサーバーはありません。本の情報を調べるときだけ、ISBN などを外部の書誌サービスに問い合わせます。

使った主なもの

Flutter 本体以外は、機能ごとに追加したライブラリ(部品)です。書誌情報の2つだけは、外部のサービスにネット経由で問い合わせています。

用途使ったもの種類
状態管理Riverpodライブラリ
データベースDrift(SQLite を型付きで扱う)ライブラリ
バーコード読み取りmobile_scannerライブラリ
表紙からの色抽出palette_generatorライブラリ
表紙画像のキャッシュcached_network_imageライブラリ
書誌情報OpenBD・Google Books外部サービス

データの持ち方

テーブルは4つです。

テーブル持つもの
seriesシリーズの書誌情報(名前・著者・出版社・完結したか・最新巻・表紙)
manga本棚に置いた1件。何巻まで持っているかと、どの棚に入っているか
volumes巻ごとの ISBN と、書誌サービスから取った情報
bookshelf_groups本棚のグループ(名前・色・並び順・他人に見せない棚か)

書誌情報(series)と、持っている状態(manga)を分けています。持っている状態は「何巻まで」の整数1つで、これが上の「巻ごとの個別管理はしない」の実体です。

ISBN は巻ごとに volumes に持たせています。一度読み取った巻は ISBN と本の情報が端末に残るので、同じ巻をもう一度読み取ったときは書誌サービスに問い合わせずに済みます。

初めて読み取る巻は、書誌サービスに問い合わせるので、ネットワークが要ります。調べた結果はタイトルの近さで登録済みのシリーズと照らし合わせ、候補が見つかれば確認や選択のダイアログを出します。どの書誌サービスからも情報が取れなかったときは追加画面に切り替わり、手入力で登録します。

画面

ホーム(一覧:リスト/グリッド/本棚の3表示)
├ カメラボタン → スキャン
│   ├ 登録済みの本 → 詳細(巻数の更新を提案)
│   └ 未登録の本   → 追加
├ 1件をタップ → 詳細
├ カートボタン → 購入リスト
└ メニュー(最新刊の確認・統計・バックアップ・本棚グループの管理など)

むすび

第0回が「何を持たないか」なら、今回は「何を作るか」と、それを「どう作ったか」でした。

ここに書いた姿は、いきなりできたものではありません。次回は、最初の版が動いたあと、次に何を作るかをどう決めたかを書きます。

この記事をシェアする

ゆーき
ゆーき

プログラマー経験10年以上。好きなプログラム言語はC#。使える言語はC++, Python他。好きな開発環境はVisual Studio。

プロフィールを見る →