naipaka blog

振り返りや技術メモなど

2026年9月の目標振り返り

はじめに

こんにちは。ないぱかです。

challenge-every-month で立てている毎月の目標と振り返り。

先月の分:

naipaka.hatenablog.com

2026年9月の目標と結果

9月最初に立てた目標と達成状況は下記の通りで、3つとも達成!

  • [x] 職務経歴書を更新する
  • [x] 減量をやり切る
  • [x] 保険を検討する

職務経歴書を更新する

達成!📝

育休明けのことを考えて、半年ぶりに職務経歴書を更新した。
Claude Code と壁打ちしながら、これまでの仕事を案件ごとに振り返った。

最初は、職務経歴書の例でよく見るように、どれだけ数値を改善したかで成果を書くつもりだったが、悲しいことにいざ書こうとすると出せる数字がほとんどなかった
改善はいろいろやってきたつもりなのに効果を前後で測っていなかったので、やったことをただ羅列することしかできなかった。

そこで方針を変えて、案件ごとにどう仕事を進めたかを中心に書き、やったことはその例として挙げる形にした。
自分の仕事の進め方は言語化できたのだが、数字がないからか並べてみると正直たいしたことしてないなーとも思った。

復帰後は、何か改善するなら計測する前提で取り組みたい。

仕上げの推敲では X で見つけた yomiyasu を使わせてもらった。
自分でも読みながら直していたつもりだったが、まだ残っていた AI っぽい言い回しを直せた。

github.com

減量をやり切る

達成!💪

9月は食事制限も運動も1日も欠かさず続けられた。

朝と昼の食事は固定の作り置きを食べた。
鶏肉とブロッコリー、にんじん、しめじ、卵をめんつゆで炒めたものと、グラムを量ったご飯と納豆と一緒に食べて、夜は残ったカロリーの範囲で食べる形。

この作り置きは、パーソナルジムで食事管理をしていた頃から作っている。
毎食同じでも苦じゃないので、そこまで大変ではない。

運動は妻と一緒に自重トレを毎日やった。
内容はディップスと腕立て、スクワット、懸垂。

まだ習慣になりきっていないので、一人だったら忘れたりサボったりする日もあったと思うが、二人だとどちらかが思い出せば声をかけられるのが大きい。

結果、体重は1ヶ月で3kg落ちた。
お腹を触った感じ皮下脂肪は少し減ってそうなので、10月のプライム感謝デーで体組成計を買って、ちゃんと毎日体脂肪率を測りたい。
今はたぶん20%前後なので、18%以下を目指して10月も続ける。

保険を検討する

達成!🛡️

8月に作った、保険に入るべきかを判断するためのツールは、結局3回くらい作り直した。
作り直すうちに、保険ごとの損得を細かく計算するより、生活防衛資金で払いきれるかどうかで決めればいいと考えるようになった。
自分で払えないものに備えるのが保険の本来の使い方なので、検討した保険はすべてこの基準で判断した。

医療費だけは最後まで迷った。
現金で備えるのと、その分を投資に回して保険に入るのとでどちらが得かも計算してみたのだが、いくら現金で持っておけばいいのかが決められなかった。
医療保険は実費ではなく「入院1日いくら」で払われるものが多いので、比べるのも難しい。
結局、大きな病気や怪我で治療費がかさんでも、家具や家電を急に買い替えるのと同じだと考えて、生活防衛資金で払うことに決めた。

ただ、保険の効かない1000万円以上かかるような治療となると、さすがに生活防衛資金では払いきれない。
親族ががんで闘病していて、まだ国に承認されていない治療の抽選に応募しているのを見ていると、自分もいざとなったら藁にもすがる思いでそういう治療を選ぶだろうと思った。
ということで、そこにだけは最低限備えておくことにした。

宣言外目標

freee の会計作業自動化

8月に未達だった freee の自動化は、9月の前半で構築した。
常駐バッチはやめて、月に一度 Claude Code or Codex から叩く形に作り直した。
各サービスの証憑集めから仕訳と検算まで、AI エージェントが進めてくれる。
毎月30〜60分かかっていた手作業が1〜2分になった。

naipaka.hatenablog.com

9月を振り返って

3つとも達成できたけど、ほとんど月末に駆け込みで終わらせた形だった。
9月の目標を決めたのが月初ではなかったうえに、前半は freee にかかりきりで、職務経歴書と保険検討に手をつけたのは後半からだった。
8月の振り返りで「どう達成するかの計画を立てる」と書いたものの、決めたのはやる順番くらいで、その順番も保険のツールを作り直しているうちに崩れた。

10月はやることが詰まっていて、9月中に終わらせないとまずかったので、なんとか間に合わせた。
追い込まれればできる。

次はバッファを持たせた計画を立てたい。

育休日記は目標とは別に毎日続けていて、9月30日で262日目になった。

note.com

GitHub の草も、9月は1日も途切れなかった。
1月からの草はこんな感じ。

contributions

2026年10月の目標

  • 来年からの資産運用の準備をやりきる: 新NISA金融機関変更、iDeco上限引き上げ対応、こどもNISAなど
  • 興味のある分野の資格の勉強を始める: テキストを1周、試験を申し込む
  • アプリを1回アプデする: 何かしらリリースする

資産運用は、来年に向けてやることが重なっている。
新 NISA はポイント還元の改悪で証券会社を移すことにした。
iDeCo は12月の制度改正で上限が上がるので積立額を変える予定。
こどもNISA も始まるので、息子の口座も用意しなければいけない。

ちょうど10月1日に、SBI 証券がこどもNISA 専用で信託報酬0.00%のオルカン(ゼロカン)を出すと発表されていた。

www.sbisec.co.jp

資格は FP3級を受けようかなと思っている。
9月に保険を検討したり、10月に NISA や iDeCo の手続きを考えているうちに、お金のことをちゃんと体系立てて知りたくなった。
10月中に11月の試験日を決めて申し込み、ひとまずテキストを1周する。

本当は個人開発アプリの全てでやることがあって触りたいのだが、10月はほかにやることが多くて手が足りなさそう。。。
今月はいったん1アプリ分対応できたらOKとする。

10月もやっていき!

freee-mcp で毎月の経理を半自動化した

こんにちは。ないぱかです。

以下は、freee-mcp と Claude Code / Codex のスキルで毎月の個人事業(アプリ開発)の経理を半自動化した話です。

この記事で言いたいこと

  • Claude Code / Codex、freee-mcp、Playwright、API を使って経理を半自動化したよ
  • 各サービスの証憑集めと添付、仕訳と検算の手間が消えたよ
  • 手作業の時間が 30〜60 分から 1〜2 分になったよ
  • 消込(明細と取引の紐付け)が freee-mcp でできるようになったら人間は確認・承認するだけになるよ

構成

`/accounting` 実行時のシーケンス図

ユーザーは最初の起動、ログインが切れたときの入力、承認、消込だけを行う。
freee とは freee-mcp 経由、各サービスとは Playwright を使う取得スクリプト経由でやりとりする。

役割分担

役割 担当
未登録明細の取得、既存登録の参照、証憑アップロード、取引登録 freee 公式 freee-mcp(ローカル stdio)
サービスごとの証憑・レポート取得(月を指定してファイルに保存) scripts/fetch-<service>.ts
仕訳の組み立てと検算(資料から明細行を作り、明細の金額と突き合わせる) scripts/<service>-journal.ts
分類、読み取り、組み立ての指示、ユーザーへの確認 AI エージェント(Claude Code / Codex)
freee の登録の形、サービス別の変換ルール references/freee-shape.md、references/<service>.md
手順 SKILL.md(/accounting)

skill は .agents/skills/accounting/ に置き、.claude/skills を symlink にして Claude Code と Codex で共有している。
共通ルールは AGENTS.md(CLAUDE.md は symlink)。
freee-mcp はローカルで起動するので、Claude Code は .mcp.json、Codex は .codex/config.toml に設定を書けば、同じトークンで動く。

運用

毎月の運用方法は以下。

  • 連携している銀行口座やクレジットカードの明細が揃ったころに /accounting を叩く
  • ブラウザのログインが切れていれば、開いた Chrome でログインする
  • 提示された内容を承認する。検算が合わない候補は登録対象に入らない
  • 登録後、freee の画面で明細を開き「未決済取引の消込」で登録した取引を選ぶ
  • ルールのない明細は「不明」として一覧に残る。年に数回のもの(povo、Amazon など)は references/manual.md にルールを置き、証憑は自分で downloads/manual/ に置く

AI を噛ませている理由は、以下の通り。

  • どの明細がどのサービスの何月分かがロジックのみで判断しづらい(入金日と売上月がずれる。App Store は翌月末〜翌々月初、Google Play は翌月 16 日)
  • 資料の読み取りが多様(CSV の列、PDF の請求書、HTML の領収書)
  • ルールにない明細が出たときに、請求書を読んで勘定科目・税区分・取引先を決め、ルールに足すことができる

また、AI を挟む上で以下に注意した。

  • 金額の計算、検算はスクリプトで行う
  • 秘密情報(トークン、cookie)は AI が読めない場所に置き、ツールの結果にも出さない

サービスごとの記帳ルール

サービスごとの記帳ルール(どの資料のどの値をどの行にするか、税区分、発生日、証憑は何を何件付けるか)は references/<service>.md に記載。

ルールは freee に登録済みの取引を半年分読んで AI に書かせ、自分がレビューして確定させた。

各サービスの証憑の取得方法

個人事業では主に以下の 6 つのサービスで収支があり、それぞれ証憑をダウンロードする必要がある。
今回の構成では、以下のような形で取得している。

サービス 資料 取得方法 取得元
App Store 支払サマリー CSV、Tax on Commission PDF Playwright App Store Connect「支払いと財務報告」画面の内部 API
Google Play earnings レポート ZIP API Cloud Storage(gcloud storage cp)
AdMob 月次明細 CSV、支払い領収書 Playwright 「お支払い」→ 取引ページ
Google Cloud 請求書 PDF、CSV 請求書、月次明細 CSV Playwright Console「お支払い」→「料金の履歴」の月カード
OpenAI Invoice / Receipt PDF Playwright ChatGPT の請求画面 → Stripe の hosted invoice
Anthropic Invoice / Receipt PDF Playwright claude.ai の請求画面 → Stripe の hosted invoice

Playwright では、自分が各サービスにログイン済みの Chrome を操作する。
取得したファイルは月ごとに downloads/ に保存して後続処理で参照する。

使用できなかった API

元々各サービスの API を使って証憑をダウンロードするつもりだったが、API が存在していても使えなかったものがあった。

App Store Connect API

財務レポートのエンドポイントは存在しているが、返ってくるのは通貨ごとの売上で、JPY への換算額が入っていなかった。
帳簿に載せるのは Apple から実際に振り込まれた JPY なので、換算は Apple のレートで行われた値が必要になる。
App Store Connect の「支払いと財務報告」の画面が内部で呼んでいる API を使うと、支払サマリー CSV(通貨ごとの収入・源泉徴収税・為替レート・JPY 換算の収益)と Tax on Commission PDF(手数料と JCT)を返してくれるので、そちらからダウンロードしている。

AdMob API

取得できるレポートは「見積もり収益額」で、無効なトラフィックの控除後の「確定額」ではなかった。
実際に 11 か月分を API で取って銀行の入金額と比べると、全部の月で 0.02〜0.26% 高かった。
確定額を返す API はなかったため、Playwright でお支払いページの月次明細 CSV から取っている。

Cloud Billing API

請求書が取得できるエンドポイントが存在しなかった。
BigQuery への課金データのエクスポートはあるが、それは利用明細であって Google Cloud Japan が発行する請求書ではないので証憑にならない。
請求書 PDF は Console の「お支払い」→「料金の履歴」の月カードにしかなく、そこから取得している。

頻度が少ない明細の証憑

以下の例のように、年に数回あるかないかの明細は取得スクリプトを書かず、references/manual.md にルール(取引先、勘定科目、税区分、発生日、証憑の取り方)だけ置いている。

例:

  • povo: 検証用端末の SIM で使用。請求書がスマホアプリからしか取得できないため、どちらにせよ自動化は厳しい。
  • Amazon: PC 周りの商品購入で使用。普段使いのアカウントで購入しているため、自動化で購入履歴から対象の商品を的確に探せる保証がなさそうだったので証憑は手動とした。
  • Apple Store: 数年に 1 度 Mac 等の端末購入で使用。頻度が少ないので自動化しなかった。

/accounting でこの明細に当たると、入力値と証憑の置き場を示すようにしている。
ユーザーが証憑を downloads/manual/ に置けば、それを読んで仕訳を組み立てる。

また、references/manual.md のルールにも載っていない明細は、その場で請求書を読んで取引先・勘定科目・税区分を調べ、ルールを作って manual.md に足すようにしていて、次からは上と同じ扱いになる。

freee-mcp の制約

実装中に気づいて設計を変更した部分がいくつかある。

Remote MCP では証憑を上げられない

最初は freee 公式の Remote MCP を使って実装を始めたが、freee_api_post は multipart を送れず POST /receipts ができなかった。
ローカルの freee-mcp(stdio)には freee_file_upload があったため、ローカル接続に切り替えることで対応した。

明細と取引を紐付ける API がない

freee が銀行口座やクレジットカードから同期した明細を画面で「登録」する場合、取引の作成と明細との紐付けを 1 回でやってくれていた。
しかし、API には取引を作る操作しかなく、作った取引に payments を付けても明細は未処理のまま残ってしまった。
そのため、取引は未決済で登録し、紐付け(消込)は画面でやることにした。

消込 API は 2026 年 9 月 15 日のプレスリリースで提供予定が出ているので、使えるようになったら移行したい。(参考)

まとめ

毎月の個人事業の経理が半自動化されたことで、面倒な作業がなくなってスッキリした。
こういう決まった作業はどんどん自動化していきたい。

消込が Public API と freee-mcp で使えるようになったら、さらに手作業がなくなるので公開されるのが楽しみ。

2026年8月の目標振り返り

はじめに

こんにちは。ないぱかです。

challenge-every-month で立てている毎月の目標と振り返り。

最近振り返りと目標立てが遅れ気味。

先月の分:

naipaka.hatenablog.com

2026年8月の目標と結果

8月最初に立てた目標と達成状況は下記の通りで、達成できたのは1つだけ。

  • [ ] freeeの会計作業自動化
  • [x] Google Play周りの更新対応
  • [ ] 本を2冊読む

久しぶりに未達!

freeeの会計作業自動化

未達。着手はしたが完成まで持っていけなかった。

作ろうとしていたのは、Mac mini で動かし続ける常駐バッチ。
定期実行で freee から未仕訳を取ってきて、自動で仕訳してくれるイメージのもの。

仕様を作って、設計して、シーケンス図を書いて、骨組みを用意して、実装は AI に任せていた。
そこまでは進んだものの、いざ動作確認をしたいというタイミングで1週間の帰省旅行が重なってしまい、そのまま8月末まで何も触れずに終わってしまった。

反省点としては AI に作らせるからと言って横着せず小さく始めるべきだったこと。
自分が仕訳する量はそんなに多くないので、月に一度、手動でコマンドを叩いたら動くくらいで十分だった。

今はその形に軌道修正中。
CLI 一発で freee から未仕訳を取ってきて、App Store Connect や Google Cloud 等々から必要な領収書などを API で取得して、AI に仕訳させてドラフトまで作る。
こちらで確認して問題なければそのまま登録させる、という流れ。

出来上がったらまた記事にしたい。

Google Play周りの更新対応

達成!🤖

対象 API レベルと Billing Library の2件、どちらも8月31日が期限だったが8月中旬に無事に終わらせた。

対象 API レベルのほうは Flutter のバージョンを上げるだけで済んで、そこまで大変ではなかった。

Billing Library の更新も、作業自体は RevenueCat のパッケージのメジャーバージョンアップで済んだが、大変だったのは動作確認のほう。
Billing 8 では消費済みの買い切り購入を照会する API が削除されるなど、買い切り周りの挙動が変わっている。
影響がないことを確かめるために、サブスクと買い切りの全パターンを一通り試した。
この辺りはまだ自動化が難しそうなので、手動でテスト項目を作って地道にやった。

せっかくリリースするので、細かい改善と不具合修正もまとめて組み込んだ。
改善は、通知を受け取る時刻をこれまでより細かく設定できるようにしたり、ホームでのスワイプによる完了・スキップ操作をオフにできるようにしたり。
修正は、ホームの日付やあいさつが実際の時刻とずれる不具合、休暇モードの設定が保存できない不具合、中国語(繁体字)の一部表示が正しくない不具合などを直した。

締切があるとアプデするモチベができる。締切駆動開発。
この毎月の目標も同じ感じで、目標があるから「ここまでにやるぞ」という気持ちになると思う。

本を2冊読む

未達。1.5冊読んだ。

4月から読んでいる4冊セットの小説、その残り2冊を読んだ。

間に合わなかったけど、8月中に読めなかった残りの0.5冊は振り返り記事を書いている最中に読み終えることができた。

なかなか生活に読書の時間が染み付いておらず読むスピードが速くならない。
技術書も興味ある本もたくさん読みたい。読みたいという気持ちだけは本当にある。
気持ちだけは。。。

さらに問題なのが、読んでいる間にも積読が増え続けていること。
いろんな本に出会おうという目的で本屋を見かけたら気になったものや目に留まったものを1冊買う、というルールを作ってみたのだが、買う一方で全然読めてない。
小説、育児書、哲学書、生き物系と、ジャンルもバラバラな本たちが溜まってきた。

現実的なのは、スマホを触っている時間を読書に置き換えることかなと考える。
トイレにスマホを持っていってしまうけれど、そこに Kindle や本を持っていく。
9月はそういう細かい時間から意識してやってみている。

8月を振り返って

今月達成できたのは Google Play の対応だけだった。
こちらは、対応しないと今後のアプリの存続に関わるものなので最優先で対応できた。
目標にしてなくてもやってたかも。いや、忘れてやってなかった可能性もあるか。

それに対して、結局やらなくても最悪痛くない freee 自動化と読書は、両方とも未達だった。
よくない〜〜。

未達の要因としては、まず着手が遅かったこと。
動き出しが遅れた分、freee の動作確認に入るあたりで月末の長期帰省と重なってしまい、帰省中も少しは進められるだろうと思っていたが全然そんな時間はなかった。

もう1つは規模の見積もりで、freee は宣言の時点では「全工程の自動化が理想だが、一部でもやりたい」と書いていたのに、仕様を考える段階に入ったら最初から全工程を一貫して実装するところまで膨らんでしまっていた。
読書は、7月にほとんど読めていなかったところに「2冊」と目標を置いていて、止まっている習慣に対して目標が大きすぎた。

とはいえ、着手しやすくて完遂しやすい目標だけ立てても挑戦になってないというのもあって塩梅が悩ましいところではある。

目標の立て方は今のままでよしとして、どう達成するかの計画を立てることも必要だ。

育休日記は毎日続いていて、8月31日で232日目になった。
GitHub の草は2日だけ途切れてしまった。2日間とも完全に忘れていた。
プライベートが充実していたと考えておこう。

2026年9月の目標

  • 職務経歴書を更新する: 将来のことを考えつつ整理する
  • 減量をやり切る: 9月末まで食事制限と運動を継続する
  • 保険を検討する: ちゃんと計算して必要有無を検討する

職務経歴書は、復帰後のことを考えつつ整理したい。
最新の情報に更新できたら達成。

減量は体脂肪が増えてきたのと運動不足のため。夫婦で減量を始めた。
カロリーと PFC の制限を毎日、運動は軽めのルールで毎日続けている。
これを9月末まで続ける。

保険は、これまで基本的に不要だと思っていたのだが、毎年更新している資産のシミュレーションを見て考えていたら、そうでもないかもしれない気がしてきた。
保険の要否を判断するためのブラウザツールを作ったので、9月はその細部を整えて、加入するかどうかまで決める。

9月もやっていき!

2026年7月の目標振り返り

はじめに

こんにちは。ないぱかです。

challenge-every-month で立てている毎月の目標と振り返り。

7月分も遅くなってしまった。。。

先月の分:

naipaka.hatenablog.com

2026年7月の目標と結果

7月最初に立てた目標と達成状況は下記の通りで、全て達成!

  • [x] 1日の時間の使い方を見直す
  • [x] 3回以上5時起きする
  • [x] 6月に読んだ本の読書記事を書く

1日の時間の使い方を見直す

達成!⏰

息子の起きている時間が長くなってきて、やりたいことに対して時間の総量が足りなくなっていた。
そこで、息子に対応する時間を先にないものとして差し引いた上で、1日のスケジュールを組み直した。

リマインダーに登録していたルーティンタスクも整理して、各タスクのやる時間を変更した。
今回は来年以降の保育園に入れてからの生活を意識したルーティンに変更している。

やってみた結果、日中はゆったりできるが、登園前と退園後のスケジュールがぱつぱつ。
まだ育休中なのにこれかー、という感じだが、先にリアルな感触を掴めたのは良かった。

3回以上5時起きする

達成!🌅

7月末に追い込みで3回起きた。
ただ、当初の狙いだった朝の家トレは一度もできなかった。

5時に起きても息子の相手をしていたら終わってしまう。。。
早起きしても自分の時間にはならず。
もっと早く起きる必要があるのかもなぁ。

とはいえ、5時起き自体はできたので目標としては達成。
朝に自分の時間を作るにはもう一工夫要りそう。

6月に読んだ本の読書記事を書く

達成!📝

6月に読んだ『オブジェクト指向UIデザイン』の読書感想記事を書いた。

naipaka.hatenablog.com

読んだら書く、の習慣化に向けた第一歩。
メモを取りながら読んでいたので、記事にまとめることで内容の定着にもなった。

宣言外目標

毎日育休日記を書く

継続中。7月31日で201日目になった。
200日突破。

1日1コミットして草継続

7月も途切れず継続。🌱

アプリを1回以上アプデ

アプデできず。8月はやる。

小説を1日3ページ以上読む

7月はほとんど読めなかった。
読めたのは月末の数日くらい。

7月を振り返って

4月から5ヶ月連続で全目標達成。

正直7月はモチベが上がらない、というか時間がうまく作れなくてなかなかやりたいようにできていない。(言い訳なのはわかっているが。。)
宣言外のアプリアプデと読書が止まっていたのがそれを物語っている。

子どもが寝た隙間時間にダラダラしちゃうところから改善していかなければ。

2026年8月の目標

  • freeeの会計作業自動化: 毎月の会計作業の棚卸しと自動化をする
  • Google Play周りの更新対応: 対象APIレベルとBilling Libraryの2件
  • 本を2冊読む: 積読消化する

日記と草は引き続き。

freeeの会計作業は、個人事業主の毎月の会計作業を freee の MCP や API を使って自動化したい。
まずは作業の棚卸しから。全工程の自動化が理想だが、一部でもやりたい。

Google Play 周りは、対象 API レベルと Billing Library の更新対応が2件ある。
どちらも8月31日が期限なので、それまでに終わらせる。

本は積読が溜まってきたので崩したい。
2冊読む。

8月もやっていき!

使いやすいUIデザインとは【オブジェクト指向UIデザイン】

使いやすいデザインとは【オブジェクト指向UIデザイン】

はじめに

先日、本棚に埋まっていた『オブジェクト指向UIデザイン』を読了。
いつどんなきっかけで本書を買ったのかは忘れてしまった…。

gihyo.jp

最近は Claude Design のように、UI デザインも AI が一瞬で作ってくれるようになった。
デザイン素人の僕は、個人開発アプリを作るうえでかなりありがたく使わせてもらっている。

AI 臭さは出てしまうけど、自分がゼロから作る UI よりはマシに見える。

ただ1つ困っているのが、AI が出してきた画面を見て「良い」とか「悪い」を根拠をもって判断できないこと。

ずっと触ってきている Flutter の実装に関しては、AI が書いたコードに対してあーだこーだレビューできるが、デザインについてはどんな基準でOKと判断すればいいのかがわからない。

この本で UI デザインの良し悪しを判断する基準が得られれば良いと思い読み始めた。

本について

オブジェクト指向UIデザイン――使いやすいソフトウェアの原理

  • 著者:ソシオメディア株式会社、上野学、藤井幸多(監修:上野学)
  • 出版:技術評論社(WEB+DB PRESS plus シリーズ)
  • 発売:2020年6月5日
  • 頁数:360ページ

前半が OOUI の理論とプロセスの解説で、後半は「ワークアウト」と呼ばれる実践演習が18本。
読んで終わりではなく、手を動かして設計手法を身につける構成になっていて、演習の分量がかなりある。
演習は読むだけでも勉強になった。

ちょうど2026年7月23日に改訂新版が出た。
前半の図版がカラーになって、巻末に「改訂新版あとがき」が追加されているとのこと。
これから買うならこちらが良い。

ユーザーの目当て

本書が繰り返し強調しているのは、ユーザーの関心・目当てを中心に置くこと。

例として最初に出てくるのが蔵書アプリ。

タスク指向 UI

まず、目当てを考えずに作るとどうなるかというと、
このアプリでユーザーがやることを並べるところから始めることになる。

  • 本を登録する
  • 本を確認する

そして、それぞれに対応したボタンを配置し、タップすると、それ専用の画面に飛ぶ。

これがタスク指向UI。やることが設計の中心にある。

この作りで困るのは、あとから編集したい、削除したい、感想文を追加したいとなったときに、やることが増えるたびに入口のボタンとそれ専用の画面がセットで増えていってしまうところ。
機能の数だけ画面が積み上がっていくので、使う側にとっても作る側にとってもつらい。

オブジェクト指向 UI

これを解決するには、まずユーザーの目当ては何かを考える。
蔵書アプリであれば「本」。本をユーザーに見せることが表現の中心になる。

具体的には、
本を一覧で見せる。
本を選んで内容を確認する。
選んで編集する。
追加ボタンから新しい本を足す。

こうすると、機能が増えても増えるのは「本を選んだあとにできること」だけで、入口の数は変わらない。
目当てが中心にあると、やることはその周りに集まってくれる。

蔵書アプリくらいの規模だと画面が数枚減るだけの話に見えるが、扱うオブジェクトも機能も多い業務アプリになると話が変わってきて、タスク指向からオブジェクト指向に組み替えるだけで画面数が5〜20分の1になると書かれている。
本書はこの転回を「銀の弾丸」と呼べるほど強力な改善方法と記載されている。

使いやすいデザインとは何か

本書には使いやすさそのものについての記述もあった。

前提として、ユーザーの要求を全部満たすのは無理だという話が出てくる。
あるユーザーに最適化された手順は他のユーザーにとって不便になることがある。

本書では、ユーザー中心のデザインというのは、

「ユーザーに合わせたデザイン」ではなく、「ユーザーが自らを合わせることができるデザイン」

であると記載されている。

これはとても耳が痛い話だった。

というのも、個人開発しているアプリで、ユーザーからの要望をどんどん取り入れてしまっていった結果、ホーム画面のUIが複数パターン存在し、設定項目が増えすぎるという複雑な状態になってしまったためだ。
ユーザーがアプリに合わせるのではなく、アプリがユーザーに合わせてしまった。
保守性も悪くなってしまい悪手だったなと反省した。

アイディアからデザインへ

アプリのアイデアを考えるときは「〜するためのアプリ」とか「〜が記録できるアプリ」という形から始まることが多い。

アイデアを思いついた時点では、やることが中心になっている。
その言葉のまま画面を考えてしまうと、「記録するためのアプリ」なんだから記録ボタンを置こう、という発想になってしまう。

そのため、アイデアからデザインに落とし込むときは、まず「〜するためのアプリ」から、そのアプリが扱うモノは何なのかを取り出す必要がある。

「記録するためのアプリ」の場合、扱っているモノは記録そのものになる。
そこまで言い換えられれば、あとは蔵書アプリと同じで、最初に記録の一覧を見せて、そこから1つ選んで中身を見たり編集したりする形が自然に出てくる。記録ボタンは一覧の脇に置くなどすれば良い。

アイデアの段階の言葉は動詞のままなので、名詞に置き換える工程を挟まないと、その動詞がそのまま画面の作りに引き継がれてしまう。

おわりに

昨今では、iOS の標準アプリや Google のサービスなど、目当てが中心にある UI が主流なので、オブジェクト指向UIは見慣れたUIだった。

しかし、それがなぜ使いやすいのかまでは説明できなかったので、本書を読むことでその理由の1つを言葉にできるようになった。

今後良し悪しを判断する際は、ユーザーの目当てが中心にあるデザインになっているか、ユーザーが自らを合わせることができるデザインになっているか、という2点を意識していきたい。

2026年6月の目標振り返り

はじめに

こんにちは。ないぱかです。

遅くなったけど、いつも通り challenge-every-month で立てている毎月の目標と振り返り。

もう7月も残り少し。。。

先月の分:

naipaka.hatenablog.com

2026年6月の目標と結果

6月最初に立てた目標と達成状況は下記の通り。全て達成!

  • [x] 技術書を1冊以上読む
  • [x] 車運転のトラウマを克服する
  • [x] アプリに E2E テストを導入する

技術書を1冊以上読む

達成!📚

読んだのは『オブジェクト指向UIデザイン』。

gihyo.jp

3月にメモを取りながら読み始めて、時間がかかりすぎて止まっていた本をようやく読み切れた。

小説で毎日ページを開く習慣がついていたおかげで、技術書でも読み進めること自体は苦じゃなくなっていた。
3月時点では「メモしながらだと想像以上に時間がかかる」と嘆いていたが、身についてきた読書習慣でなんとかなった。

読書感想記事はまだ書けていないので、こちらは7月に持ち越し。

車運転のトラウマを克服する

達成!🚗

ペーパードライバー講習を受けて、日常的に運転できるようになった。

講習は1回3時間で、最寄駅付近からいろんなところへ実際に路上を走った。
ショッピングモールに行って駐車の練習も。

講習で感覚を取り戻してからは、近所のスーパーやショッピングモールへの買い物、公園へのお出かけなど、週2〜3回のペースで運転している。
4月に車を買ってから運転は妻に任せきりだったのが、生活の足として普通に運転できるようになった。

そして7月には車で旅行にも行けた。
高速怖かったー。

アプリに E2E テストを導入する

一部だけど達成!🧪

日記アプリの One Page に、Flutter の E2E テストフレームワーク Patrol を導入した。

実は5〜6年前に公式の integration_test で E2E テストを導入しようとして、ネイティブの権限ダイアログや WebView が操作できず挫折した経験がある。
Patrol はそのあたりをサポートしていて、Dart でテストコードを書けるのも嬉しいポイント。

導入手順やハマったところは記事にまとめたので、そちらをどうぞ。

naipaka.hatenablog.com

maestro も触りたいと言っていたが、そちらまでは手が回らなかったので、まずは Patrol の導入まで。
テストコードの追加と CI でのテスト実行はこれからやっていく。
育休から復帰したときに実務でそのまま活かせるといいなー。

宣言外目標

小説を1日3ページ以上読む

6月も毎日読めた。
4冊セットの小説はいま2冊目を読み進めているところ。

技術書と並行しながらでも毎日続けられた。

アプリを1回以上アプデ

6月頭に1回リリースできた。
5月の3回からペースは落ちたが、目標の1回はクリア。

1日1コミットして草継続

6月も途切れず継続中。🌱

毎日育休日記を書く

こちらも継続中で、6月30日で170日目になった。
半年近く毎日書き続けていることになる。我ながらすごい。

6月を振り返って

4月、5月に続いて、6月も3ヶ月連続で全目標達成できた。

今月の3つは、どれも前からの積み残しや課題に関する目標で、これらをまとめて片付けられた。

特に運転は、目標として宣言したからこそ講習を申し込めた気がする。
放っておいたらずっと妻に任せきりだったはず。宣言駆動。

2026年7月の目標

7月は残り少ないので軽めに。

  • 1日の時間の使い方を見直す: 1日のスケジュールを組み直す
  • 3回5時起きする: 朝の時間を作るチャレンジ
  • 6月に読んだ本の読書記事を書く: 読んだら書くを習慣にしたい

1日の時間の使い方を見直す

時間の使い方の見直しは、息子の起きている時間が長くなってきたのがきっかけ。
やりたいことに対して時間の総量が足りなくなってきたので、息子に対応する時間を最初から「ないもの」として差し引いた上で、1日のスケジュールを組み直したほうがいいなと。
リマインダーに登録している毎日のルーティンタスクも合わせて整理する。
もう習慣化したものや、やらなくてもいいものを外して、必要なものを追加したり時間を変更したり。

3回5時起きする

5時起きは、習慣にするというよりひとまずのチャレンジ。
今の起床時間は7時〜10時とまばらなので、まずは月3回からやってみる。
日中はなかなか時間がないので、朝に家トレの時間を作れたら嬉しい。
早起きに慣れたら、もう少し早く起きてジムにも行きたい。

6月に読んだ本の読書記事を書く

読書記事は、6月に読んだ『オブジェクト指向UIデザイン』の感想から。
せっかくメモを取りながら読んだので、記事にアウトプットして内容を定着させたい。
今後も読んだら記事を書く、をセットで習慣にしていく。

7月もやっていき!

Flutter の E2E テストフレームワーク Patrol を導入した

[:content]

はじめに

こんにちは。ないぱかです。

弊 OSS Flutter アプリの One Page に E2E テストフレームワークの Patrol を導入しました。

5〜6年前に E2E テストを導入する際に Flutter の公式 E2E テストツールである integration_test を導入しようとしたことがあるのですが、
ネイティブの権限ダイアログ操作ができなかったり、WebView を操作できないのでログインができなかったりと制約が厳しく、E2E テスト導入に難航していたのを覚えています。。。

その点、Patrol はネイティブの権限ダイアログ操作や WebView 操作もサポートしているため、実用的な E2E テストを実装することができます。
しかも、Dart でテストコードを書くことができるのが嬉しいですね。

この記事では、Patrol を導入するまでの手順をまとめています。

対応 PR は以下です。

github.com

前提

One Pageは以下の構成になっています。

  • Flutter 3.44.2
  • Dart 3.12.2
  • FVM 使用
  • Dart Define で環境分け
  • マルチパッケージ構成(Pub Workspace と melos を使用)

Patrol 関連のバージョンは以下の通りです。

  • patrol_cli: 4.4.0
  • patrol: 4.6.1

2026年6月19日に SPM 対応された patrol 4.7.0-dev.1 がリリースされているので、正式リリースされたらアップデートしたいですね。

pub.dev

導入手順

基本的には以下の公式ドキュメント通りに進めれば問題ないですが、いくつかハマった点があったので、そこを中心に記載します。

patrol.leancode.co

1. patrol_cli のインストール

まずは Patrol UI テストを実行するための CLI ツールである patrol_cli をインストールします。

公式ドキュメントでは、flutter pub global activate patrol_cli と記載されていますが、ローカルのグローバルとプロジェクトの Flutter バージョンが異なっていたので、念の為 FVM が指す SDK に対してインストールすることにしました。

> fvm flutter pub global activate patrol_cli
Package patrol_cli is currently active at version 4.4.0.
Downloading packages... .
The package patrol_cli is already activated at newest available version.
To recompile executables, first run `flutter pub global deactivate patrol_cli`.
Installed executable patrol.
Activated patrol_cli 4.4.0.

インストールが完了したら、patrol doctor コマンドを実行して、環境が正しくセットアップされているか確認します。

公式ドキュメントの注意書きにもありますが、patrol 内部では Flutter CLI を呼び出していますが、何も指定しないとグローバルの Flutter SDK を参照してしてしまいます。
そのため、今回は .zshrc に PATROL_FLUTTER_COMMAND を設定して、FVM が指す Flutter SDK を参照するようにしました。

export PATROL_FLUTTER_COMMAND="fvm flutter"
> patrol doctor
Patrol doctor:
Patrol CLI version: 4.4.0
Flutter command: fvm flutter
  Flutter 3.44.2 • channel stable
Android:
• Program adb found in /Users/xxx/Library/Android/sdk/platform-tools/adb
• Env var $ANDROID_HOME set to /Users/xxx/Library/Android/sdk
iOS / macOS:
• Program xcodebuild found in /usr/bin/xcodebuild
• Program ideviceinstaller found in /opt/homebrew/bin/ideviceinstaller
Web:
• Program node found in /opt/homebrew/opt/node@20/bin/node
• Program npm found in /opt/homebrew/opt/node@20/bin/npm

2. pubspec.yaml の設定

patrol パッケージをプロジェクトの dev_dependencies に追加します。

dev_dependencies:
  patrol: ^4.6.1

続いて、patrol セクションも追加します。
今回は開発版アプリでテストを実行するため、開発版のアプリのパッケージ名とバンドル ID を指定しました。

Flavor 対応している場合は flavor パラメータで指定することもできますが、本アプリでは Dart Define で環境を分けているため、直接開発環境でのパッケージ名とバンドル ID を指定する必要があります。

patrol:
  app_name: One Page Dev
  android:
    package_name: com.naipaka.onepage.dev
  ios:
    bundle_id: com.naipaka.onepage.dev

3. Android セットアップ

Android のセットアップは公式ドキュメント通りです。

3-1.build.gradle.kts への追加内容

defaultConfig 内に以下を追加します。

testInstrumentationRunner = "pl.leancode.patrol.PatrolJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"

android ブロック内に testOptions を追加します。

testOptions {
    execution = "ANDROIDX_TEST_ORCHESTRATOR"
}

dependencies にも以下を追加します。

androidTestUtil("androidx.test:orchestrator:1.5.1")

3-2. MainActivityTest.java の作成

android/app/src/androidTest/java/com/naipaka/onepage/MainActivityTest.java を新規作成します。
公式 example app のものをベースに、パッケージ名を com.naipaka.onepage に変更しました。

各設定の役割

  • PatrolJUnitRunner: Patrol が Dart テストを Android の JUnit テストとして実行するためのカスタムランナー
  • clearPackageData = "true": テスト間でアプリデータを完全クリアし、テストの独立性を確保
  • ANDROIDX_TEST_ORCHESTRATOR: 各テストを個別プロセスで実行し、テスト間の分離を強化
  • MainActivityTest.java: Patrol が Dart 側のテストを列挙・実行するためのエントリーポイント

4. iOS セットアップ

iOS のセットアップもほぼ公式ドキュメント通りです。

4-1. Xcode で RunnerUITests ターゲットを作成

  • File > New > Target... → UI Testing Bundle
  • Product Name: RunnerUITests に変更
  • Organization Identifier: com.naipaka.onepage.dev (開発版のバンドル ID に合わせる)
  • Language: Objective-C(公式ドキュメントで指定。RunnerUITests.m が Objective-C のマクロを使うため)
  • Target to be Tested: Runner

4-2. RunnerUITestsLaunchTests.m の削除

Xcode が自動生成する RunnerUITestsLaunchTests.m を Xcode 上で Move to Trash で削除。

4-3. iOS Deployment Target の確認

RunnerUITests の Build Settings → iOS Deployment Target を Runner と同じに設定。

今回は 15.0 に設定しました。

4-4. RunnerUITests.m の置き換え

Xcode が自動生成した内容を公式ドキュメントの内容に置き換え:

@import XCTest;
@import patrol;
@import ObjectiveC.runtime;

PATROL_INTEGRATION_TEST_IOS_RUNNER(RunnerUITests)

4-5. Podfile に RunnerUITests ターゲットを追加

Runner ターゲット内、RunnerTests の下に追加。

target 'RunnerUITests' do
  inherit! :complete
end

SPM 対応されたらこの対応は不要になりそうですね。

4-6. flutter build ios --config-only の実行

mkdir -p patrol_test && touch patrol_test/example_test.dart
fvm flutter build ios --config-only patrol_test/example_test.dart

4-7. pod install

pod install --repo-update

4-8. Configuration Set を Flutter の xcconfig に変更

RunnerUITests の Base Configuration を Runner と同じ Flutter xcconfig に変更します。

Configuration Base Configuration
Debug Flutter/Debug.xcconfig
Release Flutter/Release.xcconfig
Profile Flutter/Release.xcconfig

pod install 後、RunnerUITests の Base Configuration は Pods-Runner-RunnerUITests.*.xcconfig になっています。
しかしこの xcconfig には FLUTTER_ROOT が含まれず、Build Phases の xcode_backend build / xcode_backend embed_and_thin が Generated.xcconfig を読めなくなってしまうため、Runner と同じ Flutter の xcconfig を指定する必要があるようです。

Flutter xcconfig に変更すると pod install で「CocoaPods が base configuration を設定できない」警告が出ますが、動作に支障はありませんでした。

ここも SPM 対応されたら、CocoaPods の設定が不要になるので、解消されそうですね。

4-9. Build Phases の追加

RunnerUITests ターゲットに2つの Run Script Phase を Xcode で追加します。

  1. xcode_backend build
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
  1. xcode_backend embed_and_thin
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed_and_thin

Build Phases の最終的な順序は以下のとおりです。

  1. Target Dependencies
  2. Run Build Tool Plug-ins
  3. [CP] Check Pods Manifest.lock
  4. xcode_backend build
  5. Compile Sources
  6. Link Binary With Libraries
  7. Copy Bundle Resources
  8. [CP] Embed Pods Frameworks
  9. xcode_backend embed_and_thin

4-10. Parallel Execution の無効化

Product → Scheme → Edit Scheme → Test で、RunnerUITests を追加し、Options を開き Parallelization を無効にします。

動画ではチェックボックスでの設定になっていますが、Xcode 26 ではチェックボックスではなくドロップダウン(「Parallelization: Enabled (If Possible)」)に変更されています。

5. テスト実行

テストコードは patrol_test ディレクトリに配置します。
まずは、公式ドキュメントのスモークテストを patrol_test/example_test.dart に配置しました。

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:patrol/patrol.dart';

void main() {
  patrolTest(
    'counter state is the same after going to home and switching apps',
    ($) async {
      await $.pumpWidgetAndSettle(
        MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text('app')),
            backgroundColor: Colors.blue,
          ),
        ),
      );

      expect($('app'), findsOneWidget);
      if (!Platform.isMacOS) {
        await $.platform.mobile.pressHome();
      }
    },
  );
}

このプロジェクトでは Dart-Define を使用しているため、--dart-define-from-file で dev 環境の値を渡す必要があります。

cd packages/app

patrol test \
  -t patrol_test/example_test.dart \
  --dart-define-from-file=dart_defines/dev.env
Selected device: iPhone 17 Pro (E0A9C9EF-8E0B-4FD2-B81E-6980E178854A)
• Building app with entrypoint test_bundle.dart for iOS simulator (debug)...
    Building com.naipaka.onepage.dev for simulator (ios)...
✓ Completed building app with entrypoint test_bundle.dart for iOS simulator (78.7s)
• Running app with entrypoint test_bundle.dart for iOS simulator on simulator iPhone 17 Pro...
✅ counter state is the same after going to home and switching apps (/example_test.dart) (0s)

Test summary:
📝 Total: 1
✅ Successful: 1
❌ Failed: 0
⏩ Skipped: 0
⏱️  Duration: 28s

✓ Completed executing app with entrypoint test_bundle.dart for iOS simulator on simulator iPhone 17 Pro (28.6s)

6. 初期化設定

公式ドキュメント「Initializing app inside a test」セクションに、テスト内でアプリを起動する際の制約が記載されています。

  1. WidgetsFlutterBinding.ensureInitialized() を呼んではいけない
  2. runApp() を使ってはいけない — 代わりに $.pumpWidget() を使う
  3. FlutterError.onError を変更してはいけない — Crashlytics 等の監視ツールがエラーを横取りすると、テストエンジンがエラーを検知できず、テストが失敗しても終了しなくなる

公式 example の patrol_test/common.dart では createApp(PatrolIntegrationTester $) 関数を定義し、テストから呼ぶパターンを採用していました。

上記の制約に従い、main.dart から createApp() を呼び出すように変更しました。

lib/app_initializer.dart(新規)

class AppInitializer {
  const AppInitializer._();

  static Future<Widget> createApp({Tracker? tracker}) async {
    final flavor = Flavor.values.byName(const String.fromEnvironment('flavor'));
    await Firebase.initializeApp();
    final effectiveTracker = tracker ?? Tracker();

    final (_, prefsClient) = await (
      LocaleSettings.useDeviceLocale(),
      PrefsClient.initialize(),
    ).wait;

    // ... ProviderLogger setup ...

    return ProviderScope(
      overrides: [
        flavorProvider.overrideWithValue(flavor),
        trackerProvider.overrideWithValue(effectiveTracker),
        prefsClientProvider.overrideWithValue(prefsClient),
      ],
      observers: [providerLogger],
      child: TranslationProvider(child: const App()),
    );
  }
}

lib/main.dart(変更)

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final tracker = Tracker();
  FlutterError.onError = tracker.onFlutterError;
  PlatformDispatcher.instance.onError = tracker.onPlatformError;
  Isolate.current.addErrorListener(tracker.isolateErrorListener());

  final app = await AppInitializer.createApp(tracker: tracker);
  runApp(app);
}

テストコード

初期化処理を変更したので、実際にアプリを起動してテストするテストコードを作成しました。
データ読み込みが 10 秒(デフォルト)を超えることがあったため、timeout を 30 秒に設定しています。

patrol_test/common.dart

Future<void> createApp(PatrolIntegrationTester $) async {
  final app = await AppInitializer.createApp();
  await $.pumpWidget(app);
  await $.waitUntilVisible($(K.homePage), timeout: const Duration(seconds: 30));
  await $.waitUntilVisible(
    $(K.diaryCalendar),
    timeout: const Duration(seconds: 30),
  );
}

@isTest
void patrol(
  String description,
  Future<void> Function(PatrolIntegrationTester) callback, {
  bool? skip,
  List<String> tags = const [],
}) {
  patrolTest(description, skip: skip, callback, tags: tags);
}

patrol_test/example_test.dart

import 'common.dart';

void main() {
  patrol('shows the diary calendar after launch', ($) async {
    await createApp($);

    expect($(K.diaryCalendar), findsOneWidget);
  });
}

テスト実行ログは以下のようになりました。

✓ Completed building app with entrypoint test_bundle.dart for iOS simulator (77.9s)
• Running app with entrypoint test_bundle.dart for iOS simulator on simulator iPhone 17 Pro...
✅ shows the diary calendar after launch (/example_test.dart) (27s)

Test summary:
📝 Total: 1
✅ Successful: 1
❌ Failed: 0
⏩ Skipped: 0
📊 Report: /Users/ryota/work/personal/apps/onepage/packages/app/build/ios_results_1781933158597.xcresult
⏱️  Duration: 45s

✓ Completed executing app with entrypoint test_bundle.dart for iOS simulator on simulator iPhone 17 Pro (45.7s)

まとめ

今回は、Flutter アプリの E2E テストフレームワークである Patrol を導入しました。

まだテストコードは書いてないものの、かなり快適に E2E テスト環境が構築できそうです。

近いうちに、実際にテストコードを追加するとともに、CI でのテスト実行も設定していきます。
OSS で公開しているリポジトリなので、無料で利用できる GitHub Actions で完結させたいですね〜。