Diary

Diary

日々学んだことをアウトプットする場として初めてみました

Jetpack Compose で Material You を使う

今回は、Jetpack compose で Material You を使う方法についてメモしておきます。

なお、今回の内容は『Android DevSummit "Material You in Compose apps"』から学んだものとなります。

環境

- kotlinCompilerVersion '1.6'
- compose_ui_version = '1.3.0-beta03'
- androidx.compose.material3:material3:1.1.0-alpha02
- androidx.compose.material3:material3-window-size-class:1.1.0-alpha02

Material You

Material You とは、個々人の設定に合わせてアプリ内の色(や形?)をカスタマイズしてくれるような機能です。
こちらの機能は Android OS 12 (API31) で追加されました。

今回は、個人の背景設定の色に合わせてアプリ内で使用する色を変えてみました。

使用準備

最初にプロジェクトを作成するときに material3 を選択しなかった場合、アプリレベルの build.gradle に次のライブラリを追加します。

dependencies {
    ...
    implementation("androidx.compose.material3:material3:1.1.0-alpha02")
    implementation("androidx.compose.material3:material3-window-size-class:1.1.0-alpha02")
}

色をダイナミックに変化させていくには、dynamicDarkColorScheme 等を使います。
ここでは、ダークモードかそうじゃないかで切り替えたいため isSystemInDarkTheme を使っています。

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MaterialYouTest() {
    // API31 で追加された機能のため確認する。
    val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
    val dark = isSystemInDarkTheme()
    val colorScheme = if (dynamicColor) {
        if (dark) {
            dynamicDarkColorScheme(LocalContext.current)
        } else {
            dynamicLightColorScheme(LocalContext.current)
        }
    } else {
        // 普通のカラースキームを使う。
        if (dark) {
            darkColorScheme()
        } else {
            lightColorScheme()
        }
    }
    ...
}

使うとき

ここで定義した colorScheme を次のように使っていきます。

Box(
    modifier = Modifier
        .size(100.dp)
        .clip(CircleShape)
        .background(colorScheme.primary),
)

確認してみる

こちらのコードでどのように変化するかを確認してみました。

『Wallpaper & style > Basic colors』の色が反映されてるように見えます。

おわりに

普通のアプリでは UI 的に使いたい色がるため使う機会は少ないと思いますが、電卓などの汎用的なアプリではいい感じにユーザー毎のカスタマイズが可能になりそうな気がします!

ワイヤレスデバッグをステータスバーに表示する方法

開発者モードのオプションにあるワイヤレスデバッグを、ステータスバー(通知バー)に表示する方法をメモしておきます。

開発時、ステータスバーにデバッグオプションを表示させた方が便利なケースが多いです。

標準ではこちらに表示されておりません。

Settings から、『Quick settings developer tiles』のように検索します。
(日本語では『クイック設定開発者用タイル』)

この中から『Wireless debugging』を ON にします。

そうすると、ステータスバーに『Wireless debug』オプションの設定が表示されます。

Android: offline-first

Dev Summit: Create offline-first apps を試聴したのでそのメモです()。

repository 層の役割

リポジトリー層の役割として、少なくとも 2 つのデータソース(LocalDataSource と NetworkDataSource)からデータを取得することを考える。

この際、取得できるエンティティが異なるかもしれないが(AuthorEntityNetworkAuthorなど)、それを整形して統一して返してあげるのもData Layer の役割

  • Read は Flow を使って
  • Write は suspend fun で

ネットワークのモニター

  • ネットワーク接続がとれるまでキューに貯めておく必要がある!
    • 書き込みについて
  • ネットワークをモニターし、接続が取れたらキューからジョブを実行させる

LocalDataSource については、データの一貫性のために常に読み込みできることが大切!

失敗時にリトライが必要そうなら、再度キューに入れる作戦で!

Synchronization

ローカルデータとリモートデータを統一させること。

  • Pull-based
    • on demand で取得する
    • 実装が簡単
  • Push-based
    • データ使用量が最小ですむ
    • must be supported by the network.

おわりに

なんとなくやりたいことはわかったけど、実際に手動かしてみないと!

Jetpack Compose をデバッグする方法

今回は Android Studio の Layout Inspector Jetpack Compose のを使ってみていくことになります。

環境

- Android Studio: Android Studio Dolphin | 2021.3.1
- kotlinCompilerVersion '1.6'
- compose_ui_version = '1.2.1'

確認方法

右下の方に Layout Inspector と書かれたボタンがあるので押します。

中央を確認し、自アプリのプロセスが選択されてない時は、端末・アプリを選択します。

すると、下の写真のようなことが分かります。

  • 構成されている composal の情報
  • 何回 recomposition されたか
    • 何回スキップされたか

また属性値も確認できます。

これを使って詳細なサイズ調整や、無駄な recomposition を見つけてパフォーマンスを上げることができそうです!

ShellCheck から学ぶ良いスクリプトの書き方 〜SC2086(ダブルクォート)編〜

とりあえずは変数はダブルクォーテーションで囲もうってことなんですが、囲まないとどうなるか少し調べてみました。

[目次]

今回は簡単な部類である SC2086 から確認していきます。

ShellCheck のサイトや、ShellCheck の拡張機能の入った VSCode などで以下のようなコードを打ちます。

#!/bin/bash

d="true = true -o x"
if [ $d = "pien" ]; then
    echo "d is equal to pien"
fi

すると以下のようなエラーが表示されます。

$ shellcheck myscript

Line 4:
if [ $d = "pien" ]; then
     ^-- SC2086 (info): Double quote to prevent globbing and word splitting.

Did you mean: (apply this, apply all SC2086)
if [ "$d" = "pien" ]; then

つまりは『ダブルクォーテーション(")』で囲めば解決するんですが、今回は囲まなかったらどうなるか・なぜこれが良くないのかについて少し調べてみました。

Double quote to prevent globbing and word splitting.

どうやら Double quote は何かを防いでくれるようです。

一つずつ見ていきます。

globbing

まず glob についてはマニュアルの 3.5.8 Filename Expansion にちょっと書いてありますが、要は『bash 用に定義された特殊なパターン』と思っていいんじゃないでしょうか。

// こんなやつ
$ ls *.py

『ダブルクォーテーションで囲むと、この glob 展開を禁止するよ!』と言ってるんですね。

具体例
例えば次のような例を考えます。

# なんかの演算の結果、ファイル名に * がきてしまった。
file_name="*"
# このままでは file_name がエスケープされていないので、
# sh の拡張子のファイルが全部表示される。
ls $file_name.sh

# ===== Output =====
'*.sh'   check.sh   echo_script.sh   test.sh

文字列で受け取っている以上、期待結果としては '*.sh' のファイル1 つのみなはずです。

ShellCheck の指示通り " で囲んであげたら期待値通り *.sh のファイルにのみヒットします。

# なんかの演算の結果、ファイル名に * がきてしまった。
file_name="*"
# '*.sh' というファイル名にのみヒットする
ls "$file_name.sh"

# ===== Output =====
'*.sh'

word splitting

bash では空白で一息つく癖があるので、スペースが含まれてると文字列じゃないように解釈されてしまってやばいよ!ってことです。

具体例
これは結構問題になる例が浮かんでくるかと思いますが、とりあえず 1 つ。

# なんかの拍子で d に以下のような文字列が入ってきた!
d="true = true -o x"
# 実はここは true になるので、意図しないタイミングで if 節の中が実行される!
if [ $d = "pien" ]; then
    echo "variable d is equal to pien"
fi

コマンド [ では -o オプションは OR の役割を果たしており、$d = "pien" とかいた時は次『のどちらかが成立する時』という条件式になっています。

  • true = true
  • x = "pien"

つまり 1 つ目の式が絶対に真となるため、意図せず if ブロックが実行されてしまいます!

SQL インジェクションみたいだな〜〜って思って考えていました。

おまけ

glob の文字列を含むファイル名を作成できるの?って感じですが、以下のようにすれば可能でした。

$ touch \*.sh
$ touch '*.sh'

おわりに

ShellCheck は偉大だけど納得して使いたい。

Jetpack compose で Indicator をなめらかにする

今回は、Jetpack compose でなめらかに Indicator を表示する方法についてメモしておきます。

なお、今回の内容は『Android DevSummit "5 quick animations to make your Compose app stand out"』から学んだものとなります。

[目次]

環境

- compose "1.3.0-rc01"
- kotlin "1.7.1"

実装方法

今回は LinearProgressIndicator でやってますが、特にこれに限った話ではありません。
(今回のソースコードgithub においてます。)

通常の Indicator

比較のため、まずは通常の LinearProgressIndicator を実装します。

var idx by remember {
    mutableStateOf(1)
}
val progress = idx.toFloat() / 5

LinearProgressIndicator(
    modifier = Modifier
        .padding(32.dp)
        .fillMaxWidth(),
    progress = progress,
    color = Color.Red,
)

(gif ってのもありますが)カクカクしています。

animate*AsState を使う

progressの定義を以下のように変更するだけです。
非常に簡単でありがたいです。

val progress by animateFloatAsState(targetValue = idx.toFloat()/5)

また、animationSpec を細かく変更することも可能です。

var idx by remember {
    mutableStateOf(1)
}
val progress by animateFloatAsState(
    targetValue = idx.toFloat() / 5,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioLowBouncy,
        stiffness = Spring.StiffnessLow,
    )
)

LinearProgressIndicator(
    modifier = Modifier
        .padding(32.dp)
        .fillMaxWidth(),
    progress = progress,
    color = Color.Red,
)

gif に変更してるので多少分かりにくいかもですが、通常の時に比べてなめらかに変化していることがわかります。

リンク

おわりに

Jetpack Compose のアニメーションのページが非常にしっかりしてて、気合い入れてる度合いが伝わってきます。
こっちもきちんとウォッチしていきたいです。

Android のマルチモジュール化で Preview の高速化

今回は Android のプロジェクトをマルチモジュール化し、Jetpack Compose の Preview を高速化してみました。

[目次]

環境

- PC
    - macOS version 12.4
    - Apple M1 chip
    - Memory 16 GB
- Android Project
    - compose "1.3.0-rc01"
    - kotlin "1.7.1"

マルチモジュール化のメリット

  • ビルド時間の短縮
    • 2 回目以降のビルドは、変更の入ったモジュールのみになるため速い!
  • レイヤー間の依存関係の強制
  • 各モジュールの関心ごとを小さくできる
    • package ではクラスのグルーピングしかできないが、レイアウトファイルや resource 等も含めたグルーピングが可能!
    • string, layout, manifest 等が機能単位に集約され、見やすい
    • internal 修飾子による、モノリスより柔軟な可視性制御
  • Compose の Preview も速くなる

特に『Compose の Preview を速くする』ことを目的に、今回はとあるプロジェクトをマルチモジュール化してみました。

デメリット

メリットだけだと不公平なので、デメリットも思いつく限り記載しておきます。

  • 初回ビルドは時間がかかる
    • らしい
    • 対象モジュールを結合するため
  • 浅いモジュールを作りすぎると、複雑性が増して保守しにくくなる
    • 分割の仕方大事そう

マルチモジュール化の方法

『既存の Android アプリを multi-module project 化』のサイトが詳しいですが、概略としては以下のステップで可能です。

  1. File > New > New Module
    • 何もなければ No Activity で
  2. フォルダ構造を変更(refactor)
    1. settings.gradle も変更する
    2. ':data' -> ':core:data'
  3. 余計なファイルを削除する
    • drawable など

module にするときは、build.gradle > pluginscom.android.application の値を com.android.library に変更します。

// module になる側
plugins {
    // id 'com.android.application'
    id 'com.android.library'
    ...
}

また、module を import したい時は、build.gradle に以下のように記載します。

// module を使う側
android {
    ...
}
dependencies {
    ...
    // modules
    implementation project(":core:common")
    implementation project(":core:domain")
}

実際にやった対応リポジトリです。)

プレビュー速度の向上

Jetpack Compose には UI を即座に確認できる『Preview』が用意されております。

@Preview
@Composable
fun MainViewPreview() {
    val uiState = MemberListUiState()

    CustomTheme() {
        MainView(uiState = uiState)
    }
}

ここでは Preview を行う際に重要となる次の 3 つの動作について、それぞれ変更前と後で時間を比較しました。

    1. Build Refresh
    2. Preview の内容を変更した際に変更を反映させます。
    1. Start Interactive Mode
    2. Interactive ModePreview Mode でタッチ操作可能なモード)を有効にする
    1. Stop Interactive Mode
    2. Interactive Mode を止める

それぞれ 3 回ずつ測定し平均を求めております。

変更前 マルチモジュール
12.1 1.Build 7.5
9.5 2.Start
画面 1
2.6
11.2 2.Start
画面 2
2.9
8.7 3.Stop
画面 1
3.2
9.3 3.Stop
画面 2
2.7

2~3 倍程度速度の高速化が確認できました。

おわりに

今までは正直、Preview の動作が重くて使ってなかったのですが、これでようやく使い物になりそうです。

また、マルチモジュール化を行うことで各レイヤー・各画面の責務を考えるきっかけになったのでよかったです。