2025年2月15日土曜日

Jetpack ComposeでのLaunchedEffectとwhileループの動作について

基本的な実装と動作

以下のようなコードでは、アプリ(Activity)がpause中でもwhileループは自動的には停止しません。

LaunchedEffect(key1 = Unit) {
     while (true) {
         indicatorAlpha.floatValue = 1f
         delay(1000L)
         indicatorAlpha.floatValue = 0f
         delay(1000L)
     }
}

なぜかというと、

  • LaunchedEffectはComposable関数のライフサイクルに連動するが、Activityのライフサイクルとは直接連動しない
  • whileループ内のdelayはsuspend関数だが、コルーチン自体は終了しない
  • ActivityがonPause()に入ってもコルーチンは実行を継続する

という理由があるためです。

ライフサイクルに応じた制御の実装

Activityのpause中にループを停止するには以下のような実装が必要です。

var isRunning by remember { mutableStateOf(true) }

LaunchedEffect(isRunning) {
    while (isRunning) {
        indicatorAlpha.floatValue = 1f
        delay(1000L)
        indicatorAlpha.floatValue = 0f
        delay(1000L)
    }
}

DisposableEffect(key1 = LocalLifecycleOwner.current) {
    val observer = LifecycleEventObserver { _, event ->
        if (event == Lifecycle.Event.ON_PAUSE) {
            isRunning = false
        } else if (event == Lifecycle.Event.ON_RESUME) {
            isRunning = true
        }
    }
    LocalLifecycleOwner.current.lifecycle.addObserver(observer)
    onDispose {
        LocalLifecycleOwner.current.lifecycle.removeObserver(observer)
    }
}

Decomposeを使用したMultiplatformアプリでの実装

Decomposeを使用している場合は、Decomposeのライフサイクルを使用することができます。

class YourComponent(
    componentContext: ComponentContext
) : ComponentContext by componentContext {

    private val _isRunning = MutableValue(true)
    val isRunning: Value<Boolean> = _isRunning

    init {
        lifecycle.doOnPause { _isRunning.value = false }
        lifecycle.doOnResume { _isRunning.value = true }
    }
}

まとめ

Jetpack ComposeでLaunchedEffectとwhileループを使用する際は、ActivityやDecomposeのライフサイクルを使って適切なタイミングでループを停止・再開することができます。 これにより、アプリがバックグラウンドに移行した際に不要な処理が実行され続けることを防いでバッテリー消費を抑えることができます。 回しっぱなしのループを作らないように心がけましょう。

2025年2月7日金曜日

MarkdownファイルをHTMLに変換してBloggerに掲載する方法

このブログではMarkdownファイルをローカルでHTMLに変換してBloggerにアップしています。その手順を説明します。

1. Pandocのインストール

私はMac miniをメインで使っているのでMacで作業する場合について説明します。 まずMarkdownファイルを変換するためのPandocをインストールします。 macOSではHomebrewを使用してPandocをインストールできます。

brew install pandoc

または公式サイトからダウンロードできます。

Pandoc 公式サイト

2. MarkdownをHTMLに変換する

最も基本的なコマンドでMarkdownをHTMLに変換できます。

pandoc -o output.html input.md

3. シンタックスハイライトを適用する

Bloggerでテーマの「HTMLを編集」を選択してテーマのHTMLを編集します。

<head> に以下を追加します。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/kotlin.min.js"></script>
<script>hljs.highlightAll();</script>

4. 記事をコピー

作成したHTMLファイルをエディタで開き、ファイルの内容をBloggerのHTMLビューへコピペします。最初のh1タグの内容はタイトルなので本文ではなくタイトルの方にコピペします(タグは不要)。

これでMarkdownファイルからBloggerの記事を作成することができます。

2025年2月1日土曜日

KotlinのElvis演算子でUnitはどう評価される?

概要

KotlinのElvis演算子ではUnit型はどのように扱われるでしょうか? この記事では具体的な挙動について説明します。

Unitとは

KotlinではUnitは値を返さない関数の戻り値として使用されます。Kotlinの型システムではUnitは非nullなので、nullとして扱われることはありません。

Elvis演算子の動作

Elvis演算子(?:)は、左辺の値がnullの場合に右辺の値を返します。しかし、Unitは常に非nullなため、Elvis演算子の左辺にUnitを返す関数を指定しても右辺の値は評価されません。

具体例

以下のコードの場合、

fun printMessage() {
    println("Hello, World!")
}

fun main() {
    val result = printMessage() ?: "Default Message"
    println(result)
}

printMessage()関数は常にUnitを返します。Elvis演算子の左辺はUnit(非null)であるため右辺の"Default Message"は評価されず、resultにはUnitが代入されます。その結果、println(result)kotlin.Unitと出力されます。

まとめ

  • UnitはKotlinの型システム上、常に非null
  • Elvis演算子は左辺がnullのときにのみ右辺を評価する
  • UnitをElvis演算子の左辺に置くと、右辺の評価は行われない

ちょっとあやふやなままElvis演算子を使っていたこともありましたが、Unitは値であると考えればすっきり理解できます。