🤖

【Flutter】Material3 Expressive をJetPack Composeで実装してみた!【DroidKaigi2025】

に公開

バージョン

  • kotlinバージョン:1.8.22
  • Flutter: 3.29.3(FVM)

Droid Kaigi 2025

みなさんお久しぶりです。ここ数ヶ月忙しくzennを書く暇がZennZenn(全然)ありませんでした!(笑)。先日東京渋谷で開催された年に1度のAndroidエンジニアのテックカンファレンスDroidKaigi2025私は今年初参加でした。ありがたいことに今回スカラーシップで参加させていただきました。普段Flutterばかりやっている私がAndroidのセッションを聞いて楽しめるのかまたわかるのかといった不安はありましたがとても楽しく参加させていただきました。スマホ新法やGoogle PlayなどFlutterとも共通する内容セッションも多く楽しむことができました。その中でもカンファレンスDay 1日目のセッションである5月のGoogle I/Oで発表されたMaterial3 ExpressiveについてのセッションにおいてKotlin実装の例が紹介されていたのでホテルへ帰って早速やってみようということでやってみました

Material3 Expressiveについて

上記のセッションのなかでも紹介があった通りMaterial 3 Expressiveは既存のMaterial 3のアップデート版で今あるMaterial 3コンポーネントをよりだれでも扱いやすくカスタマイズしたアップデートされたデザインコンポーネントです。
Material 3との違いはサイズの種類が増えていたりアニメーションがよりリッチでわかりやすくそして楽しさを演出しやすいというのが特徴です。

Flutterを使用したMaterial3 Expressive

結論から言うとFlutterだけでMaterial3 Expressiveを実装することは不可能です ではどうするか一言で申し上げるとJetpack Composeを呼び出して使用します。
なぜそのような実装になっているかと言うと下記URLのissueでも議論されていますがFlutter公式は当分対応しないと回答しているからです。
https://github.com/flutter/flutter/issues/168813
つまり公式では当分サポートしないと言い切っているのですでにアルファ版として配信されてるKotlin実装が必須になります。

※ ネイティブ呼び出しは以下を参考にしてください
https://note.com/mizutory/n/n72b4c3964cba

実装について

今回は試しにProgress indicatorのみを実装してみました。
以下はFlutter側のコードです。
Androidかどうかを判定してAndroidであればComposeを使用し、それ以外はFlutterがサポートするCircularProgressIndicatorウィジェットを使用すると言うだけです。
特別なパッケージ等も必要がなく比較的楽に、簡潔にかけると言った印象を受けます。今回試しに実装しただけなのでif文を使用してますが三項演算子等をかつようするともっと簡潔でわかりやすい実装になると思います。

import 'dart:io' show Platform;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class ExpressiveLoading extends StatelessWidget {
  final Color? color;
  final double size;
  final double? progress;

  const ExpressiveLoading({
    super.key,
    this.color,
    this.size = 48.0,
    this.progress,
  });

  
  Widget build(BuildContext context) {
    if (!kIsWeb && Platform.isAndroid) {
      return AndroidView(
        viewType: 'expressive_loading',
        layoutDirection: TextDirection.ltr,
        creationParams: <String, dynamic>{
          'color': (color ?? Theme.of(context).colorScheme.primary),
          'size': size,
          'progress': progress,
        },
        creationParamsCodec: const StandardMessageCodec(),
      );
    }
    // iOS/Web fallback
    return CircularProgressIndicator(
      value: progress,
      color: color,
    );
  }
}

つまづいたところ

今回一番困ったのはKotlinバージョンの不一致とネイティブ呼び出しに必要なパッケージのimportです。
Kotlinを普段やらない自分にとっては一体いくつ入れるんだ?となるくらいimport文が多くAIにエラーを投げると大体import不足ですと帰ってきました(笑)。
あとFluter3.29移行gradleが.ktsとなりインターネット上にある対処法ではimportできないことがありました。その辺はGPT5に修正を手伝ってもらいました。

まとめ

ネイティブ実装を求められる性質上あまり多様はできないと感じました。特に今回のようにロードの時だけ使用するなど部分的な使用はアリなのですがAndroidのみの実装でありiOSでは別での実装が必須になってしまうのでFlutterの強みである1つのコードでどちらのOSもサポートできるという最大の強みがいかせなくなると感じました。しかしロードのみとかだと少しUIがリッチになるので飽きないUXを実現できmaterial3 Expressiveの思想の一つであるUIのマンネリ化をなくすという思想にもマッチしているように感じました。

関連・参考記事/サイト

ぽちぽちのつどい

Discussion