ComposableView の闇

Jetpack Compose を導入したからといって、アプリが「Compose アプリ」になったわけではありません。

多くのプロジェクトでは、XML を ComposeView に置き換えただけで移行を終えています。

しかし、その状態は Compose の世界と View の世界の両方を同時に抱える アーキテクチャです。

一見すると移行できたように見えますが、実際には複雑さが増えていることも少なくありません。

 

🧑🏻‍💻 Compose に見えて、実は View

ComposeView は名前の通り View です。


Activity
└── Fragment
    └── ComposeView
        └── Composition

つまり、Compose を書いていても、

- Activity のライフサイクル
- Fragment のライフサイクル
- View のライフサイクル
- Composition のライフサイクル

という複数のライフサイクルの上で動いています。

Compose 本来のシンプルさは、この時点ではまだ得られていません。

 

🧑🏻‍💻 View のルールから逃げられない

Compose だけなら意識しなくて済むことも、ComposeView では必要になります。

例えば Composition の破棄タイミング。


composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)

これを適切に設定しなければ、Composition が期待どおり破棄されないケースがあります。

Compose アプリでは考えなくてよいことを、ComposeView では考える必要があります。

 

🧑🏻‍💻 Fragment は消えていない

ComposeView を使う構成は、多くの場合こうなります。


Fragment
    ↓
ComposeView
    ↓
Composable

これは

Fragment を Compose に置き換えた

のではありません。

実際には

Fragment の中へ Compose を埋め込んだ

だけです。

Fragment の責務もライフサイクルも Navigation も、そのまま残っています。

 

🧑🏻‍💻 「View を Compose に置き換える」は移行の半分

Compose 化というと、多くの人は View を Composable に書き換えることを思い浮かべます。

しかし、本当に変わるのは UI だけではありません。

Compose First の設計では、

- Fragment が不要になる
- XML が不要になる
- ViewBinding が不要になる
- FragmentManager への依存が減る
- Navigation の考え方が変わる
- State の持ち方も変わる

つまり、アーキテクチャ全体が変わります。

 

🧑🏻‍💻 Navigation 3 が示している未来

Navigation 3 のサンプルを見ると、Fragment は登場しません。


Activity
    ↓
NavDisplay
        ↓
Composable Screen

画面そのものが Composable になり、状態は Compose の仕組みを中心に管理されます。

ここでは ComposeView は必要ありません。

Compose がアプリの土台になっています。

 

🧑🏻‍💻 ComposeView は悪者ではない

もちろん ComposeView が悪いわけではありません。

既存アプリを段階的に Compose へ移行するには、とても重要な仕組みです。

大規模アプリでは、一気に Compose へ移行できるケースはほとんどありません。

だから ComposeView は今でも価値があります。

問題なのは、

ComposeView を使っている状態を「Compose 化が完了した状態」と思ってしまうことです。

 

🧑🏻‍💻 本当の Compose 化とは

本当の Compose 化は、単に XML を消すことではありません。


XML
    ↓
ComposeView
    ↓
Fragment を減らす
    ↓
Navigation を Compose 中心へ
    ↓
State 管理を Compose に合わせる
    ↓
Single Activity

この変化によって初めて、

Compose のシンプルな設計思想をそのまま活かせるようになります。

 

🧑🏻‍💻 おわりに

「ComposeView を使っています。」

この一文だけでは、そのアプリが Compose アプリなのか、View アプリなのかは分かりません。

もし ComposeView が Fragment の中にあり、Fragment Navigation の上で動いているなら、それはまだ View ベースのアプリに Compose を埋め込んでいる状態です。

Compose の本当の価値は、UI を書き換えることではありません。

アーキテクチャそのものをシンプルにできること。

そこまで進んだとき、ようやく「Compose 化した」と言えるのではないでしょうか。


RxJavaすら使わない。Androidに潜む「古代Java」の亡霊たち

JavaエンジニアがKotlinに移行する際、最も危険なのは「Kotlinの文法でJava5の頃の思考で書く」ことです。

RxJava(リアクティブプログラミング)という高い壁を飛び越えようとして、逆に20年前の古典的手法に着地してしまうケースが後を絶ちません。


fun loadUser(callback: (User?) -> Unit) {
    api.getUser { user ->
        if (user != null) {
            database.save(user) {
                cache.update(user) {
                    analytics.track(user) {
                        callback(user)
                    }
                }
            }
        } else {
            callback(null)
        }
    }
}


interface OnUserLoadedListener {
    fun onLoaded(user: User)
}

fun loadUser(listener: OnUserLoadedListener) {
    api.getUser(object : ApiCallback {
        override fun onSuccess(user: User) {
            database.save(user, object : SaveCallback {
                override fun onSaved() {
                    listener.onLoaded(user)
                }
            })
        }

        override fun onError() {
        }
    })
}

 

🤔 1. RxJava以前の「古代遺物」がモダンなKotlinを侵食する

RxJavaすら導入されていない現場、あるいは「Rxは難しいから」と避けた結果、以下のような絶滅危惧種がKotlinの皮を被って出現します。

① 独自インターフェースによる「バケツリレー」

interface MyCallback を定義し、それを Activity から Presenter(あるいは ViewModel)、さらに Repository へと引数で渡していくスタイルです。

地獄のポイント: 1つの処理を追うのに3つ以上のファイルを跨ぐ必要があり、デバッグ中に「今どこにいるのか」を見失います。

② AsyncTask の「自力再実装」

Googleが非推奨にした AsyncTask ですら、中身はスレッド管理とコールバックの塊でした。これをKotlinの Thread { ... }Handler(Looper.getMainLooper()) で自作再現してしまうパターンです。

地獄のポイント: isDestroyed のチェックを忘れ、画面を閉じた後にクラッシュ(NullPointerException)させる「爆弾」を量産します。

③ MutableList を使った共有メモリの恐怖

非同期の戻り値を待てず、外部の MutableList に値を詰め込ませ、別の場所で TimerThread.sleep を使って「値が入ったか監視する」という、スレッドセーフを無視した力技です。

 

🤔 2. なぜ「古代手法」は再生産されるのか?

それは、Javaエンジニアが長年培ってきた「命令型プログラミング」の呪縛です。

「待つ」という概念の欠如: 「処理を止めたらスレッドが死ぬ(UIが固まる)」という恐怖心から、すべてを「終わったらこれを呼べ」という受動的な構造(ハリウッド原則)にしてしまいます。

状態管理の煩雑さ: 古いJavaでは、状態の変化を「通知」する仕組みが乏しかったため、泥臭いフラグ管理やコールバックに頼らざるを得ませんでした。

 

🤔 3. 歴史の授業:非同期処理の進化系統図

今のAndroid開発者が知っておくべき、技術の「地層」は以下の通りです。

 

🤔 4. まとめ:レガシーの鎖を断ち切るために

Javaエンジニアの皆さんが持つ「堅牢なクラス設計」の知識は宝です。しかし、「非同期処理の書き方」だけは、一度全て忘れてください

Kotlinにおける suspend は、ただのキーワードではありません。それは、私たちが10年以上苦しめられてきた「コールバック地獄」という名の迷宮から脱出するための、唯一の出口なのです。


Mac OS Sequoia 15.4 で Android Studio を含む IDEA系 IDE が起動できないとかクラッシュの件

いろいろ問題起きてます。

Android Studio など IDEA Intellij 系 IDE すべて起動できない。

 

🤔 JBR の入れ替え

同梱されているブートに利用されているJBRを変えなさい、と。

👉 IntelliJ-based apps (and Fleet) crashes on macOS 15.4 Beta 3 (developer beta) : JBR-8422

仕方なく記述されているリンクから入れる。

👉 Mac に Homebrew で OpenJDK11 を インストール する


❯ java -version
openjdk version "21.0.6" 2025-01-21
OpenJDK Runtime Environment JBR-21.0.6+9-895.105-jcef (build 21.0.6+9-b895.105)
OpenJDK 64-Bit Server VM JBR-21.0.6+9-895.105-jcef (build 21.0.6+9-b895.105, mixed mode, sharing)

私の場合これだけでは起動できませんでした。

 

🤔 Sequoia 新機能 キーボードショートカット

便利な新機能なのですが。


これが影響していました。

すべて OFF に。

👉 IDEa crashes at startup on macOS if macOS keyboard shortcuts for Window tiling have been changed : JBR-8562

これでいけた。

いったん OS ログアウトするといけるようになる。

 

🤔 まとめ

2つの問題を乗り越えてやっと起動できるようになりました。

まだ待ったほういいかもしれん。

他にもいろいろありそう。

 

🧑🏻‍💻 追記: 2025-04-23

👉 【Fixed】Android Studio startup crashes on macOS 15.4 Sequoia


この操作を完了する権限がありません [OR-CAC-07]

これなんなのか。

聞いてみました。


スルーかな。

仕方ないので関連ポストを探す。