🙌

脱 Material3 ?! lumo ui の紹介

2025/02/12に公開
2

最近見つけた推しライブラリ lumo ui の紹介になります。

https://lumo.nomanr.com/

https://speakerdeck.com/tbsten/tuo-material3-lumo-ui-noshao-jie

lumo ui とは?

lumo ui はカスタマイズ性の高いコンポーネントライブラリです。テーマや UI コンポーネントを提供してくれます。
競合するライブラリとしては Compose Material (3) や jewel などの UI ライブラリが挙げられます。

既存の UI ライブラリで困る例

Compose Material (3) や jewel など、従来のUIライブラリの問題点として、細かいカスタマイズが難しい 点があります。

例えば以下のようなデザインのボタンを作りたかったとします。(ちょっと極端)

素直に書くと以下のようにしたくなりますが、これは期待通りの結果が得られません。

 Button(
   onClick = { },
   contentPadding = PaddingValues(0.dp),
 ) { Text("small button") }


上記のコードを実行するとこのようになります

これは、 Compose Material 3 の Button Composable のなかで defaultMinSize が指定されているためです。

なのでこの3行を消せれば解決なのですが、ライブラリのコードはライブラリユーザからは編集できません。そのためソースをコピーして、そのコンポーネントに必要な private/internal なコードもコピーして... と作業することになってしまいます。

lumo-ui では、ライブラリのコードをプロジェクトの src ディレクトリに含めるように運用 することでこの問題を回避しています。

例えば lumo-ui でボタンを使いたいとすれば以下の gradle task を呼び出すことで src ディレクトリ内に Button Composable のコードが生成されます。
src ディレクトリ内に生成されるということは(当然ではありますが)ライブラリのコードは自分で好き勝手いじれます

./gradlew lumo --add Button
# 
# /app/src/main/your/package/ui/ # 👈 アーティファクトとしてダウンロードされるのではなく、プロジェクトの src の中に生成できるのがポイント
#   Button.kt # 👈 これが生成される
# 

またコンポーネントのコードをリポジトリに含めているため lumo-ui のアップデートによるデグレも心配する必要はありません。

コンポーネントが完全にあなたのものになる感じです!

セットアップ

基本的にはドキュメント 通りですが、ざっくり4ステップです。

セットアップ1: Gradle Plugin を追加して ./gradlew lumo --init

build.gradle.kts
plugins {
  id ("com.nomanr.plugin.lumo") version "$latest"
}

ドキュメント曰く ルートプロジェクトに設定するもよし、特定のモジュールに設定するもよしとのことですが、個人的には基本ルートプロジェクトに入れれば良きかなと思いました。デザインシステムを複数持つような大きなアプリの場合はモジュールごとにGradle Plugin を設定した方がいいかもしれません。

続いて lumo --init を呼び出します。

./gradlew lumo --init

これで lumo-ui の設定ファイルである lumo.properties が作成されます。次のステップでこれを設定します。

セットアップ2: lumo.properties を設定

セットアップ1 で生成された lumo.properties では生成するコードのオプションや、どこにコードを生成するかが指定できます。

# Lumo UI Plugin
# This file is used to store configurations for the Lumo UI Plugin
# Do not delete this file
ThemeName=AppTheme
ComponentsDir=<<relative-path-to-components-dir-from-root>>
PackageName=<<component-files-package-name>>

ちょっとした罠なのですが ComponentsDirに注意する必要があります。実際にコードを生成させてみるとわかるのですが ComponentsDir の中に以下のようにcomponents ディレクトリなどが生成されるため、 ConmonentsDir に components 的な意味合いを含むパスを指定してしまうと components/components のようなモヤる構成 になってしまいます。

components のようなコンポーネントのニュアンスを含む名前は避けて uidesignSystem 的なニュアンスを含む名前にした方がいいかもしれません。

lumo.properties の例
# Lumo UI Plugin
# This file is used to store configurations for the Lumo UI Plugin
# Do not delete this file
ThemeName=AppTheme
ComponentsDir=app/src/main/java/com/example/ui
PackageName=com.example.ui

セットアップ3: (プロジェクトに compose の依存関係を追加)

./gradlew lumo --init を実行した際や、./gradlew lumo --required-deps を実行することで確認できる依存関係を追加しましょう。

上記のコマンドの出力だと api("...") のように出てきますが、 implementation でも問題ないです。

dependencis {
    // ...

    // for lumo-ui
    implementation(platform("androidx.compose:compose-bom:2024.12.01"))
    implementation("androidx.compose.foundation:foundation")
    implementation("androidx.compose.foundation:foundation-layout")
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-tooling")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.ui:ui-util")
    implementation("androidx.compose.material:material-ripple:1.7.6")
}

セットアップ4: ./gradlew lumo --setup

./gradlew lumo --setup

このコマンドを実行することで、セットアップ2 で設定した lumo.properties に従って AppTheme などの標準のコードが指定したディレクトリに出力されます。

使い方

lumo-ui のドキュメントから使用したいコンポーネントを探します。

コンポーネントを見つけたら Installation に書いてあるコマンドをポチるだけです。

./gradlew lumo --add Accordion

lumo -add することで lumo.properties ディレクトリ内にファイルが生成されます。

使い方のポイント

ここからは直近で個人プロジェクトに導入してみて気づいたおすすめな使い方をシェアします!

その1: material2/3 の依存は極力削除する

lumo ui をセットアップした後は material2/3 の依存関係を削除することをお勧め します。

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

material3 の依存を残しておくと 例えば 画面A では material3 の Button Composable を使っているが、画面B では lumo-ui で作った Button Composable を使っているみたいなことがおきえてしまいます。これでは material 3 のコンポーネントを使用できてしまい、 lumo-ui を導入した意味が薄れてしまいます。

どうしても使用したいコンポーネントがmaterial3 にある場合以外は基本的には削除しておいた方が無難かと思います。 (例外としては androidx.compose.material:material-icons-extended (material design のアイコン集) などがあるかもしれません)

(Material 2, 3 を混合させないほうがいいよね的な話と同じ理屈)

その2: 導入のタイミングである程度コンポーネントを追加しておく

compose-material2/3 とは違い、 gradle task を実行しないとコンポーネントが一切使用できません。そのため導入した段階で 使うであろうコンポーネントをある程度 揃えておくのがおすすめです。

以下はおそらくどのアプリでも必須なので思考停止で入れちゃいましょう。

  • Button
  • Divider
  • Icon
  • Icon Button
  • Scaffold
  • Text
./gradlew lumo --add Button &&
  ./gradlew lumo --add Divider &&
  ./gradlew lumo --add Icon &&
  ./gradlew lumo --add IconButton &&
  ./gradlew lumo --add Scaffold &&
  ./gradlew lumo --add Text 

(今まで Text も m3 のもの使ってたんだ... としみじみしましたw)

また好みやプロジェクトの UI に合わせて以下も追加を検討してみましょう。

  • Alert Dialog
  • Card
  • Checkbox
  • Navigation Bar
  • Slider
  • Snackbar
  • Switch
  • TextField
  • Tooltip
  • TopBar

逆にここにリストしてないコンポーネント(Accordion など)は 必要になったら都度追加でほとんどの場合事足りるでしょう。

その3: 最悪 lumo-ui 自体が廃れても剥がしやすい

前述の通り、コマンドを叩いてコードを作ると言う開発フローになっていることで、将来 lumo-ui をプロジェクトから剥がしたいとなっても移行しやすいのです。

やっぱ material3 に戻りたいとなった際は lumo-ui で作成した Composable の実装を material3 に置き換えるだけです。

 @Composable
 fun Button(...) {
-  // lumo-ui に生成してもらったコードは削除
-  val containerColor = style.colors.containerColor(enabled).value
-  val contentColor = style.colors.contentColor(enabled).value
-  val borderColor = style.colors.borderColor(enabled).value
-
-  ...
-
-  Surface(
-    onClick = onClick,
-    ...
-  ) { ... }

+  // 代わりに 移行先 の Composable 呼び出しに変更
+  androidx.compose.material3.Button(...) { ... }
 }

引数の型など完全に一致しているわけではないので多少考えて実装し直す必要はありますが、ライブラリ同士が複雑に依存し合って剥がすのが難しいと言うことはほとんどないと思います。

その4: Preview が綺麗!

compose-material2/3 のライブラリコードにジャンプしても Preview がないことがほとんどのため、初見だと Composable の見た目がわからなかったりして調べるのに苦労することがあるかと思います。

lumo-ui で生成されたコードをのぞいてみるととても見やすい Preview が用意されており、地味に感動してしまいました☺️


Button の Preview


CircularProgressIndicator の Preview


lumo-ui で あなたのデザインシステムを便利にしましょう!

2

Discussion