Android Studio
イベント
該当するコンテンツが見つかりませんでした
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
こんにちは。システムエンジニアのバッサーノです。 私はここ1年ほどモバイルデバイスに関連したソフトウェアの開発業務に携わっています。 特に近年はテスト自動化への注目が高まっており、モバイルデバイスについてもテスト自動化の導入が進んでいます。 今回はモバイルテストの自動化をする上で最もオーソドックスなツールであるAppium(アピウム又はアッピウム)について、概要や使い方に触れていきたいと思います。 この記事がモバイルアプリのテスト自動化に興味がある方、導入を検討している方や勉強中の方の参考になれば幸いです。 1. Appiumとは? 1.1. Appiumの概要 まずAppiumとは何か、形式的なところで言うと Appium は、iOS・Android向けのネイティブアプリ、ハイブリッドアプリ、Webアプリのテストを自動化するためのオープンソースツールです。 構造としては以下の図のようになっており、Appium Serverがテストスクリプトの命令を解析してAndroid/iOSデバイスを操作するコマンドへと変換し、デバイス上で指定した操作を実行してくれます。 またAppiumという名前からもわかるように、Webのテスト自動化で標準的に使用されているSeleniumベースの記法となっているため、すでにSeleniumを使用している方は同じような形式でAppiumのテストスクリプトを作成することができます。 1.2. Appiumの主な特徴 1.2.1. クロスプラットフォーム対応 iOS と Android の両方に対応し、同じテストコードで異なるプラットフォームをテストできます 1.2.2. 多言語サポート WebDriver Protocol に基づいているため、以下の言語でテストコードを記述できます Java Python JavaScript (Node.js) Ruby C# PHP 1.2.3. オープンソース Appiumはオープンソースで開発が進められているため、無償で利用することができます 1.3. Appiumを利用するメリット Appiumを使うことで以下の点を改善することができます。 自動化によってリグレッションテストのたびに同じ操作を繰り返す必要がなくなる iOSとAndroidで別々のツールを使い分ける必要がない CI/CDパイプラインにテストを組み込むことができる 2. Appiumを動かしてみる では実際にAppiumを使ってどのように自動テストができるのか、Appiumを実際にインストールして動かしてみましょう。 2.1. 事前準備 前項でも述べているようにAppiumはオープンソースであるため、無償でインストールして利用することができます。ここではサンプルとして以下の環境でAppiumを使用してAndroidのテストを実行してみます。 OS バージョン macOS 14.6.1 ツール バージョン 確認コマンド Node.js v20.19.1 node -v npm 11.6.2 npm -v JDK Java 8 java -version 2.2. Appium のインストール この記事の執筆時点(2025年11月)ではAppiumの最新バージョンは3.1.1であるため、今回はこのバージョンをインストールして使ってみます。使用するAppiumのバージョンによっては事前条件の各種ツールの必要バージョンも変化します。 # Appium本体のインストール npm install -g appium # インストールの確認 appium -v 出力例 : 3.1.1 2.3. UiAutomator2ドライバのインストール Appiumでは、プラットフォームごとのドライバを個別にインストールする必要があります。 # Android用UiAutomator2ドライバのインストール appium driver install uiautomator2 # インストール済みドライバの確認 appium driver list --installed 出力例 : ✔ Listing installed drivers - uiautomator2@6.3.0 [installed (npm)] なお、すでにAppium2系をインストール済みの場合は、ドライバのバージョンの競合などによりエラーが発生する場合があります。その場合はドライバの更新や再インストールなどを試してみてください。 2.4. Android Studioのインストール 今回はAndroid Studioのエミュレータを使用してテストを実行します。Android Studioをインストールされていない場合は公式サイトからインストールが可能です。 Android Studio公式サイト: https://developer.android.com/studio 2.5. 環境変数の設定確認 Androidツールを使用するためには環境変数が設定されている必要があります。以下のコマンドを実行し、JAVA_HOMEとANDROID_HOMEに正しいパスが表示され、PATHにそれらのパスが含まれていれば問題ありません。 # 環境変数の確認 echo $JAVA_HOME echo $ANDROID_HOME echo $PATH 未設定の場合は、以下を ~/.zshrc または ~/.bash_profile に追加します: # Java export JAVA_HOME=$(/usr/libexec/java_home -v 8) # Android SDK export ANDROID_HOME=$HOME/Library/Android/sdk export PATH=$ANDROID_HOME/platform-tools:$PATH export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH export PATH=$ANDROID_HOME/emulator:$PATH # 設定を反映 source ~/.zshrc # または source ~/.bash_profile 2.6. Appium Doctorで環境チェック Appium Doctor を使って、環境が正しくセットアップされているか確認します。 # Appium Doctorのインストール npm install -g appium-doctor # Android環境のチェック appium-doctor --android 出力例 : info AppiumDoctor Appium Doctor v.1.16.2 info AppiumDoctor ### Diagnostic for necessary dependencies starting ### … info AppiumDoctor ### Diagnostic for optional dependencies starting ### … 「 ### Diagnostic for necessary dependencies starting ### 」のすべての項目に ✓ が表示されればOKです。 2.7. Androidエミュレータの準備 今回はAndroidエミュレータを使用してテストを行います。 Android Studioを起動 Tools > Device Manager を開く Add a new device… > Create Virtual Device をクリック デバイス(例: Pixel 5)を選択して Next システムイメージ(例: API 33 (Android 13))を選択して Next (未ダウンロードの場合はダウンロードアイコンからダウンロードが可能) エミュレータ名を設定(例: Pixel_5_API_33 )して Finish Device Managerメニューにある「 ︎」を押してエミュレータを起動する Android Studio上でエミュレータの画面が表示されればOKです。 また、以下のコマンドでデバイスの接続状態が確認できます。「emulator-5554」という文字列がこのデバイスを指定するためのシリアルIDとなっており、実機の場合もここがデバイス固有の値になります。 # 接続されているデバイスを確認 adb devices 出力例 : List of devices attached emulator-5554 device Status が device と表示されていればOKです。 2.8. Python環境のセットアップ Appiumは複数の言語のスクリプトに対応していますが、今回はその中でもPythonを使用してサンプルのスクリプトを作成します。 以下で必要なライブラリのインストールを行います。 # Appium Pythonクライアントのインストール pip install Appium-Python-Client # Seleniumライブラリ(依存関係) pip install selenium # pytest(テストフレームワーク) pip install pytest 2.9. テストスクリプトの作成 それでは、実際に実行するテストスクリプトを見ていきましょう。ここでは、以下のようなテストを作成しています。 Androidの標準の設定アプリを起動する 画面要素を探して標準出力する スクリーンショットを取得する 流れとしてはまず端末の指定するためのシリアルIDやテスト対象のアプリの指定などの各種情報をcapabilitiesに設定して、この後立ち上げるAppium ServerにHTTP通信してセッションを作成します。 そして、そのセッションを使用してテスト内の各命令を送信し、デバイスを操作してテストを実行します。 ファイル名 : test_android_settings.py from appium import webdriver from appium.options.android import UiAutomator2Options from appium.webdriver.common.appiumby import AppiumBy import time def test_android_settings(): # Desired Capabilitiesの設定 options = UiAutomator2Options() options.platform_name = 'Android' options.automation_name = 'UiAutomator2' options.device_name = 'emulator-5554' # adb devicesで確認したデバイス名 # 設定アプリを起動(アプリのインストール不要) options.app_package = 'com.android.settings' options.app_activity = '.Settings' # セッション開始までのタイムアウト設定 options.new_command_timeout = 300 # Appium Serverに接続 driver = webdriver.Remote('http://localhost:4723', options=options) try: print("✓ 設定アプリが起動しました") # アプリが起動するまで少し待機 time.sleep(2) # 現在のアクティビティを取得 current_activity = driver.current_activity print(f"✓ 現在のアクティビティ: {current_activity}") # 画面上の要素を検索(検索ボックスを探す) search_elements = driver.find_elements(AppiumBy.CLASS_NAME, 'android.widget.TextView') print(f"✓ 画面上に {len(search_elements)} 個のTextView要素が見つかりました") # 最初のいくつかの要素のテキストを表示 print("\n--- 画面上の要素 ---") for i, element in enumerate(search_elements[:5]): text = element.text if text: print(f"{i+1}. {text}") # スクリーンショットを保存 driver.save_screenshot('settings_app.png') print("✓ スクリーンショットを保存しました: settings_app.png") print("\n✓ テスト成功!") except Exception as e: print(f"✗ エラーが発生しました: {e}") driver.save_screenshot('error_screenshot.png') finally: # セッションを終了 driver.quit() print("✓ セッションを終了しました") if __name__ == '__main__': test_android_settings() 2.10. Appium Serverの起動 ここまででテストを実行する準備が整いました。早速テストを実行してみましょう。 手順としてはまずAppium Serverを先に起動します。 # デフォルトポート(4723)で起動 appium # または、ログレベルを指定して起動 appium --log-level info 起動成功時の出力例 : [Appium] Welcome to Appium v3.1.1 [Appium] The autodetected Appium home path: /Users/testkit/.appium [Appium] Attempting to load driver xcuitest... [Appium] Attempting to load driver uiautomator2... [Appium] Requiring driver at /Users/testkit/.appium/node_modules/appium-uiautomator2-driver/build/index.js [Appium] Requiring driver at /Users/testkit/.appium/node_modules/appium-xcuitest-driver/build/index.js [Appium] AndroidUiautomator2Driver has been successfully loaded in 1.403s [Appium] XCUITestDriver has been successfully loaded in 3.417s [Appium] Appium REST http interface listener started on http://0.0.0.0:4723 [Appium] You can provide the following URLs in your client code to connect to this server: http://127.0.0.1:4723/ (only accessible from the same host) http://192.168.3.13:4723/ http://192.168.64.1:4723/ http://172.32.1.15:4723/ http://172.32.1.34:4723/ http://172.32.1.26:4723/ [Appium] Available drivers: [Appium] - xcuitest@10.8.0 (automationName 'XCUITest') [Appium] - uiautomator2@6.3.0 (automationName 'UiAutomator2') [Appium] No plugins have been installed. Use the "appium plugin" command to install the one(s) you want to use. 注意 : Appium Serverは起動したままにしておきます(テストスクリプトは別のターミナルウィンドウで実行してください)。 2.11. スクリプトの実行 # スクリプトを実行 python test_android_settings.py 実行成功時の出力例 : ✓ 設定アプリが起動しました ✓ 現在のアクティビティ: .Settings ✓ 画面上に 42 個のTextView要素が見つかりました --- 画面上の要素 --- 1. Settings 2. Network & internet 3. Connected devices 4. Apps 5. Notifications ✓ スクリーンショットを保存しました: settings_app.png ✓ テスト成功! ✓ セッションを終了しました 出力されたスクリーンショット(settings_app.png) このようにAppiumを使用することでモバイル端末上でアプリを起動し自動テストを実行することができます。実行時にAndroid Studioのエミュレータの画面をみてみると、実際に端末の設定画面が起動されるところも確認できると思います。 また、今回はAndroid Studioのエミュレータを使用しましたが、実機をADB接続することでエミュレータと同様に実機上でアプリをテストすることも可能です。 3. まとめ 本記事では、モバイルテストの標準的な自動化ツールとして、Appiumの概要を説明し、インストールから実際のテストコード実行までを解説しました。 Appiumは環境構築でのコマンドラインの操作やテストスクリプトの作成など、普段あまり触れない方にとってはとっつきにくい部分もあるかもしれません。実際に現在では様々なテスト自動化のGUIツールが存在し、コードレスに自動テストを作成することもできます。しかしAppiumは原始的な分、より柔軟なテストが作成できますし、自動テストの原理や流れを理解しやすいという点でも勉強して損はないと思います。 次回は、実機でのAppiumのテスト実行の手順や、より複雑なテストを作成するのに便利なツールの紹介をしていきます。 The post Appiumモバイルテスト自動化入門(1) 〜環境構築と初めてのテスト〜 first appeared on Sqripts .
1. はじめに 本記事は Timee Product Advent Calendar 2025 シリーズ1 19日目の記事です。 こんにちは。株式会社タイミーでiOSエンジニアをしている hayakawa です。 普段はiOSアプリの開発を担当していますが、弊社では職種の垣根を超えて異なる技術領域に挑戦する「越境」が盛んです。また、開発プロセス全体でAI・LLMを活用する流れも加速しています。 今回は、自身の技術領域を広げるためと、チームのケイパビリティ向上のために、Androidの実装を担当しました。本記事では、その際の実践的な知見やAI活用のポイントについて共有します。 2. 筆者のAndroidに関する前提知識 実装開始時点での私のAndroidに関する知識レベルは以下の通りです。 言語: Kotlin(Swiftと似ているという認識程度) UI: Jetpack Compose(SwiftUIに近い宣言的UIフレームワークという認識)。また、従来はXML(iOSのXIBやStoryboardのようなもの)を使って構築する手法があることも、知識としては持っている。 非同期処理: Coroutines(CombineやSwift Concurrencyの概念で理解) 開発環境: Android Studio(基本的な操作方法も不慣れな状態) 「概念は理解しているが、具体的なAPIやIDEの操作は手探り」という状態からのスタートでした。 3. 今回挑戦した実装内容 今回担当したのは、以下の2つの機能実装です。 ① 条件に応じた注意書き表示ロジック サーバーレスポンスに含まれる「未来の時間情報」に基づき、UIを出し分ける機能です。 現在時刻から指定時間までの「残り時間」をカウントダウン表示する。 端末の「通知設定(ON/OFF)」を取得し、文言を切り替える。 ② 条件に応じたUI制御とデザインシステムの拡張 サーバーレスポンスに含まれる特定の値に基づき、View内のコンポーネントを制御する実装です。 デザインシステムに新たなカラーバリエーションを追加定義する。 既存のリスト表示(RecyclerView)の一部に、Jetpack Compose化したViewを組み込む。 組み込んだView内に追加のコンポーネントを表示する。 なお、同様の機能をiOS版でも私が担当しており、そちらは「実装1日、レビュー・マージまで2日」で完了しています。iOS版の実装時点では、Android版はキャッチアップも含めてその1.5倍ほどの工数で完了すると予想していました。 4. AI活用の勘所:メンタルモデルに合わせた翻訳 実装は Claude Code と協調して進めました。 ここで効果的だったのは、単にコードを書かせるのではなく、 「自分の持っているiOSの知識とマッピングさせる」 というプロンプトの出し方です。 プロンプトの工夫:「iOSエンジニア向けに説明して」 具体的には、以下のような指示を行いました。(※例なので実際に入力したプロンプトとは異なります) Prompt: 「{SwiftのAPI名}を使って {やりたいこと} を実装したい。これを {KotlinのAPI名} を使ったコードと、Swiftの概念と比較した実装方針とコードを提示して このように依頼することで、AIは「StateFlowはSwiftでいうCurrentValueSubjectに近い挙動です」といった補足を加えてくれます。これにより、単なるコピペではなく、挙動を正しく理解しながら実装を進めることができました。 5. ぶつかった技術的・文脈的な壁 AIの支援があっても、スムーズにいかない場面がいくつかありました。 ① RecyclerViewとComposeの共存 最も苦戦したのは、既存のRecyclerViewの中にJetpack Composeで作ったViewを組み込む部分です。 SwiftUIであれば UIHostingController 等で比較的直感的にブリッジできますが、Androidの既存実装(ViewHolder)の中に、ComposeViewをライフサイクル的にどのように正しく配置するか 、という点は構文の違い以上に「作法」の違いが大きく、AIの出力したコードをそのまま適用するだけでは、ビルドエラーやレイアウト崩れが発生しました。 ② モデル名の不一致(ドメイン知識の欠如) また、プロジェクト固有の「命名規則」の壁もありました。 例えば、iOSで定義されているモデル名が、Androidでは少し違う名前になっているケースがありました。 例) iOS: ServiceRequest Android: RequestedService 私がiOSの感覚で「 ServiceRequest を拡張して」とAIに指示すると、AIはプロジェクト内に RequestedService が存在することを知らないため、 新しく data class ServiceRequest を定義してしまいました。 これは「AIはプロジェクトの歴史や文脈までは(コンテキストに含めない限り)知らない」という典型的な落とし穴でした。 6. 人間(Androidエンジニア)との協調 AIが出力したコードは「動く」ものの、それが「保守性の高いコード」であるかは別問題です。 そこで、Androidエンジニアのチームメンバーに対して、「同期的な相談」の時間を設けてレビューを依頼しました。 この時、私が意識して聞いたのは「合っていますか?」ではなく、以下の質問です。 「期待通りに動くんですが、Androidの流儀としてもっと良い書き方はありますか?」 AIは汎用的な正解を出しますが、現場のベストプラクティス(例えば、よりモダンなライブラリの選定や、プロジェクト独自の拡張関数の活用など)は、人間の方が詳しいケースが多々あります。このプロセスを経ることで、コードの品質を担保しました。 7. まとめ 今回の挑戦で得られた知見は以下の通りです。 AIは「言語の壁」を限りなく低くする Swiftの知識があれば、適切なプロンプトでKotlinのコードを生成・理解することは容易です。 ドメイン知識とアーキテクチャの理解は人間が補う必要がある モデル名の違いや、既存コンポーネント(RecyclerView等)との整合性は、AI任せにせず人間がハンドリングする必要があります。 「もっと良い書き方」は人間に聞く AIで「0→80点」まで持っていき、最後の「80→100点(最適化)」を専門家との対話で行うスタイルが非常に効率的でした。 AIという強力なパートナーがいれば、iOSエンジニアにとってAndroid開発(あるいはその逆)は、もはや高いハードルではありません。今後も積極的に技術領域を越境していきたいと思います。 タイミーではiOSエンジニアやその他のポジションを含めて採用活動中です! プロダクト採用サイトTOP カジュアル面談申込はこちら
プロダクト部 AppグループでAndroidアプリ開発を担当している山越です。 現在、Appグループでは既存アプリのUIをXMLレイアウトからJetpack Composeへ段階的に移行しています。 本記事では、「Compose化」を進める背景や目的、実際の進め方、そして移行の中で直面した課題・クラッシュ事例についてご紹介します。 「Compose化」とは? Jetpack Composeとは、Android向けの新しい宣言的UIフレームワークです。 従来のXMLレイアウトでは、UIの見た目と動作を別々のファイルで管理する必要があり状態更新のたびにViewを直接操作する必要がありました。 一方のComposeでは、UIをKotlinコード上で直接定義することとなり、アプリの状態(State)に応じて自動的にUIを再描画することが可能となります。 「Compose化」とは、こうしたComposeの考え方を既存アプリにも取り入れて、XMLやViewBindingを使っていた画面を段階的にComposeへ移行する取り組みを指します。 スタンバイでは、UIの保守性・再利用性・開発効率の向上を目的として、既存画面のCompose化に着手しました。 移行作業の進め方 今回の移行作業では、全画面を一気に置き換えるのではなく、段階的に移行する方針を取りました。 新規機能は最初からComposeで実装して、既存画面は影響度の低いところからCompose化を進めています。 これにより影響範囲を最小限に抑えながらComposeへの知見を積み重ねることができました。 XMLレイアウトのリソースをComposeへ 移行の第一歩として、既存のXMLレイアウトをCompose化しました。 画面全体を一気にCompose化するのではなく、ButtonやCardといった汎用的なUIコンポーネント単位での置き換えから始めています。 Compose化した画面は、Fragment上でComposeViewを利用して組み込む方法をとっています。 class SampleFragment: Fragment() { // ViewModel参照 private val viewModel: SampleViewModel by viewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = ComposeView(requireContext()).apply { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnLifecycleDestroyed( viewLifecycleOwner ) ) setContent { // Compose化した画面 SampleScreen( uiState = viewModel.uiState, onClickButton = { viewModel.onClickButton() } ) } } } こうすることで、FragmentやViewModelの構造は活かしつつ部分的にComposeを導入することで移行のリスクを抑えています。 既存Viewとの共存 Compose化の途中では、XMLとComposeが混在する期間は避けられません。 そのため、以下のような点を意識して両者を共存させました。 テーマおよびカラー定義をMaterialThemeとXMLスタイルで統一 ViewModelは共通で利用して、StateFlowを collectAsState() で監視 この結果、既存プロダクトの安定性を保ちながらCompose化を進めることができました。 つまづいたところ 汎用コンポーネントの対応を経て画面単位のCompose化を行なっていましたが、スムーズに進まないケースがいくつか発生しました。 共通して直面した課題に対してどのような手法をとって対応してきたかをご紹介します。 テーマ設定やレイアウト崩れ Compose化を進める中で、既存テーマとの整合性でいくつか課題がありました。 既存アプリでは themes.xml や styles.xml で定義したカラーやフォントサイズを利用していました。 一方、Compose側では MaterialTheme を経由する必要があるため、色味や余白が一致しないケースもありました。 スタンバイでは、Figma上にデザイン定義が整理されているため細かい調整は不要でしたが、実装上ではXMLリソースをComposeに寄せる過程で小さなずれが多く発生しました。 特にTextStyleの設定はMaterialTheme配下に再定義し、colorResourceを活用して既存リソースとの不整合を解消しました。 また、再利用可能なUI部品は designsystem パッケージにまとめる構成を採用しました。 このパッケージではボタンやテキストフィールドなどの共通UIコンポーネントを定義して、各画面ではそれらを呼び出してUIを構築しています。 この構成によりアプリ全体のデザインを統一しつつ、デザイン修正を一箇所で反映できるようになりました。 また、Figma上のデザイン定義とComposeコードの対応関係も整理され、Compose化によるUIのばらつきを防ぐことができています。 プレビューとの乖離 Jetpack Composeにはプレビュー機能があり、コンポーネント単位で描画されるUIを即座に確認できます。 Composeのプレビュー機能は非常に便利な反面、実際のAndroid端末上の挙動と異なる場合があります。 特にViewModel経由で状態を受け取る画面や、 LocalContext・MaterialTheme に依存するComposableでは、 プレビュー上でスタイルが反映されなかったり、クラッシュしたりするケースがありました。 そのためプレビュー用にダミーのUiStateを渡す「@Preview関数」を用意し、テーマも本番と同じAppThemeを適用して確認するよう意識しました。 それでも最終的な見た目や動作はエミュレータおよび実機確認で差分を吸収するようにしています。 思わぬクラッシュの発生 Compose化完了後の画面を実機またはエミュレータで動かすと、クラッシュすることがありました。 Android Studio上では警告やエラーなどの問題は表示されていません。 今回、Appグループが直面したものから2点のクラッシュを抜粋して対処方法も合わせてご紹介します。 Columnの入れ子構造による高さ非制限 とある画面のCompose化を進めていく中で、Columnの入れ子構造を持つ画面表示の際にクラッシュが発生しました。 この画面では親要素が Column 、その中に LazyVerticalGrid を配置しており wrapContentHeight() を指定していました。 Column { LazyVerticalGrid( columns = GridCells.Fixed( 2 ), modifier = Modifier .fillMaxWidth() .wrapContentHeight(), // コンテンツのサイズに応じた高さにしたい verticalArrangement = Arrangement.spacedBy( 8 .dp), ) { items(items = targetItems, key = { it.id ?: it.hashCode() }) { item -> // 子要素のComposable } } } この場合、 Column は縦方向に無限スクロール可能とみなされます。 そして、内部の LazyVerticalGrid も高さを計算しようとして「無限サイズを要求する形」になり、 描画時に下記の例外が発生しました。 IllegalStateException : Vertically scrollable component was measured with an infinite height constraints これは、親と子の両方がスクロール可能(または高さ非制限)な構成になっていたことが原因です。 対応として、内部の LazyVerticalGrid に Modifier.heightIn(max = 200.dp) を付与して高さの上限を明示することで無限再帰的な計測を防ぎました。 Column { LazyVerticalGrid( columns = GridCells.Fixed( 2 ), modifier = Modifier .fillMaxWidth() .wrapContentHeight() .heightIn(max = 200 .dp), // 高さの上限指定を追加 verticalArrangement = Arrangement.spacedBy( 8 .dp), ) { items(items = targetItems, key = { it.id ?: it.hashCode() }) { item -> // 子要素のComposable } } } heightIn() を使うことでComposeの測定制約を明確にし、描画処理が安定するようになりました。 バックグラウンド復帰時の例外発生 もうひとつのクラッシュは、アプリのバックグラウンド復帰時に発生したものでした。 とある状態を保持するためにScreen内で rememberSaveable を利用していましたが、保持していたオブジェクトが画面再生成時に正しく復元できず IllegalStateException: MutableState cannot be saved が発生しました。 val type = rememberSaveable { mutableStateOf<SampleType?>( null )} rememberSaveable では保存できる型が限られており、ParcelableまたはSerializable、もしくはSaverで明示的に変換できるものだけが対象です。 今回のケースでは、保存対象のデータクラスがParcelableを実装しておらず、復帰時に型不一致として例外がスローされていました。 対応として、Saverを実装して保存対象を明示することで解決しました。 // SampleTypeを保持するためのSaver private val SampleTypeSaver = Saver<SampleType, Map < String , String >>( // 保存するときはMap<String, String>型に変換 save = { sampleType -> when (sampleType) { is SampleType.TypeA -> mapOf( "type" to "A" ) is SampleType.TypeB -> mapOf( "type" to "B" ) is SampleType.TypeC -> mapOf( "type" to "C" , "key" to sampleType.value ) else -> mapOf() } }, // 復元するときはSampleTypeに変換 restore = { map -> when (map[ "type" ]) { "A" -> SampleType.TypeA "B" -> SampleType.TypeB "C" -> SampleType.TypeC(map[ "key" ].toString()) else -> null } } ) ~ // stateSaverに作成したSampleTypeSaverを割り当てる val type = rememberSaveable(stateSaver = SampleTypeSaver) { mutableStateOf<SampleType?>( null )} このように、Composeは状態のスコープが明確である一方で、 Activity再生成やプロセスキル時に永続化対象を誤るとクラッシュしやすいため注意が必要です。 完全な「Compose化」までの課題点 Compose化は着実に進んでいますが、完全な移行にはまだいくつかの課題が残っています。 現在の主な課題は「DeepLink経由での画面遷移処理」と「トップ画面におけるボトムナビゲーションによるタブ遷移」です。 現状、これらの部分は既存のFragmentをベースに動作しており、Compose Navigation への置き換えにはさらなる検討が必要です。 特にルーティング管理やデータの受け渡し、DeepLink起動時にの初期タブの制御やバックスタックの再構築など、 既存のナビゲーション構造とCompose側の遷移管理をどう共存させるかが課題となっています。 ボトムナビゲーションについても、画面再生成やタブ切り替え時の状態保持をCompose側でどこまで担うかを整理する必要があります。 今後はこれらの遷移処理をCompose Navigationに統一し、アプリ全体を完全なComposeベースへ移行することを目標としています。 まとめ Compose化を進めていく中で、開発効率やUI設計の柔軟性といった多くのメリットを実感できました。 特に、UIをKotlinコード上で完結できる点や、状態管理をViewModelと密に連携できる点は大きな強みです。 また、共通UIを designsystem というパッケージを作成してまとめることで画面ごとの見た目の統一や再利用性も向上しました。 一方で、導入初期はCompose特有のレイアウト制約や状態保持の仕組みによるクラッシュなど、これまでのXMLベースとは異なる観点でのトラブルシューティングが必要でした。 また、部分的なCompose導入ではXMLとの共存コストも発生し、完全な移行にはまだ時間がかかると感じています。 それでも、UI実装のシンプルさや開発体験の改善は大きく、長期的に見ればCompose化のメリットが明確に上回ると実感しています。 機会があれば今後の改善や完全移行についてもご紹介させていただければと存じます。 スタンバイのプロダクトや組織について詳しく知りたい方は、気軽にご相談ください。 www.wantedly.com
動画
該当するコンテンツが見つかりませんでした








