この記事は Beer Advent Calendar 2022 - Adventar の 9 日目の記事です。
これがうめぇんだ!
DEEDS XPA
しっかりとした苦味がありつつ、後味スッキリですいすい飲めます。 最高です。
DEEDS DOUBLE TIME
さっきの XPA の苦味と後味にくわえ、HAZY PALE らしいフルーティーさが合わさった感じですいすい飲めます。 最高です。
この記事は Beer Advent Calendar 2022 - Adventar の 9 日目の記事です。
これがうめぇんだ!
しっかりとした苦味がありつつ、後味スッキリですいすい飲めます。 最高です。
さっきの XPA の苦味と後味にくわえ、HAZY PALE らしいフルーティーさが合わさった感じですいすい飲めます。 最高です。
次のように定義されたコンストラクタを名前付き引数を使って呼び出したい。
// 定義 data class Sample( val hoge: String, val fuga: String, val moga: String, val piyo: String, val foo: String, val bar: String, val baz: String, val qux: String, val quux: String, ) // 次のコードを補完機能で自動生成したい val sample = Sample( hoge = "", fuga = "", moga = "", piyo = "", foo = "", bar = "", baz = "", qux = "", quux = "", )
IntelliJ の標準のコード補完機能では、コンストラクタや関数の呼び出しについて名前付き引数のコード補完ができない。ヒントは表示されるが補完まではしてくれないので、すべて手入力しないといけない。
プラグインを使いましょう。
このプラグインを使うと、コンストラクタや関数の呼び出し箇所で Fill Function
という補完メニューが出てくるようになり、すべての名前付き引数を自動で作ってくれます。
YouTrack にも Issue がたっていますが、今のところは上記の Plugin を使うのが一番はやいです。
この記事は つけ麺 Advent Calendar 2022 - Adventar の4日目の記事です
これがうめぇんだ!
BottomSheetScaffold
を使って BottomSheet を作るとき、BottomSheet の中身は BottomSheetScaffold
の引数にある sheetContent
で作っていきます。
BottomSheetScaffold( sheetContent = { /* BottomSheet の中身 */ } ) { paddingValues -> /* 画面の中身 */ }
この画面において BottomSheet の表示に複数の種類がある場合を考えてみます。
種類に応じて sheetContent
を切り替えたいので、次のような enum による分岐をしてみます。
今回は 2 種類の BottomSheet を一つの画面で表示する想定で BottomSheetType
という enum を定義して sheetContent
内部で表示を切り替えます。
このとき、BottomSheet には開いた状態と閉じた状態の 2 種類があることを考慮に入れ、閉じた場合に sheetContent
を表示しないようにするためなにもないことを示す値も合わせて定義してみます。
enum class BottomSheetType { NONE, FOO, BAR } var sheetType by remember { mutableStateOf(BottomSheetType.NONE) } val bottomSheetState = rememberBottomSheetState( initialValue = BottomSheetValue.Collapsed, ) val scaffoldState = rememberBottomSheetScaffoldState( bottomSheetState = bottomSheetState, ) BottomSheetScaffold( scaffoldState = scaffoldState, sheetPeekHeight = 0.dp, sheetContent = { when (sheetType) { BottomSheetType.NONE -> { // 閉じた状態を表現したい。何も表示するものはないので空のまま。 } BottomSheetType.FOO -> { Text(text = "foo") } BottomSheetType.BAR -> { Text(text = "bar") } } } )
このようなコードで sheetContent
を作る画面を動かすと BottomSheetState
のもつ状態がおかしくなってしまいます。具体的には、rememberBottomSheetState
で初期値を Collapsed
にしていても、Composition がおわると Expanded
な状態であると判定されてしまいます。
またこのような sheetContent
の作り方を実装した上で BottomSheet を展開しようと次のようなコードを呼び出すとアプリがクラッシュします。
// Jetpack Compose 1.1.0 でクラッシュするコード (1.2.0 ではクラッシュしない) coroutineScope.launch { bottomSheetState.expand() } // Jetpack Compose 1.2.0 でクラッシュするコード (1.1.0 ではクラッシュしない) coroutineScope.launch { bottomSheetState.animateTo(BottomSheetValue.Collapsed) }
クラッシュ時の例外は次の通りで、展開時のアニメーションを実行しようとしたとき、アニメーションが終了する位置が指定できていないことでクラッシュしていることがわかります。
java.lang.IllegalArgumentException: The target value must have an associated anchor.
そもそも BottomSheet を展開していないのに BottomSheetState
は Expanded
になってしまうことが良くないのですが、これは sheetContent
で何も Composable を呼ばないケースが不具合を引き起こしています。
このため、次のように何も表示しないケースで Spacer(modifier = Modifier.height(1.dp))
を差し込むか、when
全体を Box
で囲むか、そもそも enum の定義から表示しないパターンの定義を消すかのいずれかで、常に sheetContent
が何かしらの Composable を呼び出すようにする必要があります。
// 小さな Spacer を差し込むパターン BottomSheetScaffold( scaffoldState = scaffoldState, sheetPeekHeight = 0.dp, sheetContent = { when (sheetType) { BottomSheetType.NONE -> { Spacer(modifier = Modifier.height(1.dp)) } BottomSheetType.FOO -> { Text(text = "foo") } BottomSheetType.BAR -> { Text(text = "bar") } } } ) // when 全体を Box で囲むパターン。BottomSheetType.NONE でも最低 1dp は確保する BottomSheetScaffold( scaffoldState = scaffoldState, sheetPeekHeight = 0.dp, sheetContent = { // 最低 1dp の高さは確保 Box(modifier = Modifier.requiredHeightIn(min = 1.dp)) { when (sheetType) { BottomSheetType.NONE -> { // 閉じた状態を表現したい。何も表示するものはないので空のまま。 } BottomSheetType.FOO -> { Text(text = "foo") } BottomSheetType.BAR -> { Text(text = "bar") } } } } ) // enum の定義を削除するパターン BottomSheetScaffold( scaffoldState = scaffoldState, sheetPeekHeight = 0.dp, sheetContent = { when (sheetType) { BottomSheetType.FOO -> { Text(text = "foo") } BottomSheetType.BAR -> { Text(text = "bar") } } } )
Jetpack Compose 1.2.0 (正確には Jetpack Compose 1.2.0-alpha07 以後) から Scaffold
の content で Scaffold
から渡される PaddingValues
を使わないと Lint エラーとなります。
次の例は Lint のエラーとなります。Scaffold
の content は content: @Composable (PaddingValues) -> Unit
と定義してあり、Scaffold
側から PaddingValues
を渡すように設計してあります。
この PaddingValues は Jetpack Compose 1.1.x までは使わなくても特に Lint に怒られることはありませんでしたが、Scaffold
が content を呼び出す前に内部で PaddingValues
を計算しているため、
使わないとレイアウトが崩れる原因となる場合がありました。特に PaddingValues
の計算には bottomBar の高さが計算に入っているようです。
このため、Jetpack Compose 1.2.0 からは Lint のエラーが出るようになりました。
@Composable fun SampleScreen { Scaffold( scaffoldState = rememberScaffoldState(), topBar = { /* compose TopAppBar... */ }, ) { // このラムダに渡される PaddingValues を無視して画面を構成するとエラーになる ConstraintLayout { /* compose screen... */ } } }
Scaffold
の content で使う最もトップレベルの Composable に PaddingValues
を設定します。
@Composable fun SampleScreen { Scaffold( scaffoldState = rememberScaffoldState(), topBar = { /* compose TopAppBar... */ }, ) { innerPadding -> // Scaffold 直下の Composable に innerPadding を設定する ConstraintLayout( modifier = Modifier.padding(innerPadding), ) { /* compose screen... */ } } }
DroidKaigi: Weekend Chat は Mac 上で Discord をつないで @mhidaka さんと話しているのを OBS に流して YouTube Live 配信にのせています。
Windows であれば音声キャプチャはそれほど難しくありませんが、Mac でやるとなるとソフトウェアないしハードウェアを揃えていかないと配信環境が整いません。 特に Discord からの音声を配信にのせたり、BGM として使う音楽を配信にのせたりする場合はこれらのソフトからのオーディオ出力をキャプチャする手段が Mac OS 標準にも OBS にもないため、別途キャプチャ用のものが必要です。
そこで DroidKaigi: Weekend Chat 開始当初から使っているソフトとして Loopback が役立ちます。 Loopback は仮想オーディオ出力デバイスを作成し、その出力に対してどのソフトからのオーディオを入力するかをビジュアルで分かりやすく設定できます。
自分の場合、初回から長らくは Discord の音・Chrome の音(BGM 用・YouTube の BGM 素材動画から音を取り込んでいた)をソースとして OBS に流す仮想オーディオ出力デバイスを作成し、Discord から聞こえてくる @mhidaka さんの音と BGM を OBS に流しつつ、自分のマイク入力はそのまま Discord と OBS に流すようにしていました。
ただしこの設定だと、話し相手の mhidaka さんには自分の声しか聞こえないので BGM が入らず、mhidaka さん側にも BGM を流すためにわざわざ mhidaka さんに同じ BGM の再生ページを開いてもらって再生タイミングをせーので合わせる、という超絶運用でカバーをすることになってしまいました。
Loopback では、仮想オーディオ出力デバイスは複数作成可能です。 そこで、次のステップとして、Discord に流すための仮想オーディオ出力デバイスと OBS に流すための仮想オーディオ出力デバイスの 2 つを作成し、BGM の再生タイミングをせーので合わせることなく mhidaka さん側にも流せるようにしました。
まず OBS 用の仮想オーディオ出力デバイスは、Discord の音、BGM の音、追加で SE を鳴らすソフトの音を出力するように設定します。
次に Discord 用の仮想オーディオ出力デバイスでは、Discord のオーディオ入力をこの仮想オーディオ出力デバイスとするため、マイクの入力、BGM の音、SE を鳴らすソフトの音を出力するようにします。
これで OBS と Discord の両方に BGM を出力できるようになります。 ここで注意すべき点としては、Discord は独自にノイズキャンセルする仕組みを持っていて、ラジオ配信などで使う BGM の音量が小さいとノイズ判定されて mhidaka さん側では消えてしまうことに気をつけます。 ノイズ判定を回避して BGM を届けるには Discord の設定で Input Sensitivity を手動で設定し、閾値を下げておく必要があります。この閾値を下げても YouTube の配信には影響はありません。
このあとさらに YAMAHA AG06MK2 を購入しましたがその話はまた別記事で書こうと思います。
次のような記述で Android 用の Docker Image を利用すると、ジョブのステータスを表示する画面で You’re using a deprecated Docker convenience image. Upgrade to a next-gen Docker convenience image.
といったメッセージが表示されます。
executors: android: docker: - image: circleci/android:api-30
メッセージのリンク部分をたどると、新しい next-gen Docker convenience image へ置き換えるための手順が示されていますが、Android に関してはどの Docker Image を使うべきか明示されていません。
代わりに、この手順を示した記事のコメントにて新しい Docker Image の情報を記載したページのリンクが示されています。
Legacy Convenience Image Deprecation - #6 by zmarkan - Announcements - CircleCI Discuss
DockerHub で CircleCI が公開している Docker Image は api-30 を最後に更新が止まっていて、api-31 以上を含むイメージは提供されていません。 代わりに、DockerHub で cimg という別の organization (実体は CircleCI)が提供している android のイメージを利用します。
cimg で公開しているイメージはこれまでと Docker Image のタグの付け方が異なります。 これまでは API Level ごとにタグを付けていましたが、今後はビルドした日付ごとにタグが付与されます。 また Docker Image に含まれる Android SDK の内容にも違いがあり、最新の API Level から数えて直近 4 つ分の API Level のものが Docker Image に含まれています。
この他、node や ndk などを含むイメージもあり、それぞれタグの日付を示す部分の後ろに variant 名を付けています。
この記事をかいた時点では 2022.04 が最新イメージとなっているので、上記 YAML は次のように書き換えることになります。
executors: android: docker: - image: cimg/android:2022.04