GoogleI/O行ってないけどAndroid Jetpack気になったので調べてみた

先週はGoogle I/Oでしたね。現地にも行かずリアルタイムで見ることもしませんでしたが、TwitterのTLから現地のレポートを見てそわそわしていました。
KeynoteでのAndroid P発表も騒がれましたが、何やらJetpackとやらが話題の様子。Googleがdocumentを揃えてくれているので、読んでみました。

なお、翻訳ではなくメモ程度です

developer.android.com

What’s

公式サイトによると、

Jetpack is a set of libraries, tools and architectural guidance to help make it quick and easy to build great Android apps. 

とのこと。後述する内訳も見ると、これまでGoogleが提供していたArchitectureComponentを含んだコンポーネント群、のような感じでしょうか。

What’s included

4つの区分があるようです。

Foundation

Android開発の全領域に関わるコンポーネントが入っています。AppCompatよく分からんけど使っとくか、みたいな時期がすごく長かったので、個人的にこれがちゃんと分類されたことはすごく嬉しいです。

Architecture

  • Data Binding
  • Lifecycles
  • LiveData
  • Navigation
  • Paging
  • Room

ここがいわゆるArchitecture Componentが入るカテゴリのようですね。データの永続化、データ変更の通知などを行うものから、ライフサイクルのつらみをラッピングしてくれるものまで詰まっています。 正直まだ全部は分かってないので詳しくは書きません。
Navigationのところは、Androidアプリのデザイン自体とも絡んできそうなので、早めに読みたいです。

Behavior

  • Download manager
  • Media & playback
  • Notifications
  • Permissions
  • Sharing
  • Slices

制作しているアプリが、他のアプリやOSなどと上手くやっていくところをお手伝いしてくれるコンポーネント群、という理解です。
特に気になったのはNotificationsとSlicesです。前に業務でウィジェット(ホームスクリーンに出てくる方)を作ることがあったのですが、通知やらUI微妙に作りづらい問題やらで結構苦労しました。この辺がきれいに作れるようになってるといいなぁ……。(まだ見てない)

UI

  • Animation & transitions
  • Auto
  • Emoji
  • Fragment
  • Layout
  • Palette
  • TV
  • Wear OS by Google

当たり前なのですが、UIというだけあって、handset以外のモバイル端末から車までもをサポートするコンポーネントが揃っています。スマホだけがAndroidアプリ開発じゃないんだ。
加えて、おなじみFragmentから絵文字、アニメーションを対象としたコンポーネントが入っています。

How to use

developer.android.com

具体的な使い方は各コンポーネント群をインストールして……となるのです が、Jetpackベースでアプリを作る時のハンズオン?のようなものも用意されていました。

Android Studioの3.2+が必須条件です。まだアイコンが緑色の人(2018/05/12時点)は、公式サイトからPreview版をインストールしましょう。 Stable版とは違う名前でインストールされたのでご安心を。

f:id:t-miliya612:20180513030135p:plain

次に、新しくプロジェクトを作ります。
すると……

f:id:t-miliya612:20180513030156p:plain

新顔がいますね。Activity & Fragment + ViewModelだそうです。

f:id:t-miliya612:20180513030213p:plain

ActivityやFragmentとそれらに紐づくlayout配下のXMLの名前を聞かれます。さらに、ViewModelの名前やFragment package pathを入力する項目がありますね。

とりあえずそのままにしてFinishを押すと……

f:id:t-miliya612:20180513030227p:plain

Activity, Fragment, ViewModelが出来ています。Activity以外はuiパッケージに押し込めてくれています。
ソースはかなりシンプルですが、以下のとおりです。

Activity

...
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                    .replace(R.id.container, MainFragment.newInstance())
                    .commitNow()
        }
    }

}

Fragment

...
class MainFragment : Fragment() {

    companion object {
        fun newInstance() = MainFragment()
    }

    private lateinit var viewModel: MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        // TODO: Use the ViewModel
    }

}

ViewModel

import android.arch.lifecycle.ViewModel

class MainViewModel : ViewModel() {
    // TODO: Implement the ViewModel
}

ページ上に進めていくと、ViewModelを実装したりArchitectureComponentのNavigationの香りを感じたりできます。

Guide to App Architecture

Guide to App Architecture  |  Android Developers

ちょっと長いので箇条書きで。

  • アプリのコンポーネントの中にアプリのデータや状態を保持してはいけない
    • 全てのアプリのコンポーネントは、個別に起動されたりいつでも捨てられる可能性があるため
  • コンポーネント間は依存せず独立しているべき
  • 重要な原理は2つ
    • 関心の分離をしよう
      • ActivityやFragmentからUI操作やOSとのやり取り以外のコードを排除する
    • UIを(できれば永続化した)モデルから操作しよう
      • OSによるリソース解放でアプリのプロセスがdestroyされたとしてもユーザーがデータを失わないため
      • ネットワークの状態がよくない時でもアプリを起動しておくため
      • Modelとはアプリ内のデータを扱うコンポーネント

UIを作る時は?

  • ViewModelを使おう
    • 表示するデータを保持しておくため
    • Viewや設定の変更(画面の向きの変更によるactivityの再生成など)とは関係なしにデータを扱える
  • LiveDataを使おう
    • アプリの他のコンポーネントから保持しているデータの変更を検知するため
    • データの変更をobserveしてUIを更新したいときには、propertyをLiveDataで包んだ後、observe()メソッド内で処理を行う

データを取得するときは?

  • REST APIと通信を行うライブラリを使おう
    • Serviceという形でメソッドを準備する
    • 今回はRetrofitを使う
  • ViewModelにデータの取得とオブジェクトへのマッピングの責務を追わせるのはやめよう
    • メンテナンス性向上のため
    • 関心の分離のため
  • Repositoryオブジェクトを作ろう
    • データの操作を担当する
    • データの取得先やデータ更新時に呼ぶAPIを知っている
    • data sourceを抽象化できる
    • (これはコンポーネントというより実装方針?)
  • コンポーネント間の依存性を管理しよう
    • RepositoryAPI通信用のServiceインスタンスが必要
    • 今後Repositoryを使う全クラスでServiceインスタンス生成を行うのは面倒
    • 方法は2つ
      • DIを使って他クラスから実行時にインスタンスを提供(注入)する
      • Service Locatorを使ってregistryを用意する
    • テスタビリティも向上する

ViewModelとRepositoryを接続するときは?

ViewModelからrepositoryのメソッド呼ぶだけです。次!

データをキャッシュしたいときは?

  • Fragmentに戻るたびにAPIをcallするのはやめよう
    • 貴重なネットワークの帯域を浪費することになる
    • ユーザーに通信完了まで待たせることになる
val cached = CacheObject.get(id)
if (cached != nil) {
    return cached
}

// 以降、APIからの取得処理

そっか、インメモリキャッシュってこんなシンプルだったんだと考え直しました。SQLiteやらrealmやらライブラリ先行で技術を追っていると大事なことを忘れてしまいますね。

データを永続化したいときは?

  • Roomを使おう
    • SQLiteのORマッパー
    • コンパイル時にSQLSchemeと合っているかバリデーションしてくれる
    • 使い方
      • local schemeとして、Entityとするクラスに@Entityアノテーションをつける
      • RoomDatabaseを拡張したdatabaseクラスを作成する
      • データ挿入用のDAOを生成する
      • databaseクラス内でDAOへの参照を作る

テストしたいときは?

  • UI & Interaction
    • この時だけEspressoなどを使ってUIの実装のテストをしよう
  • ViewModel
    • JUnitでテストをしよう
    • Repositoryをモックににする必要がある
  • Repository
    • JUnitでテストをしよう
    • ServiceとDAOをモックにする必要がある
    • 以下のことを確かめよう
      • 正しくServiceのメソッドをcallしているか
      • APIの結果を正しくDBに保存しているか
      • データがキャッシュされていて最新のとき、余計なAPIリクエストをしていないか
  • DAO
    • instrumentationテストを行う必要がある
    • JUnitSupportSQLiteOpenHelperを使えばUnitテストができるが、端末とローカルPCとでSQLiteのバージョンに違いがあると困る
  • Service
    • Mock化しよう
    • 外部に依存しないテストにするため、通信が走らないでテストをしよう
    • ライブラリはたくさんあるけど、okhttp/mockwebserverとかおすすめ
  • TestingArtifacts
    • ArchitectureComponentは、バックグラウンドのスレッドを操作するmavenアーティファクトを用意
      • InstantTaskExecutorRule: 全てのバックグラウンドの操作を行う
      • CountingTaskExecutorRule: instrumentationテスト内で、ArchitectureComponentのバックグラウンド処理を待ったりEspressoと接続したりする

TODO

  • 古くなったデータの更新処理
  • ネットワークの状態をユーザーに通知する

Note

  • データの整合性を保つには、data sourceを1つにしよう
    • 同じデータを返す複数のAPIのエンドポイントがあるとき、データの齟齬が発生する
    • 全てのデータ取得を内部のDBなどに統一すれば解決できる

大筋は以上です!!長かった!~後半JetpackというよりはAACの話だけどまあいっか~
実際はコードラボ風味になっているので、手を動かしながら進めていただきたく!

AACの紹介ではなくて、直面した問題ベースでの設計の考え方をベースに進めてくれたので、かなり分かりやすいし研修の教材としてもいい感じなのではないでしょうか。
まだAACで触わりたりないところがあるので、各コンポーネントの内部もしっかり読んでいきたいです。