私がデザインして作ったものの、結局リリースしなかったアプリ「Stocketa」
(paulstamatiou.com)- コロナ隔離初期の2020年3月、StocketaはSwiftとSwiftUIを学ぶための個人プロジェクトとして始まり、2年以上にわたって週10〜20時間開発されたが、App Storeにはリリースされなかった
- 目標は、複数の証券会社や金融サービスに散らばる資産を1か所で確認し、取引ごとの損益や実現・未実現損益まで把握できるカジュアル投資家向けポートフォリオトラッカーだった
- アプリは標準のiOSコンポーネントよりも独自に作ったインタラクションに注力し、pull-to-search、株式カード、カスタムシート・メニュー、スワイプアクション、ウィジェット、TSVインポートなどを実装した
- リリースを阻んだ最大の現実的制約は金融データAPIであり、IEXの品質・カバレッジの問題、月額100ドルのプロバイダーの信頼性、月額2,000ドル以上の商用データ価格は、インディーアプリには合わなかった
- LLC、Node/Expressバックエンド、約1,000人規模のTestFlight、数千人のウェイトリストまで揃っていたが、データ費用・カスタマーサポート負担・時間不足のため畳むことになり、SwiftUIの学習経験はその後のRewind AIでの仕事に直接つながった
Stocketaが解こうとしていた問題
- Stocketaは、複数の場所に散らばった投資資産を1つの画面で確認できる、よくデザインされたポートフォリオ追跡アプリを目指していた
- 個人的に、複数の証券会社や金融サービスに散らばった資産を確認するのが面倒だった経験から出発した
- 単なる価格確認ではなく、自分の取得原価を反映した実際の取引ごとの損益を見たいと考えていた
- 当時の選択肢は要件に合わなかった
- Apple Stocksのような単純な株価アプリはチャートやニュースを提供するが、保有数量や損益追跡機能はない
- Robinhoodのような証券会社アプリは、その証券会社内の保有分しか表示しない
- TradingViewのような高度なサービスはモバイル中心ではなかったり、求めるほど洗練された体験を提供できなかったりした
- Personal Capitalのような投資アドバイザリーサービスには口座連携機能があるが、個別資産を深く見るのは難しく、自社のアドバイザリーサービス販売に焦点が当たっている
- App Storeにある小規模な保有銘柄追跡アプリは、デザインとUXに満足できなかった
- 既存の金融アプリは概して情報密度を優先し、一覧から詳細画面へ何度も入っていく必要があったため、Stocketaは最初の画面のカード内でより多くの情報を見せようとした
カスタムUIで作ったアプリ構造
- Stocketaは単一フィードと株式カードを中心に据え、周辺のUIクロームを最小限にする方向で設計された
- 標準のタブバーやナビゲーションヘッダーは使わないようにした
- ほとんどの機能をホームタイムラインの株式カード内に収める構造だった
- ほぼすべてのUIはSwiftUIベースのカスタムコンポーネントで作られた
- pull-to-refreshの代わりに、新しい銘柄を探して追加するpull-to-searchを使った
- 株式カードは、チャートのスクラビング、スワイプアクション、主要統計など、ほとんどの情報をインラインで提供した
- カードをタップすると既存のカードはその場に残り、ニュースや保有詳細カードがアニメーションで現れ、主要な実装には
overlayPreferenceValueとanchorPreferenceが使われた - カスタムシートには日付ピッカー、案内画面、星がきらめく背景、ドラッグに応じた背景透明度の補間が含まれた
- カスタムアプリメニューは有料プランやメンバーシップ状態を表示するスペースを提供し、タップやスクロールで閉じられるように設計された
- スワイプアクションとヘッダーも自前で実装した
- スワイプ距離に応じてアクションが移動し、一定のしきい値を超えると選択状態へアニメーションする
- ヘッダーボタンはページ上部では枠線なしで表示され、スクロール上では控えめなボタンへ切り替わる
- Stocketaのテキストはスクロールに応じて上へ消えるように補間された
取引追跡とアプリ機能
- 初期実装は、株式カードのスクロールビュー、チャート、データベース永続化から始まった
- 最初はCore Dataを使い、その後Firebaseへ移行した
- 金融データプロバイダーの呼び出しとポーリングを組み込み、最初のプロバイダーとしてIEXを使った
- Stocketaは銘柄ごとの保有株数だけを保存するのではなく、取引単位の追跡を実装した
- 売買ごとの総損益だけでなく、取引ごとの損益まで表示しようとした
- 売却時にはFIFO、LIFOなどの取得原価方式を知る必要があり、どの過去取引から株式を売却するのかを計算しなければならなかった
- 株式分割は過去取引を単純に変更するのではなく、新規分割の自動適用と手動適用の両方をサポートする必要があった
- 機能はアプリ全体へ広がっていった
- オンボーディング: 一般的なカルーセルではなく、z軸に沿ってページが押し込まれる流れを使い、株式カードのような主要コンポーネントを紹介した
- TSVインポート: スプレッドシートで作ったTSVファイルから取引を取り込み、インポート状態をTSVに書き戻すことで、iCloud同期後の再インポートをサポートした
- 購入入力: 数量、価格、日付を中心にした株式専用フォームを提供し、日付を変えるとその日の過去価格がplaceholderに変わる
- 売却入力: FIFO、LIFO、Average cost、Highest cost、Lowest cost、特定lotをサポートした
- サブスクリプション画面: パララックステキスト、スクロールに応じたぼかし解除、モーションベースの機能カード、最下部到達時の小さなfireworks効果を含めた
- メンバーシップカード: アクティブなサブスクリプションアカウントの状態をインタラクティブカードで表示し、背景のstarfield効果とshimmer効果を入れた
- 株式カード表示設定: 複数のカード形式と設定を提供し、トグル時にマイクロアニメーションを入れた
- 保有状況カード: メインスクロール上部で、設定に基づく集計情報を表示した
- ウィジェット: 個別株、ポートフォリオ、シンプルなポートフォリオの3種類を提供し、パフォーマンスに応じて大きなグラデーション色が変わる
- Face ID猶予時間: 金融資産を扱うアプリに必要だと考え、アプリ切り替え中に再びロックされるまでの猶予時間を調整できるようにした
- 市場休場日シート: 株式保有アカウントで休場日を表示し、ウィジェットにも同じ処理を行うためのサーバーコードが必要だった
- フィードバックシート: 既知の問題と予定機能を表示し、アカウント別の質問の場合はアカウント確認用トークンを含められるようにした
- アカウント削除: 2021年のApp Storeアップデート要件に従い、アプリ内でアカウントを完全に削除できるようにした
SwiftUIで学びながら作り直す
- StocketaはSwiftとSwiftUIを学ぶ過程と同時に進められ、多くの画面やアプリの一部が継続的に再設計・再実装された
- SwiftUIはiOS 13以降、パフォーマンスやコンポーネント・機能の面で進化し、iOS 17ではshaders、keyframes、scroll transitionsのような機能も提供する
- SwiftUIはコードツールであると同時に、デザインツールとしても有用だった
- デザイナーが素早くレイアウトを作れる
- インタラクションを付けやすい
- ネイティブツールで実際のデザイン感覚を確認できる
- UIKitが必要なケースは限られていた
- より細かな入力・スタイル・フォーマット制御のためのカスタムテキストフィールド
- 銘柄順序のドラッグ変更のためのCollectionView
- confettiや夜空の星のようなパーティクルエミッター
- 長押しが始まった位置を取得するための
UILongPressGestureRecognizer
バックエンドとTestFlight運用
- 最初はバックエンドやユーザーアカウントのない単純なアプリを考えていたが、すぐに独自バックエンドが必要になった
- 金融データAPIキーをアプリ内に入れないため、バックエンドプロキシを作った
- APIの乱用で費用が増えないように、rate-limitingとthrottlingを組み込んだ
- Sign In With Appleを追加した
- 銘柄別統計を一定時間キャッシュし、複数のアカウントが同じデータを要求しても一度だけ取得するようにした
- バックエンドはNode/Expressベースで約13,000 LOC規模まで大きくなった
- Cloud Runで動作する
- 認証、キャッシュ、ニュース、主要なビジネスロジック、データ整理、通知、ウィジェット、複数API連携、一部スクレイピングを担当する
- 作業管理はLinearで行った
- タスク、アイデア、機能、顧客フィードバック、マイルストーンを管理した
- TestFlightは親しい友人向けの小さなアルファから始まり、徐々に拡大した
- 一時は約1,000人まで招待した
- ウェイトリストは数千人いたが、API・ホスティング費用とサポート・フィードバック対応の負担のため、それ以上は増やさなかった
- フィードバックには良い第一印象、機能要望、バグ報告が混在していた
ウェブサイトのデザインと実装
- 初期のウェブサイトは関心を集め、ウェイトリスト用メールアドレスを収集するための簡単なランディングページだった
- 初期デザインには、株式市場チャートの上下をゆるく参照した波形要素と、浮遊するミニ株式カードが入っていた
- ミニカードにはSVG折れ線チャートがあり、ページ読み込み時にアニメーションした
- 背景でゆっくり動く効果は、
offset-pathCSSと2つのkeyframeアニメーションで実装された
- ホームページ
v1.0は2021年初めに完成した- アプリ機能を見せられるほど実装が積み上がったと判断し、より完成度の高いホームページを作った
- 一般的なアプリのホームページにある電話機フレーム+ヘッドライン構成ではなく、スクロールに合わせて3D電話機フレームが動き、アプリのティーザー動画が再生される構成を試した
- Lottieの代わりに、JavaScriptが
canvasとoffscreenCanvasへフレームシーケンスを描画する方式を選んだ - Rotatoで短いStocketaのスクリーンキャストを3D電話機動画とPNGフレームシーケンスとして作った
- その後、フレームシーケンスを生成・最適化・更新するプロセスが大きな手間になった
- ホームページ
v2.0は1年後に再設計された- より多くの機能と改善点を見せるため、長く反復するモジュールではなく、ざっと見やすい機能一覧を求めた
- 右側に固定されたデバイスフレーム、左側にスクロール可能な機能一覧を置く2パネル構造を採用した
- 機能項目にマウスを合わせると、デバイスフレームのスクリーンショットが変わる仕組みだった
- モバイルではヘッダーをスクロール可能なデバイスフレームのカルーセルに変えた
- スクロールに応じたヒーローテキストのグラデーション、アイコン色の変化、hover背景グラデーション、アイコンのパーティクルエミッターを追加した
金融データAPIが生んだ限界
- Stocketaが依存した金融データの品質は、リリース可能な水準ではなかった
- 最初のプロバイダーであるIEXには複数の問題があった
- チャートが古かったり、価格が不正確だったりする場合があった
- IEX取引所で取引の少ない資産は特に問題が大きかった
- OTC市場データがなく、OTCと直接高額なライセンス契約を結ぶ必要があった
- Nasdaq上場株式データも大きく制限され、プレマーケット・アフターアワーズのような基本データも不足していた
- 事前告知なしにミューチュアルファンドデータを完全に停止した
- その後使った別の商用利用可能なプロバイダーは月額100ドルだったが、データと信頼性の問題はさらに大きかった
- エンドポイントが古い、または誤ったデータを返した
- 問題を知らせても、バグを無視したり存在しないと回答したりした
- 一部のデータは有料APIでも入手が難しく、バックエンドにスクレイピングエンジンを作る必要があった
- dividend yield
- earnings dates
- OEFデータ
- 高品質な市場データは、インディーアプリ開発者には合わない価格体系だった
- いくつかの主要な米国市場データプロバイダーの商用データ価格は月額2,000ドルから始まる
- 指数データやオプションデータには追加費用が必要
- あるプロバイダーはスタートアップ向けプランとして月額499ドルとMAUごとの料金を提示したが、それでも必要性に対して非常に高いと判断した
- 金融データAPIに依存するプロジェクトは、外部企業のAPIの上にビジネスを載せるリスクを抱えており、TwitterやRedditのAPI変更とも似た問題だと見ている
リリースを止めた理由
- Stocketaは最初から実際の事業ではなくサイドプロジェクトだった
- 本物の事業にするには、より多くの機能と収益化方法が必要だった
- 金融アドバイザリーサービスや株式取引のような領域へ入る必要があったかもしれず、そこは大手企業が競争する混雑した領域である
- その方向性は、シンプルで洗練されたアプリを作ろうとした出発点とは合わなかった
- 中止理由は3つに整理できる
- データ: 妥当なサブスクリプション価格でリリースするには、信頼でき、安価で高品質な金融データが必要だったが、費用と確保の難易度が大きかった
- サポート: TestFlightでの利用状況を見ると、カスタマーサポート、メール対応、保守、継続的な機能開発にかなりの投資が必要だった
- 時間: より集中すべき対象としてRewind AIを選び、Stocketaは数年にわたり夜や週末、ブログ執筆の時間まで占めていた
- 当初の目標だったSwiftとSwiftUIの学習は達成された
- リリースはされなかったが、ネイティブiOS開発について多くを学んだ
- Stocketaで得たSwiftUIの知識は、Rewind AIでの仕事に大いに役立った
1件のコメント
Hacker Newsのコメント
何年もかけたプロジェクトが実際には使われないというのは、ほとんど名誉勲章のように感じられることもある。賢明な勲章ではないけれど、私のように、良い意味で過度にのめり込み、完全に吸い込まれてしまう開発者にとっては、ほとんど必要な授業でもある。
昔の経験やそこから来た苦痛について話すと、人々がぽかんとするように感じる。1年以上、1日10時間以上何かに取り組んだことがなければ、何千時間も注ぎ込んだのに何の結果もない感覚は理解しにくいと思う。
成功したプロジェクトも多いし、今もうまくいっているけれど、その何年がかりのプロジェクトは取り返せない。学びには必要だったのだろうが、思い出すと今でも悲しく、寂しい。
その代わり、ずっと優れたプログラマーになるための技術を得て、Swiftが好きになった。今なら、この7年間でAppleの開発ツールとAPIが進歩したおかげで、そのアプリ全体を週末にゼロから作り直せる気がする。
こういうプロジェクトは記念に値する。プログラミングは芸術になり得るし、芸術は創作のはけ口として、自分自身のためだけに作られることもある。
後から得た知識で過去のプロジェクトに苦しむ結果中心思考は、あまり役に立たない。ほとんどのプロジェクトや事業は失敗する。それが現実だ。
ある元金融関係者が「悪い取引でたまたま儲けたからといって解雇される人はいない」と書いていたのを覚えている。結果中心の文化では、私たちが実際にコントロールできるのはプロセスだけだという事実を忘れがちになる。
やめ時を知る方法のような貴重な教訓は得たが、あまりにも長すぎる授業だった。今でもまともに振り返ることができていない。
誰も使わなくても後悔しないという確信がないなら、1年を超えるプロジェクトは始めるな、と勧めたいのかどうか気になる。
素晴らしい文章だが、結論が悲しく感じられる。こういう「余計なものがないバージョン」があればいいのにと思うアプリは本当に多いし、ここに注がれた作業量は驚異的だ。
もっとシンプルなiOSアプリをリリースして、自然流入で1日のアクティブユーザーが約1,000人まで行ったが、結局閉じた。広告削除用のアプリ内課金を$1.99か$2.99で1つ付け、月に70ドル程度を稼ぎながらバグレポートに対応していた。
当時のSwiftUIはまだ成熟しておらず、UIKitを使う必要があり、修正しづらいエッジケースに何度もぶつかった。
決定打は、Googleが私が自分の広告をクリックしたと主張して、広告配信を停止したことだった。実質的にすべての収益が途絶え、これを一生懸命作っても、結局は巨大テック企業の気まぐれ次第なのだと悟った。
Appleはすべてのアプリ内課金から30%を持っていき、広告収益は小銭程度なのに、Googleは理由もなく止めることができ、異議申し立ての方法もなかった。大金を稼ごうとしていたわけではないが、時間とエネルギーを使う理由は必要で、もはや正当化できなかった。
なぜ開発者に収益除外デバイス、IP範囲、位置情報のリストを登録させてくれないのか理解できない。自分のアプリは結局使うことになるし、スクロールしていると広告を誤って押さないのがほとんど不可能な場合も多い。
実環境でのテストも重要だし、広告が本当に興味深く、潜在顧客として合法的にクリックする可能性もわずかながらある。なのに今の方式は、実質的に自分のアプリを使うなと言っているようなものだ。
こういう根本的に愚かなポリシーを固持するGoogleの開発者や管理者以外に、誰の利益になるのか分からない。少しでもまともに処理するのが、本当にそんなに難しいのかと思う。
20年間、公演もアルバムも出していない自室のミュージシャン、18歳以降ボールに再び触れていない子ども時代のアスリート、大会に出ないランナー、教えない知性も、同じように考えられる。
同じ「外部への成果物 = 価値」という前提なら、何十年も生きて子どもを産まなかった人間にまで拡張され得る。私の信念ではなく、単に論理の流れとしてそうだという意味だ。
しかしそうした判断は人間が作った構築物であり、長く抱いてきた「私たちの子」を世に送り出そうとする生物学的命令の鏡のようなものだ。なぜそれらが自分の人生を得てはいけないのか、と私たちは問い、それ自体が成果を出し続け、未来に糸を織り込んでいくことを望む。
その命令に従うことが道徳的・倫理的・楽しさの頂点なのかといえば、ある人にとってはそうで、判断したくはない。だがある人にとっては、不思議なことにそうではない。
このプロジェクトを売るつもりがあるのか気になる。コンセプトは気に入っているし、関心のある顧客もいるようだが、サポート負担や事業化は担いたくないように見える。
私は資産トラッカー(https://jch.app)を作ってから1年ほどになるが、機能やユーザー体験はまだはるかに及ばない。
途方もない作業で、旅路についての深い記録も良かった。rewind.aiから次に何が出てくるのかも楽しみだ。
多くの人が、他の誰にとっても大きな意味を持たなかった数多くのプロジェクトに、延々と時間を注ぎ込んできたことと大きくは変わらない。自転車をハックしたり、鉄道模型をいじったりするようなもの
ただ、アプリはこれほど簡単に公開し、文書化し、保存できる一方で、「変な自転車部品を切ったり溶接したりして過ごした時間」は、YouTubeに全部上げでもしない限り、同じようにはしにくいという違いがある
もちろん多くの人はそうしているし、ある程度の意味もあるが、趣味や個人的な追求を、それ自体ではなく別の目標を持つプロダクトに変えようとすると、核心を少し見失うこともある
こういう記事が間違っているという意味ではない。不満を言っているとか怒っているようには見えず、最初に言っていたように「どこかに何らかの形で記録しておきたい」という感じだ。自分のGitHubの大半も同じ
こうした評価基準の性急な調整は、実際の結果を曖昧にしてしまうため、あまり有用ではないかもしれない。この場合、著者は「無駄にした」努力を恥じやすいが、より適切な反応は学んだことへの感謝だろう
こういうプロジェクトから得られる学びはとてつもなく大きい
この記事が怖いのは、自分の姿があまりにも多く見えるからだ。優れたプロダクトを作ることに深く没頭し、職人というアイデンティティに入り込み、素早く雑なMVPと反復的な市場テストという「魂のない」アプローチに抵抗を感じる
r/SaaSサブレディットに行ったことがあるなら、あれは自分がソフトウェア開発者として楽しんでいることの正反対だ
しかし、市場テストや素早く雑な反復に対する考え方を変えられれば、むしろ安心して職人技に集中できる、より良い土台になることも分かっている
分かる。自分もまだ銀行バックエンドを作っている。コア、会計、顧客、口座、決済(SEPAとカード)、通知(ServiceNowのようなもの)、制裁スクリーニング、リスク・監視エンジン、イベントフロー、レポーティングまで含めて5年目だ
こんな巨大なことを引き受けた自分が狂っているのか愚かなのか、疑い始めている。まだランディングページも設計しておらず、コードはおよそ15万行ある
既存プラットフォームにはバグや制約、セキュリティ問題が多かったにもかかわらず、顧客に私たちのコアバンキングプラットフォームへ移行してもらうだけの信頼を得るには、5年よりはるかに長くかかった
ところがモバイルアプリが重要になり、ネイティブアプリも作り始めたところ、それはやり過ぎだった。結局バーンアウトし、破産までしたので、超大型プロジェクトは勧めたくない
多くを学んだが、その浪費に見合う価値はなかった。あれほど多くのものを作り直すには、時間はあまりにも重要だ。今は最大でも6〜12か月の「小さな」プロジェクトだけをやっている
オープンソースになる可能性もあるのか気になる
追加したいものをすべて外しても基本版をリリースできないなら、いったん脇に置くことを検討すべきだ
過去20年以上、多くのプロジェクトやプロダクトを作ってきて、2〜3個は大きく成長した。ほかのものには数千人のユーザーがいるが、継続的な収益源はなかった
収益性のある事業になったものの一つは「週末ビルド」版としてリリースし、その後7年かけて積み上げた
慣れた状態が、リリース前の作業を延々と見つけ続けさせる罠になり得る。だから頭の中でフレームを捉え直してみるといいかもしれない
最近、Leonardo DaVinciがものすごい先延ばし屋で、完成しなかったプロジェクトが非常に多かったという話を読んだ
複数の分野であれほど成功した人なのでずっと尊敬していたが、それを知ってから、先延ばしは創造的な人間である私たちの一部なのだという見方に変わった
失敗と見ることもできるが、プロセスの一部として再解釈することもできる。結局のところ、私たちは何かを作ることを楽しんでいる
彼は実質的に「MVPを作り、早くリリースし、反復せよ」という一般的な助言を受け入れたうえで、正確にその逆をやったことになる
見栄えのするサイドプロジェクトについての素晴らしい記事。興味深く読んでいる途中で rewind.ai を見つけたが、これも良さそうに見える
継続的な自動の画面・音声録画のような、潜在的に侵襲的なものではプライバシーが核心なので、「Privacy first」セクションを見ていて疑問が湧いた
「FileVault でデータを暗号化してください。Apple FileVault は Rewind と連携して動作します。これをオンにするとデータが暗号化されます」と書かれているが、Rewind が Apple FileVault が「Rewind と連携して動作」するように何かしているのだろうか?
それとも、あらゆるものに適用される一般的なディスク暗号化のことを言っているのだろうか? 真面目な質問です。Rewind は使ってみたいが、この表現は余計な飾り文句のようにも解釈できる
「関連するテキストベースのデータだけがクラウドに送信され、転送中に暗号化されます」という文言も似た印象がある。「転送中の暗号化」とは標準的な TLS 暗号化を意味しているのだろうか? Rewind は良さそうだが、信頼できなければならない
「クラウド連携は不要です。Gmail、Dropbox、Slack のような複数のサービスに接続する必要はなく、すべてが自動で動作します」
ところが https://www.rewind.ai/privacy の公式プライバシーポリシーの詳細を見ると、こうなっている
「[収集項目:] OpenAI が生成した情報。Rewind AI の OpenAI 連携の一部として、音声録音の文字起こし要約や、OpenAI 連携が生成したその他のデータなど、OpenAI が生成した出力も収集する場合があります」
だとすると、「クラウド連携は不要」だが、私の音声は OpenAI と共有されるのだろうか?
また https://www.rewind.ai/privacy-first には「Rewind で検索すると何が起きますか? すべての録画データはローカルに残ります」と書かれている
どちらなのか分からない。Rewind を信頼したいが、ユーザーデータがどのように扱われるのかを説明する内容の不一致のために躊躇している