inSmartBank

B/43を運営する株式会社スマートバンクのメンバーによるブログです

iOS / Androidアプリの複雑な画面遷移フローを実現するステート駆動な設計手法


この記事は SmartBank Advent Calendar 2024 の8日目の記事です。昨日はPMの jou さんの 「打ち手検討の勘所: プロの思考を観察し、素人の反応を尊重しよう」 という記事でした。

blog.smartbank.co.jp


こんにちは。スマートバンクで iOS / Android エンジニアをしている nakamuuu です。

iOS / Androidアプリの設計や実装を行う中で、筆者が繰り返し難しさを感じているポイントが “画面遷移フローの構築” です。ごく少数の画面を持つアプリでもない限り、以下に挙げたような要因で画面遷移には複雑性を伴うことと思います。

  • 画面数の増加 : 画面数とともに遷移パターンも増大することで、アプリや機能の全体像を掴むことが難しくなる。イレギュラーケースの考慮漏れや意図しない挙動も招きやすくなる。
  • 画面間の依存関係 : 遷移時のデータの受け渡しやViewModelの共用などによって、画面間の依存関係が生じる。画面を表示するための前提条件が増すことで、柔軟性や検証容易性の低下に繋がる。
  • データや状態の揮発 : Androidにおいては画面インスタンス(Activity etc)やオンメモリで保持する状態よりも、画面スタックが長期にわたって保持される。画面インスタンスが再生成された際にも操作の継続性を担保するには、データ表現も含めて適切な形を探る必要性も生じうる。

直近では NavigationStack(iOS)や Navigation Component(Android)を活用し、画面実装と遷移ロジックを分離することで実装における複雑性を抑えるアプローチも存在します。しかし、設計において要件や仕様の複雑性を紐解いていく必要があることには変わりません。

このエントリーではB/43の新機能である「クレジットカード・銀行口座連携機能」を題材に、複雑な画面遷移フローの設計における思考プロセスを記していきます。

「クレジットカード・銀行口座連携機能」

先月リリースした「クレジットカード・銀行口座連携機能」により、B/43カード以外の支出のアプリ上での管理ができるようになりました。家賃や光熱費など、B/43カードで支払うことが難しい支出もまとめて把握できるようになったので是非ご活用ください ✨

b43.jp

さて、この「クレジットカード・銀行口座連携機能」においては、カードや口座を紐づける連携先の追加フローの設計や実装に大きく頭を悩ませました。

連携先の追加における複雑な遷移フロー

画面遷移フローの構築においてよく生じる複雑性を冒頭で述べましたが、「クレジットカード・銀行口座連携機能」の連携先の追加フローでは金融機関ごとの様々な違いが複雑さをよりもたらしていました。

  • 連携箇所の違い : アプリ内でログイン情報を入力するケースとWebブラウザで認証を行うケースが存在する。
  • 連携手順の違い : 入力するログイン情報の項目や2段階認証(SMS認証 / 秘密の質問 / ワンタイムパスコード etc)の方法も金融機関ごとに異なる。
  • 連携範囲の違い : 金融機関のアカウントに複数のカードや口座が紐づいていた場合に一部または全て連携するかを選択できる。

これらの複雑性によって、連携先の追加フローには全容を把握しづらい、検証容易性が乏しいといった課題が生じました。

難解な画面遷移フローの設計では、システム上のデータ表現と乖離せずにいかに複雑性を抑えた仕様に落とし込めるかが重要です。そのような横断的な仕様の策定は、アプリの実装上の都合とデザインシステム、サーバーサイドのデータ表現のいずれにも一定の理解を持つ、モバイルアプリエンジニアが振る舞いやすい領域です。

金融機関との接続部分を担う外部ベンダーの仕様書も読み解きつつ、必要とされる画面をリストアップし Figma 上でデザインデータを作成しました。このデザインデータは関係者間で必要となる実装コンポーネントの認識を揃えつつ、最終的なデザイン案のベースにもなっています。

連携先の追加フローのFigmaのイメージ(実際より単純化したもの)。一見するとシンプルな遷移フローであるように見えるが、存在する遷移フローを網羅的には示せていない。

💭 Off Topic : 実現可能性のリスクを下げるモバイルアプリエンジニアの振る舞い

複雑な画面遷移フローに限らず、システム上のデータ表現とデザインが密接に関与するような場面では何らかの画面イメージを通した議論が有用です。スマートバンクでは施策の実現に向けて全体像や制約の認識を揃えるアプローチをモバイルアプリエンジニアが主導する場面も多くあります。

関連して、このブログでは @rockname が「プロダクト開発におけるリスクを排除するためにモバイルアプリエンジニアができること」のテーマでエントリーを記しています。このエントリーの中で触れられる “実現可能性のリスク” を下げる観点でも、モバイルアプリエンジニアが Figma をコミュニケーションツールとして活用しつつ認識のすり合わせを図っていくことは有用なアプローチの1つなのかもしれません。

blog.smartbank.co.jp

ステート駆動な遷移ロジックの構築

ここからは少し実装寄りの話として、このエントリーの表題となっている「“ステート駆動” な遷移ロジック」の話を進めていきます。

連携先の追加フローにおいて、起こりうる遷移フローを網羅的に把握することは困難を伴いました。そのため、「画面Aの遷移先は画面Bまたは画面Cである」のように存在しうる遷移フローをすべて細かく実装に落とし込むことをしていません。

完了画面へ至るまでの画面遷移は一連のフローではなく “状態の変化” として考えることとしました。前節で認識合わせのために用いた Figma のイメージを示しましたが、具体の実装では以下のようにフロー上の画面群をすべて並列な存在として捉えています。

連携先の追加フローの遷移ロジックの実装イメージ。金融機関の選択画面と完了画面の間に存在する画面群の中での遷移フローを細かく実装に落とし込むことは避けた。

連携先の追加フロー冒頭(金融機関の選択後)では追加フローを表すリソースを生成するようにしました。このリソースはAPIサーバーに保存され、仮にアプリで保持するデータが揮発したとしても再取得で復帰可能とします。

追加フローを表すリソースは状態を持ち、ログイン情報の入力などのユーザー操作に呼応する形で状態が変化していきます。例えば、ログイン情報(ID / パスワード)の入力のみを行うフローでは「初期状態 → 完了状態」と状態変化が1度だけ起こります。

そして、アプリで実装する画面群はリソースが持つ状態から遷移先が一意に定まるように構成しました。また、各画面での操作完了がリソースの単発の状態変化を引き起こすようにします。何の副作用も起こさない画面や複数回の状態変化を引き起こすような画面の実装は複雑性を増すので避けています。

連携先の追加フローではリソース表現や画面群にいくつかの性質をもたらしつつ「ステート駆動な遷移ロジック」を構築することで、その複雑性を乗り越えていくアプローチを取りました。

ステート駆動な遷移ロジックの旨み

画面遷移フローにおける複雑性や連携先の追加フロー固有の難点がさまざま存在する中で、ステート駆動な遷移ロジックは何を解決するでしょうか。いくつかの観点で簡単に整理していきます。

① 遷移ロジックの記述が一箇所に集約される

ステート駆動な遷移ロジックの場合、フロー上の各画面で用いる遷移ロジックはすべて共通のものを用いることができます。以下は Android の Navigation Component を用いたコード例ですが、連携先の追加フローのリソース表現である AggregationAuthentication を引数に取る遷移用の関数を用意することで各画面からの遷移ロジックを一箇所に集約しています。

/**
 * 連携先の追加フロー上の各画面(金融機関の選択 / ログイン情報の入力 etc)からの遷移を行う
 */
private fun NavController.navigateByAuthentication(
  // `AggregationAuthentication` は連携先の追加フローのリソース表現
  authentication: AggregationAuthentication
) {
  when (val status = authentication.status) {
    // 初期状態であれば金融機関の種類に応じてログイン情報の入力やWebブラウザへ遷移させる
    is AggregationAuthentication.Status.Initial -> {
      when (status.instituteType) {
        is InstituteType.CreditCard -> {
          navigate("credential_input") { popUpTo(...) }
        }
        is InstituteType.Bank -> {
          navigate("web") { popUpTo(...) }
        }
      }
    }
    // 追加の認証が必要な状態であれば認証方法ごとの認証画面へ遷移させる
    is AggregationAuthentication.Status.AdditionalAuthenticationRequested -> {
      when (status.authentication) {
        is AggregationAuthentication.AdditionalAuthentication.SecurityQuestion -> {
          navigate("security_question") { popUpTo(...) }
        }
        is AggregationAuthentication.AdditionalAuthentication.Captcha -> {
          navigate("captcha") { popUpTo(...) }
        }
        ...
      }
    }
    // 金融機関のアカウントに紐づく口座 / カードの選択が必要な状態であれば選択画面へ遷移させる
    is AggregationAuthentication.Status.CandidateSelectionRequested -> {
      navigate("candidate_selection") { popUpTo(...) }
    }
    // 必要な操作がすべて完了した状態であれば完了画面へ遷移させる
    is AggregationAuthentication.Status.Completed -> {
      navigate("completed") { popUpTo(...) }
    }
    // 復帰不能なエラー状態であればエラー画面へ遷移させる
    is AggregationAuthentication.Status.Failed -> {
      navigate("error") { popUpTo(...) }
    }
  }
}

ロジックの見通しの良さも保たれる他、フロー内に存在する状態や画面の種類が将来増えた場合にも改修を要する箇所が限られるのは大きなメリットでしょう。

② 画面間の依存関係を疎に保てる

想定される遷移フローを網羅的に定義している訳でないため、画面の新設を伴わないフローの変更であればコードの修正なく実現可能です。今回の「クレジットカード・銀行口座連携機能」の開発末期では2段階認証を複数要求する金融機関がある…など想定の不足していたフローがいくつか判明しましたが、いずれもアプリ側の実装の修正は最小限に対応を完了できています。

また、ステート駆動な遷移ロジックを構築するにあたり、画面間の依存関係を疎に保つことが強制されるのも柔軟性や検証容易性に寄与しています。フローを表すリソースを適宜モックすることで各画面の検証を簡単に行うことができます。

③ 画面インスタンスやデータの揮発に強い

エントリー冒頭で画面遷移フローの構築の難しさの一因として「データや状態の揮発」を挙げ、以下のように述べました。

データや状態の揮発 : Androidにおいては画面インスタンス(Activity etc)やオンメモリで保持する状態よりも、画面スタックが長期にわたって保持される。画面インスタンスが再生成された際にも操作の継続性を担保するには、データ表現も含めて適切な形を探る必要性も生じうる。

今回のようなステート駆動な遷移ロジックを構築した場合、フローを表すリソースは冒頭で作成されAPIサーバーに保存されます。アプリ側での考慮を適切に行えば、リソースをAPIサーバーから再取得して操作の継続性を担保することができます。メモリ不足などでの画面インスタンスやオンメモリのデータの破棄に対し、堅牢なアプリを構築する一助となるかもしれません。

④ 監視や分析に必要な情報がサーバーに集約される

4つ目に挙げるのは当初メリットとして見込んでいなかった副産物です。前述の通り、フローを表すリソースがAPIサーバーに保存されるため、完了状態に至っていないリソースを分析することで “ユーザーがどこで離脱したのか” を明確に把握することができます。

アプリ起点のイベントログではデータ送信のタイミングやサンプリングによって、リアルタイム性や正確性を必ずしも担保できない場合があります。今回の「クレジットカード・銀行口座連携機能」では多様な金融機関に対応する中で、追加フローの突破率にリリース当初から強く意識を向けていました。そのため、それらの監視や分析に必要な情報がサーバーに集約されることが1つの利点となりました。

まとめ

このエントリーではB/43の新機能である「クレジットカード・銀行口座連携機能」を題材に、複雑な画面遷移フローの設計プロセスを記しました。

冒頭、システム上のデータ表現とデザインが密接に関与するような場面でのモバイルアプリエンジニアの振る舞いについて触れました。 “実現可能性のリスク” を下げる観点でも、複雑な画面遷移フローの構築において全体像や制約の認識を揃えるアプローチを早期に行ったことは有効であったと考えています。

後半では、リソース表現や画面群にもたらした性質を示しつつ、ステート駆動な遷移ロジックの設計について触れていきました。 「遷移ロジックの記述が一箇所に集約される」「画面間の依存関係を疎に保てる」などの複数のメリットも示しました。

このエントリーが複雑な画面遷移フローの設計や実装にあたる一つの参考例となれば幸いです。

SmartBank Advent Calendar 2024、まだまだ続きます!!

約1ヶ月にわたる SmartBank Advent Calendar 2024 もまだ8日目!明日9日目はPMの inagaki さんの記事です。お楽しみに ✨


スマートバンクでは一緒に B/43 を作り上げていくメンバーを募集しています!

12月18日(水)にはオフラインでのエンジニアイベントも開催予定なので、お気軽にご参加ください。やりたいことに対して「エンジニア」が足らんです!ほんとに… 😱

smartbank.co.jp smartbank.connpass.com

We create the new normal of easy budgeting, easy banking, and easy living.
In this blog, engineers, product managers, designers, business development, legal, CS, and other members will share their insights.