WPP
vscode で unittest の coverage 計測するために task を使う

表題通りですみません。今、python でちょっとしたツールを作っているのですが、ユニットテストも書いています。世間では pytest が人気みたいですが、何もインストールしなくて良いという安直な理由で unittest を使っています。

vscode では unittest も認識してくれていい感じで discover でテストをたどってくれているようです。

ただカバレッジは計測できないようで、愚直にターミナルから実行するのも良いのですが、coverage のコマンドをいつも忘れてしまいます。

そこで、vscode の task 機能? で予めタスクを登録することにしました。

task 機能の説明は参考ページが詳しいです。が手順だけリストアップすると

タスクを登録するには

  1. Ctrl +p でコマンドパレット開く
  2. tasks: configure task
  3. 何か適当に選ぶと tasks.json が開く
  4. 追加したいタスクを記述する

こんな感じです。今回は、以下のコマンドを記述しました。

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "echo",
            "type": "shell",
            "command": "echo Hello"
        },
        //  ここから追加
       //  カバレッジの計測
        {
            "label": "coverage",
            "type": "shell",
            "command": "coverage run --source . -m unittest discover",
            "options": {
                "cwd": "${workspaceFolder}"
            }
        },
        // lcov 形式に変換 (Code Coverage という vscode extension で使用するため)
        {
            "label": "coverage lcov",
            "type": "shell",
            "command": "coverage lcov",
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "dependsOn": "coverage"
        }
    ]
}

今回は、計測のためのタスクと lcov に変換する 2つのタスクを登録しました。

タスクの実行

  1. Ctrl +p でコマンドパレット開く
  2. tasks: run task
  3. 実行したいタスクを選ぶ

これで面倒なく実行できる。

が、考えてみると makefile の方が楽な気もする。というのも tasks.json はワークスペースの .vscode に作成されるので個人的には git に入れず削除することが多い。悩ましい。

参考

【初心者でも分かる】VSCodeのtasks.jsonでタスク自動化する方法を解説 | Inno-Tech-Life

edk2 参考サイト

引き続き Mikan 本をやっているわけですが、edk2 のいいサンプルを見つけたのでメモ。

edk2 のセットアップの方法を解説したページは多くありますが、でどう書けばいいのよっていうサンプルはあまり見つかりません。

edk2 は、UEFI のための開発キットという位置づけなのですが、そのドキュメントには edk2 自身の使い方しか記述していません。UEFI は公式を見てねというスタンスになっているようです。

参考

Kostr/UEFI-Lessons: Lessons to get to know UEFI programming in Linux with the help of EDKII
ここのチュートリアルはわかりやすい

Specifications | Unified Extensible Firmware Interface Forum
UEFI 本家、仕様書をダウンロードできる。

FreeDict で英和、和英をターミナルで辞書引き

ふと英和辞書を引きたく状況になって、ちょっとブラウザで検索するの面倒と思い調べてみる。

どうやら FreeDict ってプロジェクトがあってそこの辞書を使えるらしい。(Ubuntu系のリポジトリには含まれると思う。)

$ sudo apt install dictd dict

; 英和辞書と和英辞書をインストール
$ sudo apt install dict-freedict-eng-jpn dict-freedict-jpn-eng

$ dict はてな
1 definition found

From Japanese-English FreeDict Dictionary ver. 0.1 [fd-jpn-eng]:

  はて /hˈäte̞/, はてな /hätˈe̞nä/
  1. (interjection (kandoushi))
  Dear me!, Good gracious!
  2. well ... (used before sentences expressing a doubt), let me see, now ...


; gui ツール
$ sudo apt install goldendict

ネット上の解説では dictd なしでも行けるっぽいことを見つけられるが、挙動の遅さからどうもインターネット上の辞書を検索しているっぽい。dictd をインストールするとローカルの辞書を引いてくるようだ。

GUI ツールは色々あるが goldendict がいいかもしれない。goldendict-ng というのもあって見た目はいい感じだが例題の 「はてな」が見つからない事例があった。一方の goldendict ではきちんと検索できたので一旦はこちらを使用することにしよう。

goldendict では検索結果を Favorites に保存できるのでそこが採用ポイント。

参考

dictd – ArchWiki
dict と dictd の使い方などがのっている。

Linux のコマンドラインでオフライン英和辞書 #Linux – Qiita
GENE95 という辞書テキストを shell から引く方法を解説している。

Documentation — FreeDict
FreeDictプロジェクトのサイト

Mikan 本の環境構築

先日の edk2 のエントリもそうだが、Mikan本 を今さらやっているところです。

本の通りに環境構築すれば何も難しいことはないのですが、フォルダ構成を好みな状態に変更するために行ったよもやまの記録です。。

環境構築で躓いたので3週間位経っているのに未だ 3 日目、4 日目あたりをうろうろしているがようやくスピードアップできそうだ。

Mikan 本からの変更点

  • edk2 は最新版(詳細は、前回のエントリ)
  • edk2 フォルダの配置先
  • day01 とかの日付で作業フォルダを区切っていく
  • devenv フォルダのスクリプトを makefile に直接書いていく

Mikan 本では、edk2 を ホームディレクトリ(~) に置いているのですが、自分としてはあまり好みでないのでプロジェクトフォルダ内に配置するようにした。

というのも、 github から取ってきた os のソースファイルは checkout して作業時点の日付を切り替えるようになっている。このスタイルだととても変更作業がやりにくいのでローカルにスナップショットを残す形にしようと思う。

前回(前日)分の作業内容は残して参照しながら進めたいので、日付で作業フォルダを区切ってその中からビルドできるようにしていく。それにともなって MikanLoader も日付フォルダの中に入れ込むことにした。

devenv フォルダのスクリプトの分割のしかたが自分にはどうしてもすっと入ってこないので、いっそ makefile に直接書いていくことにした。

まとめるとプロジェクトフォルダはこんなイメージになる。

  • [プロジェクトフォルダのトップ]
    • env フォルダ ひとまず、 .fd だけ置いている。
    • edk2 フォルダ
    • day03 のような日付フォルダ
      • MikanLoader03Pkg フォルダ
      • … その他

ちょうど kernel フォルダが新たに登場したのでそのうち追加しようと思う。後ろの章に進むとフォントファイルや画像リソース、newLib が追加されるようだが、それはまたその時考える。ことにする。

MikanLoaderPkg

Mikan 本では、UEFI で動く bootloader を MikanLoader と呼んでいるが、本の中では、プロジェクトフォルダの MikanLoaderPkg フォルダを edk2 フォルダ内にリンクすることで edk2 のビルドを実行するようにしている。

今回は本と異なり、day03 等の日付名のフォルダで作業をしたいので MikanLoader03Pkg のように日付の数字を埋め込んで名前の衝突を回避することにした。

それに伴い、 Mikanloarder の MikanLoader.dsc を書き換える必要がある。

6 行目: ビルド出力先を指定し直している。
23 行目: 最新の edk2 に対応するために追加。
26 行目: 使用する Loader.inf を指定している。

[Defines]
PLATFORM_NAME           = MikanLoaderPkg
PLATFORM_GUID           = 1b6bbd8c-686c-11ef-b861-b30cfa469952
PLATFORM_VERSION        = 0.2
DSC_SPECIFICATION       = 0x00010005
OUTPUT_DIRECTORY        = Build/MikanLoader03$(ARCH)
SUPPORTED_ARCHITECTURES = X64
BUILD_TARGETS           = DEBUG|RELEASE|NOOPT

[LibraryClasses]
UefiApplicationEntryPoint|MdePkg/Library/UefiApplicationEntryPoint/UefiApplicationEntryPoint.inf
UefiLib|MdePkg/Library/UefiLib/UefiLib.inf

BaseLib|MdePkg/Library/BaseLib/BaseLib.inf
BaseMemoryLib|MdePkg/Library/BaseMemoryLib/BaseMemoryLib.inf
DebugLib|MdePkg/Library/BaseDebugLibNull/BaseDebugLibNull.inf
DevicePathLib|MdePkg/Library/UefiDevicePathLib/UefiDevicePathLib.inf
MemoryAllocationLib|MdePkg/Library/UefiMemoryAllocationLib/UefiMemoryAllocationLib.inf
PcdLib|MdePkg/Library/BasePcdLibNull/BasePcdLibNull.inf
PrintLib|MdePkg/Library/BasePrintLib/BasePrintLib.inf
UefiBootServicesTableLib|MdePkg/Library/UefiBootServicesTableLib/UefiBootServicesTableLib.inf
UefiRuntimeServicesTableLib|MdePkg/Library/UefiRuntimeServicesTableLib/UefiRuntimeServicesTableLib.inf
RegisterFilterLib|MdePkg/Library/RegisterFilterLibNull/RegisterFilterLibNull.inf

[Components]
MikanLoader03Pkg/Loader.inf

[Components] 節は注意が必要だ。どうも Components で指定されたものがパッケージに含まれるようなしくみらしく、MikanLoader.dsc が他のフォルダの Loader.inf を読み込んでもビルドは問題なく通る。
この場合だとビルドされるソースは他のフォルダのものが MikanLoader03Pkg として生成される。

はじめこのことに気づかず、もともの MikanLoaderPkg の挙動になってしまい原因究明に時間がかかった。

また、Loader.inf も最新の edks2 を使うために変更している。RegisterFilterLib が必要なので追加した。

[Defines]
INF_VERSION     = 0x00010006
BASE_NAME       = Loader
FILE_GUID       = c9d0d202-71e9-11e8-9e52-cfbfd0063fbf
MODULE_TYPE     = UEFI_APPLICATION
VERSION_STRING  = 0.2
ENTRY_POINT     = UefiMain

# VALID_ARCHITECTURES = X64

[Sources]
Main.c

[Packages]
MdePkg/MdePkg.dec

[LibraryClasses]
UefiLib
UefiApplicationEntryPoint
RegisterFilterLib

[Guids]
gEfiFileInfoGuid

[Protocols]
gEfiLoadedImageProtocolGuid
gEfiLoadFileProtocolGuid
gEfiSimpleFileSystemProtocolGuid

makefile

本で進められる 環境では、 devenv フォルダに必要なスクリプトが全部入っている形になっている。が、makefile に書き直している。読み取って書き直す時間はかかるが、この方が自分には何をやっているか理解できるのであえてそうしている。

3日目の時点で使用できる makefile はこんな感じになる。

PWD 		:=$(shell pwd)
MNT_POINT	:=mnt
DISK_IMG	    :=disk.img
EDK_DIR		:=../edk2
EDK_OUT		:=Build/MikanLoader03X64/DEBUG_CLANGDWARF/X64/Loader.efi
ENV_DIR		:=../env  # 本でいうところの devenv フォルダ、ここに .fd がある。

CXXFLAGS	:=-O2 -Wall -g --target=x86_64 -ffreestanding -mno-red-zone -fno-exceptions -std=c++17
LDFLAGS		:=--entry KernelMain -z norelro --image-base 0x100000 --static


.PHONY: help
help:
	@echo "avilable targets"
	@echo "loader: build bootloader with edk2"
	@echo "kernel: build kernel"
	@echo "image: build bootddisk image"
	@echo "run: boot diskimage on QEMU"
	@echo "help: display this message"


# source edk2/edk-setup.sh はエラーになるので、予め実行する必要があるかも。
# ここの挙動はいまいちよくわかっていない。
.PHONY: loader
loader:
	ln -sf $(PWD)/MikanLoader03Pkg ../edk2
	cd $(EDK_DIR) && \
	# source edksetup.sh && \
	build -p MikanLoader03Pkg/MikanLoaderPkg.dsc && \
	cd $(PWD)

.PHONY: kernel
kernel:
	@echo "building kernel..."
	clang++ $(CXXFLAGS) -c main.cpp 
	ld.lld $(LDFLAGS) -o kernel.elf main.o

.PHONY: image
image: 
	mkdir -p $(MNT_POINT)
	sudo umount $(MNT_POINT) || :
	qemu-img create -f raw $(DISK_IMG) 200M
	mkfs.fat -n MIKAN -s 2 -f 2 -R 32 -F 32 $(DISK_IMG)
	sudo mount -o loop $(DISK_IMG) $(MNT_POINT)
	sudo mkdir -p $(MNT_POINT)/EFI/BOOT
	sudo cp $(EDK_DIR)/$(EDK_OUT) $(MNT_POINT)/EFI/BOOT/BOOTX64.EFI
	sudo cp kernel.elf $(MNT_POINT)/
	sleep 0.5
	# sudo umount $(MNT_POINT)


.PHONY: run
run:
	@echo "launch bootdisk on QEMU!!"
	qemu-system-x86_64 -m 1G \
	-drive if=pflash,format=raw,readonly=on,file=$(ENV_DIR)/OVMF_CODE.fd \
	-drive if=pflash,format=raw,file=$(ENV_DIR)/OVMF_VARS.fd \
	-drive if=ide,index=0,media=disk,format=raw,file=$(DISK_IMG) \
	-device nec-usb-xhci,id=xhci \
	-device usb-mouse -device usb-kbd \
	-monitor stdio

.PHONY: clean
clean:
	@echo "do cleaning"
	rm kernel.elf
	rm -rf $(EDK_DIR)/Build/MikanLoader03X64

使用するには、ひとつずつ手で実行していく必要がある。

; boot loader のビルド
$ make loader

;  kernel のビルド
$ make kernel

; イメージファイルの生成
$ make image

; qemu で実行
$ make run

この makefile は全然だめだめなのはわかっているが、ここからすこしずつ直していこうと思う。

ぼちぼち続けていきます。

edk2 の Emulator を動かす

だいぶ前に買った Mikan 本の OS開発をするにあたり、edk2 環境を作った。 とりあえずエミュレータを動かしてみた。本と異なり edk2 は最新のものでやってみることにした。

2025-1-28 追記: BaseTools をビルドする必要があったのでそれを追加。

環境
OS: Liux Mint 22 (upstream: Ubuntu24.02)
GCC: 13.2.0
edk2 は b437b5C (2024-9-7にこの記事は作成してます。)

; インストールとモジュールのアップデート
$ git clone https://github.com/tianocore/edk2
$ cd edk2
$ git submodule update --init --recursive

; エミュレータを動かすには lixb11-dev が必要らしい
 $ sudo apt install libx11-dev

; ビルド
$ source edksetup.sh

; まずは BaseTools をビルド
$ make -C BaseTools

; エミュレータビルド
$ build -p EmluatorPkg/EmulatorPkg.dsc  -t GCC -a X64 

; エミュレータの実行、Host があるディレクトリで実行する必要がある。oo
$ cd Build/EmulatorX64/DEBUG_GCC5/X64/
$ ./Host

build 実行時のパラメータは edk2/Config フォルダの target.txt に記述してもいい。

ACTIVE_PLATFORM    = EmulatorPkg/EmulatorPkg.dsc
TARGET_ARCH           = X64
TOOL_CHAIN_TAG    = GCC

TARGET_ARCH や TOOL_CHAIN_TAG は Conf デイレクトリの tools_def.txt に例があるのでそこから取ってくる。 以前までは TOOL_CHAIN_TAG に GCC5 を指定したようだが deprecate なので GCC となるらしい。(EmulatorPkg の Readme.md にあるのだが、GCC でないとビルドできないらしい。clang で頑張っても無駄なので素直に諦める。)

実行するには、Build ディレクトリに移動する必要があるようだ。

公式のやり方は、 build.sh run とかで実行できるとあるがオレカンでは何故か実行できないようです。

$ cd Build/EmulatorX64/DEBUG_GCC5/X64
$ ./Host

実行するとコンソールの画面出力がおかしくなるのでそこは注意。

これから Mikan 本をなぞっていくが TOOL_CHAIN_TAG CLANG8 から CLANGDWARF に変更するといいらしい。

参考

EmulatorPkg · tianocore/tianocore.github.io Wiki
– ここに、 Ubuntu の依存ライブラリの記述あり、 EmulatorPkg/build.sh run では動作しなかった。

edk2/EmulatorPkg at master · tianocore/edk2
– ここの起動方法に従うとと動作した。

EDK II (最新版) のツールチェーンに大幅な仕様変更が入っています · Issue #147 · uchan-nos/os-from-zero
– ここに Mikan 本での Toolchain についてのイシューレポートがある。

Android color int の色の並び

超小ネタです。

アンドロイドの UI で Color( 0xd0203040 ) となっていた場合の色の並び

ARGB の順に並んでいる A は透明度で基本 FF でもかまわないと思う。

apt-key が deprecate と怒られたので

sudo apt update を実行すると以下のようなメッセージで Wine のキーが trusted.gpg にあるって理由で警告がでた。昨日 Linux Mint 22 にアップグレードしたのが原因かもしれない。

W: XXXXttps: Key is stored in legacy trusted.gpg keyring (/etc/apt/trusted.gpg), see the DEPRECATION section in apt-key(8) for details.

この辺の操作はいつも適当にファイルをいじって回避しているので少しだけ調べた。
参考ページの操作をするとキーが trusted.gpg ファイルから trusted.gpg.d フォルダの個別ファイルに移せるってことらしい。

$ apt-key list
Warning: apt-key is deprecated. Manage keyring files in trusted.gpg.d instead (see apt-key(8)).
/etc/apt/trusted.gpg
-------------------------------------------
pub   rsa3072 2018-12-10 [SC]
      D43F 6401 4536 9C51 D786  DDEA 76F1 A20F F987 672F
uid           [ unknown] WineHQ packages <wine-devel@winehq.org>
sub   rsa3072 2018-12-10 [E]

#trasted.gpg.d 内のファイルについて続く

例として wine のキーを移動してみる

$ sudo apt-key export  F987672F | sudo gpg --dearmour -o /etc/apt/trusted.gpg.d/docker.gpg

これで警告がでなくなった。

参考

Fixing “Key is stored in legacy trusted.gpg keyring” Issue in Ubuntu

Linux Mint 22 にアップグレード

21.3 から 22 にアップグレードした。

やり方は簡単、mintupgrade というツールをインストールして起動してやるだけ。

$ sudo apt install mintupgrade
$ mintupgrade

注意しないといけないのは、【Timeshift】のスナップショットが一つもないとアップグレードのプロセスが途中で中断してしまう。

TimeShift の注意点として保存先のパーティションに timeshift フォルダが作成される。その時バックアップ対象から timeshift フォルダを除外しないとスナップショットの作成が空き容量が 0 になるまで永遠に終わらないので注意すること。
例えば / のあるパーティションに timeshift フォルダが作成されている時、 / 全体をバックアップする設定の場合に発生する。この場合は、前述の通り /timeshift フォルダをバックアップ対象から除外すること。

順調に行けば1時間から2時間くらい放っておけば、あっけなくアップグレードは完了する。

公式のフォーラム見てみると、どうも Linux Mint は LM と略すらしい。 Linux Mint で検索かけてもいまいち Mint 特有の結果に絞り込むことはできないことが多いのので LM22 とか入れてみるといいかもしれない。

pip search の代替

pip search が廃止されて結構長いこと時間が経っているが、いい感じの代替が見つかったのでメモする。

pypi に pypi-search というのが登録されていたので試してみた。

2024-12-24 追記: pypi-search で検索できなくなった。

2024-9-3 追記: pipx でインストールすることにした

pypi-search インストール

// pipenv 内で実行していたので pipx に変更する
//$ pip install pypi-search
$ pipx install pypi-search
$ pipy-search mqtt
[INFO] :: Searching for package `mqqt`...
[INFO] :: Could not find package `mqqt`
[INFO] :: Searching for other packages that match that query...

Here are some packages that match `mqqt`:
=========================================================================================================================================================================================================================

Name         : dyrkdevice
Version      : 0.1.0
Release date : Oct 24, 2021
Description  : Library for implementing a dyrk device

$ $ pypi-search -help
usage: pypi-search [-h] [-d] [--version] [-o] search

Search for PyPi packages

positional arguments:
  search             Package to show information on

options:
  -h, --help         show this help message and exit
  -d, --description  Show package description
  --version          show program's version number and exit
  -o, --open         Open homepage in browser

これでいちいち pypi をブラウザで開く必要がなくなりそうで助かるなと。

Ubuntu 24.04 かそれに含まれる Python 3.12.3 は pip が venv の中でしか実行できないらしい。先日インストールできたのははからずも、venv 内で作業していたため後日 pypi-search を実行できないことに気がついた。pipx なら実行できるらしい。

python 製のコマンドツールはちょくちょくあるので pipx でインストールするようにしよう。恐らく pip –user と同じようなことをしているのだと思われる。

pypi-search のエラー 12/24 追記

今日の時点で実行するとこんなエラーが起きている

$ pypi-search  numpy
[INFO] :: Searching for package `numpy`...
Traceback (most recent call last):
  File "/home/mnishi/.local/bin/pypi-search", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/mnishi/.local/share/pipx/venvs/pypi-search/lib/python3.12/site-packages/pypi_search/main.py", line 35, in main
    version_info = pypi.get_version_info()
                   ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/mnishi/.local/share/pipx/venvs/pypi-search/lib/python3.12/site-packages/pypi_search/utils.py", line 41, in get_version_info
    version_no = package_name_header.text.split()[1]
                 ^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'text'

ソース見てみると .local/share/pipx/venvs/pypi-search/lib/python3.12/site-packages/pypi-search の utils.py で URL を開いている。(package_name は、検索したい文字列)

ここで .text が返ってこないらしい。

        self.response: Optional[requests.Response] = requests.get(
            f'http://pypi.org/project/{self.package_name}'
        )
        try:

試しに、wget するとエラーを意味する html が返ってきてよく見ると javascript が動かないとだめってことらしい。恐らく以前からのスクレーピング対策が強化されたようだ。

それもあるのか、pypi-search の Github に行くとアーカイブになっていた。javascript 対応するのが面倒なのでもうやめることにしたのだと思う。

うーん、困った。

[参考]

【備忘録】Ubuntu 24.04 で Python3.12 の Pip を利用する #Linux – Qiita

俺流!PEP668とうまくやっていく方法 | スクエニ ITエンジニア ブログ

Android でサービスと通信する-Channel編

サービスと通信するサンプル 3 部作の追加編です。で Flow (Channel) を使用してみます。

3 部作はこちら

  • ブロードキャストを送信する
  • LiveDataを使う
  • Messanger を使う
  • そもそも、なぜ上の3手法を試したのかというと、Claude か Gemini か ChatGTP に「サービスの状態をアクティビティに通知するにはどういう方法がある?」って聞いた結果、3 つの方法が回答にあったのがキッカケだった。

    でサンプルを探して作っていくうちに、Flow をイベントバスに使う方法もあるらしいということを探し当てたのでやってみる。

    MainActivity はこんな感じになりました。

    package red.txn.service_flow
    
    import android.content.ComponentName
    import android.content.Context
    import android.content.Intent
    import android.content.ServiceConnection
    import android.os.Bundle
    import android.os.IBinder
    import android.util.Log
    import androidx.activity.ComponentActivity
    import androidx.activity.compose.setContent
    import androidx.activity.enableEdgeToEdge
    import androidx.compose.foundation.layout.fillMaxSize
    import androidx.compose.foundation.layout.padding
    import androidx.compose.material3.Scaffold
    import androidx.compose.material3.Text
    import androidx.compose.runtime.Composable
    import androidx.compose.ui.Modifier
    import androidx.compose.ui.tooling.preview.Preview
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.launch
    import red.txn.service_flow.ui.theme.ServiceflowTheme
    
    class MainActivity : ComponentActivity() {
        companion object {
            const val TAG = "MainActivity"
        }
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            enableEdgeToEdge()
            setContent {
                ServiceflowTheme {
                    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                        Greeting(
                            name = "Android",
                            modifier = Modifier.padding(innerPadding)
                        )
                    }
                }
            }
    
            // launch service
            Intent(this, FlowService::class.java).also { intent ->
                startService(intent)
            }
            Log.d(TAG,"service launched")
    
            val scope = CoroutineScope(Dispatchers.IO)
            scope.launch {
                FlowService.BUS.collect {
                    Log.d(TAG, "Received data: $it")
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String, modifier: Modifier = Modifier) {
        Text(
            text = "Hello $name!",
            modifier = modifier
        )
    }
    
    @Preview(showBackground = true)
    @Composable
    fun GreetingPreview() {
        ServiceflowTheme {
            Greeting("Android")
        }
    }

    LiveData の observe が Flow の cllect に変わった感じで極端な変化はないですね。わかりやすい。

    サービスのインスタンスなしでアクセスできているのは BUS がサービスの中で companion ojbect として定義されているからです。(理解あってる?)

    package red.txn.service_flow
    
    import android.app.Service
    import android.content.Intent
    import android.os.Binder
    import android.os.IBinder
    import android.util.Log
    import kotlinx.coroutines.CoroutineScope
    import kotlinx.coroutines.Dispatchers
    import kotlinx.coroutines.channels.Channel
    import kotlinx.coroutines.flow.MutableStateFlow
    import kotlinx.coroutines.flow.asSharedFlow
    import kotlinx.coroutines.flow.receiveAsFlow
    import kotlinx.coroutines.launch
    
    class FlowService : Service() {
        companion object {
            val TAG = "FlowService"
            private var _BUS = Channel<String>()
            val BUS = _BUS.receiveAsFlow()
        }
    
        override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
            Log.d(TAG, "onBind")
            Thread {
                for (i in 1..2) {
                    Thread.sleep(2000)
                    updateData("データ$i")
                    Log.d(TAG, "データ$i")
                }
            }.start()
    
            return START_STICKY
        }
    
        override fun onBind(intent: Intent): IBinder? {
            return null
        }
    
        fun updateData(data: String) {
            val scope = CoroutineScope(Dispatchers.IO)
            scope.launch {
                _BUS.send(data)
            }
        }
    }

    BUS と _BUS は companion object から普通のメンバに変えてもいいかも。

    AIチャットボットの回答で漏れていた方法をたまたま見つけたわけですが、現時点では AI の回答が網羅性があり、正しい(妥当か)を判断するには読み取る人間側がそもそもその答え知らないといけない例として覚えておきたい。

    AIチャットボットに限らず検索の回答から漏れているものが認識されない、つまり存在しないことにされてしまうのはとても考えさせられると感じてしまう。

    参考

    LiveDataをFlowにリプレースしてみて得た知見(StateFlow、SharedFlow、Channel) #Android – Qiita