チームが注目したJetpack Composeの特性1:宣言的UI

山田祐介氏:これに対し、チームが注目したJetpack Composeの特性をお話しします。ここでようやくJetpack Composeのお話です。Jetpack Composeを学習したところ、先ほどの課題を解決するような特性をいくつか発見しました。それらについて話したいと思います。

今回は課題の数に合わせて大きく3つあげました。本当はメンバーからももっといろいろ出てきたのですが、課題に関連が深いと思われるものだけを紹介したいと思います。宣言的UI、パフォーマンスの最適化、便利なプレビュー機能です。

まず1つ目の宣言的UI。一言で説明することは難しいのですが、検索すると「あらかじめ完成形のUIのイメージに基づいて画面を実装する手法」などといった説明があったりします。

(スライドを示して)今回はGoogle I/Oの『What's new in Jetpack Compose』という動画の、日本語訳に基づく説明を記載しました。状態をUIに変換するという考え方や、UI生成後に中身は更新できない、更新させないUIを再生成するといったところです。

こちらは図も用いて説明したいと思います。「状態:1」を「UI:1」に変換すると言っても、これは基本的な部分で、先ほどの話であった「UI:1」を「UI:2」に直接変化させることができない特性があります。

では「UI:2」を変換するにはどうするかというと、状態を2にして、それをUIに変換して生成し直します。状態はデータやパラメータと考えてもらって、UIがComposable関数を生成するものと捉えてもらえるといいかなと思っています。

こちらは、コードでも解説したいと思います。コードに関しては、「Composeの思想」という『Pathways』の3つ目にある記事から抜粋してきたものとなっています。

中身としては、名前のリストを引数に名前の数だけ「Hello 〇〇」みたいなテキストを表示するComposable関数となっています。先ほど「状態」と言っていたのは、namesパラメータのところに該当します。

「UI」はこの関数が生成するものです。生成はしますが、リターンするわけではないというところに注目してください。この関数を見ても、戻り値は存在しません。リターンしないので、変数としてinstance化もできないため、中身を変えるには関数の引数を変えるしかありません。

こちらも「Composeの思想」から抜粋した図です。グレーの四角がComposable関数で、層としてはデータ最上位から上位に、さらに下位に渡していくかたちです。そうすることで、データの一元化が可能になります。

一元化すると、すべてのComposableに関してデータを渡すことになるので、パフォーマンスに影響があるのではと思われるかもしれませんが、そこに関しては考慮済みということで、一元化してもパフォーマンスは最適化されます。

というのも、状態の変更があるComposable関数のみ再実行される特性があるからです。これも検証してみたので、コードも交えて話したいと思います。

(スライドを示して)こちらは私がオリジナルで用意したコードです。最初に定義している3つの値「yamada」「takahashi」「suzuki」をテキストで表示して、さらにその下にボタンがあります。そのボタンを押すと「yusuke」「takahashi」「suzuki」という新しい3つのパラメータで表示し直すコードになっています。

先ほどのスライドでは出ていなかった関数の中身を用意しておきました。「TreeGreeting」というのは、3つの名前に対して挨拶をするコードで、Greetingはテキストを表示してログを出しています。

「NameChanger」はボタン用のComposable関数で、引数で受け取っているラムダをただ実行するだけのものです。

(スライドを示して)先ほどのコードも踏まえてこちらを実行すると、このようなかたちになっています。画面としては、最初の状態が左の画像の「Hello yamada」「Hello takahashi」「Hello suzuki」という状態です。ここで下にある「ChangeName」というボタンを押すと「Hello yusuke」「Hello takahashi」「Hello suzuki」と、一番上のyamadaがyusukeに変わります。

その時のログを右側に貼っています。これを見ると、yamada、takahashi、suzukiと、最初の初期化のところで3つの名前が出ていますが、ボタンを押した後は、yusukeしか出てきません。

先ほどのコードでは、yusukeだけではなくてtakahashi、suzukiというパラメータも一緒に渡していたのですが、変化しないところに関しては再生成、再コンポーズがされていないことがわかると思います。そのため、差分更新は実装を考えなくてよいことがわかります。

Jetpack Composeへの移行で状態の一元化が可能になる

これらの特徴がZOZOTOWNの課題とどう関連するのか。ちょっと間が空いてしまいましたが、検索画面の各Viewの状態が複数あり、その変化・復元を意識しなければならないことが課題でした。

これをJetpack Composeに移行すれば、先ほどの状態の一元化が可能になり、あとは一元化したものを更新・維持すればよくなります。パフォーマンスを気にする必要もないですし、タイミングも一元化したので、たぶん気にしなくてよいはずだと。

「Jetpack Composeじゃなくても一元化自体は可能なのでは?」という意見もあると思いますが、Jetpack Composeのようにソーステキストとして明確になっているほうが、開発メンバー全員が共通認識を持ちやすいと思います。そういう意味でも、Jetpack Composeでやることに意味があるかなと思っています。

結果、バグが減り、開発スピードも上がりそうです。

チームが注目したJetpack Composeの特性2:パフォーマンスの最適化

続いて、パフォーマンスの最適化について話したいと思います。これは「思想」に文章としてまとめられていました。先ほどの内容も「再コンポーズは可能な限りスキップする」として記載されています。さらにその中で気になったのは、「コンポーズ可能な関数は並行して実行できる」という記載です。

(スライドを示して)こちらに内容を抜粋しました。「Composeは複数のコアを活用し、画面上にないコンポーズ可能な関数を、より低い優先度で実行します。この最適化は、コンポーズ可能な関数がバックグラウンドスレッドのプール内で実行される可能性があることを意味します」と書いてありました。

画面上にないComposable関数は、バックグラウンドスレッドのプール内で実行されるというところです。すごいなと思いました。こちらは検証ができていないので、記事の紹介のみとなります。

Lazy化

また少し前の話で、ZOZOTOWNではRecycler View化を検討しているという話があったと思いますが、そのCompose版がとても魅力的でした。それがLazy化なのです。

これに関しては、Adapterのようなものが存在していないにもかかわらず、Recycler View同様、画面内に表示される直前で各Composeを生成するような仕組みとなっているそうです。こちらは簡単に検証できそうだったので、コードを書いてみました。

(スライドを示して)こちらのコードでは一番上がスタートボタンで、間に100個のデータがあって、最後にエンドボタンが描画されています。これは見やすさのためにボタンにしたのですが、特に押してもなにも反応しません。

左側のコードを見てもらうとわかると思いますが、itemsのところでボタンとデータとボタンというかたちで、itemsで分類するだけで、RecyclerViewでいうViewTypeが実現できています。これはすごい。本当にすばらしいなと思いました。

ZOZOTOWNであった課題の話です。「Viewが多すぎてパフォーマンスに悪いよ」という警告が出ていました。それをComposeに移行すれば、並列実行での最適化が期待できます。そうなれば、ユーザーがより早く画面を操作できるようになると思います。

また、最適化に期待するよりも、やはりRecyclerView化を進めたほうがいいんじゃないかという場合でも、LazyColumnの存在で簡単に実現が可能というところで、こちらに期待したい。

その結果、画面の起動速度が向上しそう、パフォーマンスが向上しそうなところが、よいところとして認識しています。

チームが注目したJetpack Composeの特性3:プレビュー機能

最後に、便利なプレビュー機能。@Previewというのは、Composable関数につけるだけで、アプリをビルドしなくてもプレビューが確認可能です。ただ、ファイル自体のビルドはしているものだったので、すごくシームレスにできるという感じではないです。それでも便利なところだと思います。

特筆すべき点は、1つのファイル内に複数のプレビューを用意することも可能なところです。そこが、今までのビューではできなかったことだと思います。こちらもコードとともに簡単に紹介したいと思います。

(スライドを示して)Composable関数が生成するものを最大2行まで表示して、3行目にまで中身がいきそうになると3点リーダーに変えるようなテキストになっています。

下の関数は、Preview1関数でPreview化をします。アノテーションがついているところです。文章が「あいうえお」とちょっと短いので、3行いったら省略されるのかわかりにくいかと思います。そこで、Previewを増やして確認します。

「あいうえお」から「まみむめも」まで書いたものをPreview2として用意して、改行コード「さしすせそ」のところまで用意したものをPreview3として用意しました。

そのPreviewが右側の画像です。Previewのとおり、3行目にいく前にすべて省略されていることがわかると思います。こんな感じで、複数のパターンがあっても、簡単に目視でも確認できます。

余談になるかもしれませんが、maxLinesのところを3に増やしてみると、当然ですがPreviewの全部が同時に更新されて、3点リーダーのところが省略されなくなっています。

Previewを多くすることでパターンの網羅がある程度解決できる

ZOZOTOWNの課題との関連です。パターンの網羅が難しいですが、このPreviewをたくさん用意することで、ある程度解決できるのではないかと考えています。さらにデザイン実装のコードレビューも、これを使えば捗りそうだなと思っています。

結果、デザイン実装時の網羅性が向上し、開発効率が上がりそうというところを期待しています。

チームが注目した特性のまとめです。まず宣言的UIで状態管理がスッキリしそう。パフォーマンスの最適化で、Viewが多かった画面も高速化されそうだし、RecyclerViewの実装も簡単そう。プレビュー機能をうまく使えば、状態の再現・確認が簡単にできて、開発効率が上がりそう。

その他のJetpack Composeのよさそうなところ

今回はいろいろな都合上、これだけしか紹介できませんでしたが、メンバーからは「こんなところでもよさそうだね」という意見ももらっているので、少し紹介します。

内容としては「テストが書きやすい」とか、あとは「けっこう新しい技術なので、チームのモチベーション増加から生産性の向上も期待できるんじゃないか」というような意見をもらっています。

Jetpack Composeはいろいろな課題をきっと解決してくれる

全体のまとめです。Jetpack ComposeはZOZOTOWN Androidの課題を解決する糸口でした。さらに「Composeの思想」というものを理解して実装すると、とてもよさそうです。

なにがいいかというと、バグが減り、開発スピードが向上しそうです。また、描画パフォーマンスも向上し、さらにPreview機能の活用で網羅性が向上しそうです。

そして冒頭でもお伝えしましたが、Jetpack Composeはいろいろな課題をきっと解決してくれます。みんなで学びましょう。学習は『Jetpack Compose Pathways』がおすすめです。

最後にHappy composing! ということで終わりたいと思います。ご清聴ありがとうございました。