Jetpack Compose Beta Overviewの動画を見たのでまとめる


注意

  • 本記事はYouTubeのAndroid Developersチャンネルにおいて投稿された動画である Jetpack Compose: Beta overviewについて、動画内で述べられていることと、一部筆者の主観が入っている記事となっております。
  • 記事内で使用される画像は、Jetpack Compose: Beta overviewより引用させていただいております。(不都合等ありましたらコメントをお願いします)
  • 動画を全範囲網羅している記事ではありません。

Jetpack Composeとは?

1. Modern, declarative UI tool kit

  • Composeは、宣言的UIを可能にするUIツールキット。
  • 宣言的UIとは?
    • どのように見えるか(What)を記述し、どのように作るか(How)を記述しない

2. Built on Kotlin

  • Kotlinで作られており、Composeの関数のような、直感的なAPIを構築するのに十分な表現力

3. Unbundled

  • Composeは他のJetpackライブラリと同様にアンバンドルされています。
  • プロジェクトのペースで更新できるように、APIバージョン間で一貫した動作をする
  • アンバンドルとは?
    • Androidのプラットフォームに含まれておらず(unbundle)、ライブラリとして追加できる

4. Built for interop

  • 相互運用(interop)を重視している
  • 例えば、新規の画面から徐々にComposeに移行することができるみたいな

現在のJetpack Composeの状況

  • Jetpack Composeは現在Betaである(2021/04/15時点)
  • APIの洗練と安定化に焦点を当てて開発中

Jetpack Composeの推しポイント

  • ComposeはAndroidのUIをよりはやく・簡単に実装することができる
  • なぜ、はやく・簡単に実装することができるのか?
    • 宣言的とUIツールの2つに分解して説明していく

まず宣言的について

従来のアプローチ(慣例的なアプローチ)

上の図の例は、入門書などに書かれている一般的な例。

XMLがあり、その状態を変更するために、findViewById()などをして、プロパティを設定する。

例えば、DBやネットワークから情報をActivity・Fragmentが受け取ったとき、その情報をもちいてUIを更新するなど。

この問題点はStateがXMLとFragment/Activityの両方に存在すること。

例えば、上図のTextViewのテキストをAPIの結果に応じて変化させる場合を考える。

XMLのレイアウトファイルにTextViewがあり、初期のテキストが設定されているとする

そして、APIの結果として、文字列を受け取ったときにその情報を格納する変数をFragment・Activityに定義していたとする。

TextViewの状態、Fragment・Activityの状態の2つが存在する。

Fragment・Activity間の状態を、TextViewの状態に反映するには、setText()などを使用しなければいけない。

この手続き的な操作は、開発者の責任で行う必要があり、バグの原因となってしまう。

JetPack Composeのアプローチ

上図のようにStateとUIがあり、Stateは変化するもので、UIは普遍で変更されないもの。

状態が変化した際に、UI全体が再生成される(効率化のために実際には差分のみの再生成となる)

先ほどの例(TextViewのテキストをAPIの結果に応じて変化させる場合)について考えてみる。

状態はFragment・Activityで保持する1つのみとなり、状態としてTextViewに表示する文字列が入っている。

APIリクエストがあり、状態の変更を検知して、状態をもとにUIを再生成する。

何が良いのか?

これにより、状態に応じて自動的にUIが変更されることになる。
例えば、先ほどの例でいうところの、setText()という手続きが不要になる。

結果として、手続きの記述漏れなどによるバグがなくなる。

コードレベルで確認していく!

  • 上記コードの説明
    • 文字列のリストを表示する単純なコンポーネント
    • 関数は何も返さないが、UIをemitする
    • messageの中身が空の場合は「No messsages」を、それ以外の場合はメッサージをテキストとして表示
    • 関数が実行されたら、新しいUIが作成される
    • これをRecomposingと呼ぶ。メッセージの状態が変わったら、UIが再生成される
messageの変化をどう検知するのか?

messageをLiveDataとする。

  • 上記のコードの説明
    • ViewModelにおいて、messageをLiveDataで公開
ConverationViewmodel.kt
  // おそらくこんな感じ
  class ConversationViewModel: ViewModel() {

    private val _message = MutableLiveData("")
    val message: LiveData<String> = _message
                :
  • LiveDataで新しいデータを検知するたびにMessageList(message)が呼ばれUIが再生成される
  • 注意点
    • Composableはimmutableなので、参照を保持して、後で照会したり、内容を更新することはできない
    • したがって、全ての情報をEmit時にパラメーターとして渡す必要がある
しかしながら、コンポーネントが変化できないということを意味するわけではない

すべてのメッセージを選択するチェックボックスを追加して、すべてのメッセージを選択できるようにしてみる。

上記のコードだと、チェックボックスをクリックしても視覚的には切り替わらない。(cheched = false)としているので。

なので、状態を更新して、チェックボックスをemitする必要がある

どうするか?
チェックするかどうかのローカルな状態を持たせる。

そして、CheckBox()のパラメータonCheckChangeにユーザがチェックしたときのコールバックを設定する。

このようにすることで、チェックボックスの状態であるselectAllがクリック時に更新され、チェックボックスへ視覚的な状態が反映される。

しかしこれだと、UIにロジックがあって気持ち悪い
下記のように、MessageList()のパラメータとして、ステートと更新のためのラムダを渡す。

Composeはどういうアーキテクチャが適しているか?

基本的には、Composeはどのようなアプリケーション・アーキテクチャにも対応できる。

特に一方向性のデータ・フローとの相性が良く、ViewModelが画面の状態のストリームを公開しているとき。

各コンポーネントはパラメーターとして状態を受け取り、データが更新された場合にのみUIを更新する。

1つのLiveDataを使用することで、状態を一元化でき、画面全体の状態についての理解が簡単になりエラーを減らせる。

宣言的についてのまとめ

Composeを使用すると Single Source Of Truth(信頼できる唯一の情報源) の原則が守れる。

慣習的なアプローチでは、Fragment・Activityで保持している状態とViewが保持している状態の2つがあり、バグの原因となる可能性があった。

しかし、Composeを使用すると状態を一元管理することができる。

そして「状態に変化があったときに、自動でUIが変更される」という性質により、バグが生まれにくくなる。

UIツールについて

ComposeはUIコンポーネントの豊富なツールキットを提供している。

機能やメリット

Material Design ComponentとMaterial Themingのサポート
Material Design Component Material Theming
シンプルだがパワフルなレイアウトシステムを提供

例えば、RowというComposableは、LinearLayoutのorientationhorizontalに指定したレイアウトとほぼ同等。
しかし、Viewシステムが異なり、ネストされたレイアウトをパフォーマンスの高いものにしてくれるメリットがある。

より複雑なレイアウトを表現するために、ConstraintLayoutも提供されている。

ただし、カスタムレイアウトを使う方が簡単。

アニメーションシステム

UIに動きをつけることが、より使いやすく、非常にパワフルになっている。
MotionLayoutをComposeに導入する作業も行っている

テスト

Composeは、テストを容易にするために専用のテストシステムを提供する。
そして、Composablesを個別にテストするための簡単なAPIを提供する。
アニメーションのテストも書くことができる。

Kotlinで書かれている

Kotlinの優れた言語機能のおかげで、シンプルで直感的なAPIとなっている。
例えばCoroutineを使用することによって、シンプルな非同期APIを作成できるようにしている。
Coroutineを使用して、タッチ検出がブロックされないように、新しいコルーチンで押された位置にアニメーションさせている。

具体例で確認していく!

下記レポジトリの中のJetchatというアプリで説明。
https://github.com/android/compose-samples

レイアウトの説明

上記のレイアウトを作るには下記のことを行っている

  • message(=表示するテキスト)、isFromMe(=自分からのメッセージがどうかのフラグ)をパラメータに持つComposableを作成
  • メッセージ全体を囲むCard Composableを作成
    • isFromMeに応じて、Themeから色を指定
  • 実際にテキストを表示するためにText Composableを作成
    • Modifierパラメータ用いてpaddingを設定
開発・検証方法

@Previewアノテーションを用いてプレビュー用のComposableを作成する。
それをAndroid Studio上で確認することが可能

この後、細かい実装方法が紹介されているので割愛。
述べられている内容

  • ComposableのパラメータにModifierを渡して、再利用可能に
  • Modifierでは、最大幅やテキストのAlignmentなどを指定
  • LazyColumnを使用してRecyclerViewのような効率的なスクロールの実装
  • など

興味ある方は、10:3312:59

推しポイントまとめ

宣言的なUIによって、Composeを使用するとSingle Source Of Truth(信頼できる唯一の情報源)の原則が守れる。
UIツールによって、レイアウトやアニメーションの実装が簡単になる。
-> 簡単に、はやく実装することができる

まだまだ推しポイントはある

既存のViewシステムと共存できる

Interoperability APIと呼ばれるものが用意されている

:
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
                      :
    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
                      :
</LinearLayout>
class ExampleFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // Inflate the layout for this fragment
        return inflater.inflate(
            R.layout.fragment_example, container, false
        ).apply {
            findViewById<ComposeView>(R.id.compose_view).setContent {
                // In Compose world
                MaterialTheme {
                    Text("Hello Compose!")
                }
            }
        }
    }
}

コードの引用: https://developer.android.google.cn/jetpack/compose/interop?hl=ja#interop-apis

他の主要なライブラリとの統合を提供

資料等

Composeの学習系

フィードバック等