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年以上苦しめられてきた「コールバック地獄」という名の迷宮から脱出するための、唯一の出口なのです。


【Jetpack Compose】mutableStateOf(list) と mutableStateListOf() の違いと使い分け

Jetpack Composeでリストを扱う際、多くの開発者が最初にぶつかる壁が
「mutableStateOf(list) と mutableStateListOf() どっちを使えばいいの?」
という疑問です。

この記事では、両者の型、構造、そして「なぜあの書き方は動かないのか?」という落とし穴についてスッキリ整理して解説します。

 

🤔【結論】一目でわかる比較表

 

🤔 mutableStateOf(listOf<T>())

型の構造:箱ごと入れ替えるスタイル

mutableStateOf は「値(value)」そのものを監視します。リストを扱う場合、中身は不変(Immutable)な List であることが前提です。

型 :
MutableState<List<T>>

変更方法:
state.value = state.value + "A"

リストの中身をいじるのではなく、「新しいリストを作成して、Stateという箱に入れ直す」ことでComposeに通知します。

 

🤔 mutableStateListOf<T>()

型の構造:中身の動きを監視するスタイル

mutableStateListOf は、それ自体が MutableList のように振る舞い、内部の要素が追加・削除されたことを Compose に直接伝えます。

型 :
SnapshotStateList<T>

変更方法:
list.add("A"), list.remove("B")

リスト自体を再代入する必要はありません。addremove を実行するだけで、Compose が自動的に変更を検知して再描画してくれます。

 

🤔 実務での使い分け

mutableStateOf(list) を使うケース

主に UI State をデータクラスで一括管理する場合です。

・ Flow から combine して State を生成する
data class を用いた一方向データフロー(UDF)設計


data class MyUiState(
    val items: List<String> = emptyList()
)

// ViewModelなどで管理
var uiState by mutableStateOf(MyUiState())

mutableStateListOf() を使うケース

リストそのものが「動的な操作の主体」である場合です。

Navigation3rememberNavBackStack()
・ 要素の追加・削除が頻繁に発生する編集画面やスタック操作

 

🤔 まとめ

基本的には
「UI全体の状態管理なら mutableStateOf」
「リスト個別の動的操作なら mutableStateListOf」
と使い分けるのがスマートです。


Jetpack Compose Navigation3:rememberNavBackStack で実現する宣言的ナビゲーション

Jetpack Compose の次世代ナビゲーションライブラリ Navigation3 では、従来の NavHost と文字列ベースのルート定義から脱却し、より "Compose らしい" 状態管理へと進化しました。

その中核を担うのが rememberNavBackStack です。今回は、この新しい API の「型」の意味と、それを使った最小構成のサンプルコードを解説します。

 

🧑🏻‍💻 rememberNavBackStack の型とその正体

まず、この関数が何を返しているのかを確認しましょう。


val backStack: SnapshotStateList<T> = rememberNavBackStack(initialBackStack)

この関数が返す型は、Compose 独自の SnapshotStateList です。

 

🧑🏻‍💻 なぜこの型なのか?

オブザーバブル(監視可能): SnapshotStateList は、リストの中身(要素の追加や削除)が変化したことを Compose のランタイムに通知します。これにより、スタックを操作した瞬間に NavDisplay が自動的に再描画されます。

保存と復元: rememberNavBackStack は内部で rememberSaveable の仕組みを利用しています。つまり、画面回転やプロセスの再起動が発生しても、ナビゲーションの履歴(スタック)が消えずに保持されることを意味します。

直感的な List 操作: MutableList インターフェースを継承しているため、開発者は add()removeLast()clear() といった標準的なメソッドで遷移をコントロールできます。

 

🧑🏻‍💻 実装サンプル

1. 画面(デスティネーション)の定義
スタックに積むデータ型を sealed interface で定義します。


sealed interface Screen {
    data object Home : Screen
    data class Details(val id: String) : Screen
}

2. ナビゲーションの構築


@Composable
fun MyNavigationApp() {
    // initialBackStack で最初の画面を指定
    // 戻り値はスタックの状態を保持する SnapshotStateList<Screen>
    val backStack = rememberNavBackStack(initialBackStack = listOf(Screen.Home))

    NavDisplay(
        backstack = backStack,
        onBack = { 
            // スタックが2つ以上あれば、最後の要素を取り除いて「戻る」
            if (backStack.size > 1) {
                backStack.removeLast() 
            }
        }
    ) { screen ->
        when (screen) {
            is Screen.Home -> HomeScreen(
                onNavigateToDetails = { id -> 
                    // 1. 新しい画面をスタックに add するだけ
                    backStack.add(Screen.Details(id)) 
                }
            )
            is Screen.Details -> DetailsScreen(
                id = screen.id,
                onBack = { 
                    // 2. 現在の画面を remove するだけで戻れる
                    backStack.removeLast() 
                }
            )
        }
    }
}

 

🧑🏻‍💻 まとめ

Navigation3 において、rememberNavBackStack は単なる「履歴リスト」ではなく、「アプリの現在の状態そのもの」を管理するハブです。

型安全: 任意のオブジェクト(Screen)をスタックに積める。

宣言的: スタックの状態が変われば、UI(NavDisplay)が同期して変わる。

堅牢: SnapshotStateList により、構成変更(回転など)にも強い。

これまでの NavController による命令的な遷移から、「スタックというデータを操作する」という Compose 本来の作法へ。Navigation3 は開発体験を大きくシンプルにしてくれます。


Jetpack Compose Foundation サンプル目次リンク

Jetpack Composeの核心を担う androidx.compose.foundation

そのサンプルコード群は、Googleのエンジニアが「正しい書き方」を提示している宝庫です。

今回は、これらを実務での利用頻度とモダンな設計(2026年現在のトレンド)に基づいてグループ分けしました。

 

🧑🏻‍💻 1. インタラクション & ジェスチャー(操作感のキモ)

ユーザーが画面に触れた時の挙動を制御する、最も重要なグループです。

 

🧑🏻‍💻 2. スクロール & リスト(データの表示)

効率的にスクロールさせるためのテクニック集です。

 

🧑🏻‍💻 3. テキスト & 入力(文字の表示と編集)

2026年のトレンドである「次世代入力」が含まれます。

 

🧑🏻‍💻 4. 描画 & 視覚効果(見た目のクオリティ)

 

🧑🏻‍💻 5. 高度なシステム統合・同期

 

🧑🏻‍💻 これだけは読んでおくべきトップ5

1. ClickableSamples.kt(すべての基本)
2. LazyDslSamples.kt(リスト表示の要)
3. AnchoredDraggableSample.kt(モダンなUIに必須)
4. BasicTextFieldSamples.kt(入力の実装)
5. CanvasSamples.kt(カスタムUIの第一歩)

ぐらいか。


[Jetpack Compose] Implement "Pull-to-Refresh" with the New PullToRefreshBox

The "Pull-to-Refresh" gesture is a staple in Android app UI.

While we previously relied on Modifier.pullRefresh, Jetpack Compose has introduced PullToRefreshBox in Material 3 as the new standard. It's more intuitive and requires much less boilerplate code.

In this post, we’ll quickly cover everything from basic implementation to customization!

 

🧑🏻‍💻 1. Prerequisites

PullToRefreshBox is available in Material 3 (version 1.3.0 or later).

Make sure to check your build.gradle dependencies:


dependencies {
    implementation("androidx.compose.material3:material3:1.3.0")
}

 

🧑🏻‍💻 2. Basic Implementation Pattern

The best part about PullToRefreshBox is that it encapsulates both the refresh logic and the indicator UI into a single component.


@Composable
fun RefreshableListScreen() {
    var isRefreshing by remember { mutableStateOf(false) }
    val scope = rememberCoroutineScope()
    val items = remember { mutableStateListOf("Initial Item A", "Initial Item B") }

    PullToRefreshBox(
        isRefreshing = isRefreshing,
        onRefresh = {
            scope.launch {
                isRefreshing = true
                // Perform your refresh logic (e.g., API calls)
                delay(2000) 
                items.add(0, "New Item ${items.size + 1}")
                isRefreshing = false
            }
        }
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(items) { item ->
                ListItem(headlineContent = { Text(item) })
            }
        }
    }
}

Key Highlights

  • isRefreshing: A boolean that controls the visibility of the refresh indicator.
  • onRefresh: The callback triggered when the user performs the pull gesture.
  • Content Size: Ensure your scrollable content (like LazyColumn) uses Modifier.fillMaxSize() so the pull gesture is detectable across the entire area.

 

🧑🏻‍💻 3. Practical Usage with ViewModel

In a production environment, it's best practice to let a ViewModel handle the state.


class MyViewModel : ViewModel() {
    var isRefreshing by mutableStateOf(false)
        private set

    fun refreshData() {
        viewModelScope.launch {
            isRefreshing = true
            // Simulate network call
            isRefreshing = false
        }
    }
}

val viewModel: MyViewModel = viewModel()
PullToRefreshBox(
    isRefreshing = viewModel.isRefreshing,
    onRefresh = { viewModel.refreshData() }
) {
    // ... Content
}

 

🧑🏻‍💻 4. Customizing the Design

If you want to change the indicator's color to match your brand, use the indicator parameter.


PullToRefreshBox(
    isRefreshing = isRefreshing,
    onRefresh = { /* ... */ },
    indicator = {
        PullToRefreshDefaults.Indicator(
            state = it,
            isRefreshing = isRefreshing,
            containerColor = Color.DarkGray, // Background color
            color = Color.Cyan              // Progress spinner color
        )
    }
) {
    // ...
}

 

🧑🏻‍💻 Conclusion: Simplified Refresh Logic

With the arrival of PullToRefreshBox, implementing this common UI pattern has never been easier.

  • Use Material 3 1.3.0+.
  • Pass the state (isRefreshing).
  • Handle the logic in onRefresh.

That’s it! You now have a modern, native-feeling refresh experience.