kobakei's blog

プログラミングの話や技術系イベントの話をゆるく書くブログです

Androidアプリのパッケージ構成の個人的ベストプラクティス

以前Androidのパッケージ構成について考えてみたという記事を書いたんですが、書いたときから結構時間が経って考え方が変わった箇所もあるので、再度まとめようと思います。

前提

アプリケーションのアーキテクチャには、MVVM with data bindingを採用しています。また、モデルは階層化アーキテクチャ(っぽい何か)を採用しています。

結論

こんな感じになりました。ここでは以下で説明する順に並べていますが、Android Studio上ではアルファベット順に並びます。

com.example.myapp
|- di
|  |- scope
|
|- model
|  |- entity
|  |- usecase
|  |- repository
|  |- dao
|  |- net
|
|- ui
|  |- base
|  |- util
|  |- view
|  |- foo
|  |- bar
|  |- ...
|
|- service
|
|- receiver
|
|- App.java

各パッケージ

di

Dagger 2のComponent / Moduleをここに置きます。

scope

DaggerのScopeアノテーションを自作したときはここに置きます。

model

Modelの各クラスがここに入ります。

entity

データオブジェクト

usecase

ビジネスロジックを表すクラス。1つの処理で1クラス。例えば、「サインインする」というビジネスロジックには、SignInUseCaseというクラスを作成します。

repository

オブジェクトのCRUDを提供するクラス。対応するエンティティごとに1クラス。後述のdaoとnetパッケージのクラスを抽象化するクラスです。

dao

データベース(主にSQLite)にクエリを投げてレコードを取得し、エンティティに変換するクラス。もちろんその逆(データベースへのWrite)も行います。

net

サーバーと通信してレスポンス(主にJSON)を取得し、エンティティに変換するクラス。もちろんその逆(POST / PUT / DELETEメソッドのAPIコール)も行います。

ui

UIそのものと、UIに関連するロジックをここに書きます。

MVVMの場合、VとVMをここに書いていきます。なぜビューとビューモデルでパッケージを分けないかというと、大体のビューは対応するビューモデルがいるので、パッケージを同じにしておいたほうが開発するときに作業しやすく、あとから見やすいためです。同じ理由で、Activity、Fragment、Adapterも同じパッケージに置きます。ちなみに、モデルは複数の画面から呼び出されることが多いので、独立したmodelパッケージ以下に配置しています。

base

BaseActivityやBaseFragmentなど、ベースクラスを置きます。

util

ビューやビューモデルで共通に利用するユーティリティを置きます。Data bindingのBindingAdapterなどもここに置きます。

view

カスタムビューをここに書きます。

それ以外

画面単位でパッケージを作成します。例えば、ユーザー登録画面ならsignup、商品の購入画面ならpurchaseなど。その画面のActivity, Fragment, ビューモデルがすべてそこに入ります。

service

AndroidのServiceをここに書きます。model以下でもいいような気もしますが、ライフサイクルを持つという点ではActivityに近いので、あえて独立させました。

receiver

AndroidのBroadcastReceiverを書きます。大体上に同じです。

アプリケーションクラス

これはアプリケーションのパッケージの直下に置きます。

Android開発でRxJavaを使うと嬉しいこと

RxJavaをAndroid開発で使い始めてしばらく経ったので、RxJavaを使うと何がどう変わったのかをまとめます。

※RxJavaを使ったことがない人向けの戯言です。

非同期処理の待ち合わせが書きやすい

よくある例として、2つのREST APIを並列実行して、両方の結果が帰ってきてから画面を更新する、みたいな例を考えます。

これをRxJavaを使わずに普通に書くと、

  • それぞれのAPIごとにスレッドを作る
  • CountDownLatchを使って、両方のスレッドの完了を待ち合わせる

という風になります。結構つらいです。

RxJavaでは、非同期処理を簡単に並列化できます。

// api1, api2 はAPIコールのObservable
Observable.combineLatest(api1, api2, Pair::create)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(...)

僕はCountDownLatchで3つのAPIを待ち合わせるコードを書いててウワアアア!!!ってなって、RxJavaの導入を決断しました。

画面のライフサイクルに合わせて非同期処理を止めやすい

Androidを書いてる人が一度は見たことがあるのが、非同期処理のコールバックでUI操作しようとしたけど、ユーザーがバックボタンを押したなどの理由で画面がすでにないためExceptionになるというクラッシュです。

これを回避しようとすると、コールバックが呼ばれたときに画面が破棄されていないかをチェックすることが必要になります(getActivity() != nullみたいなやつ)が、そもそも画面が破棄されたときに自動で非同期処理を止めてくれたら嬉しいですよね。RxJavaと一緒にRxLifecycleというライブラリを使えば、ActivityやFragmentの破棄時に非同期処理を中止することが簡単にできます。

myObservable
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY)) // ここ
    .subscribe();

composeの行を足すだけで、Activity/Fragmentのライフサイクルにバインドできます。

リスト操作を簡単にしたい

Javaの配列やリストはメソッドが貧弱なので、他の言語を触ったことがある人ならmapやfilterなどのメソッドが欲しくなると思います。

RxJavaを使えば、配列やリストをより簡単に扱えます。例えばこんな感じ。

List<Integer> list = Observable.just(1, 2, 3, 4, 5)
                .filter(integer -> integer % 2 == 0) // 2, 4
                .map(integer -> integer * 2) // 4, 8
                .toList() // リストへ変換
                .blockingGet(); // 同期的に取得

ただ、リスト操作を便利にするだけなら最近ではLightweight Stream APIを使うほうがスマートな気もしますし、Kotlinであれば最初からリスト関連のAPIが充実していたりもします。

まとめ

ということで、ここで書いたような課題を感じている方は、RxJava試してみるといいのではないでしょうか。 僕もまだ定番の使い方しかマスターできてませんが、考え方を一度理解すればコードが書きやすくなると思います。

shibuya.apk 13で登壇しました #shibuya_apk

Intermediate Level Data Bindingというタイトルで発表しました。

speakerdeck.com

また、サンプルコードはこちらにあります。

github.com

基本的にはデータバインディング使ったことない/簡単なとこでは使ってる人に、BindingAdapterやリストとの連携などもう少し突っ込んだ使い方を紹介するというセッションでした。

かなり発表時間が伸びてしまい、ちょっと後半駆け足になってしまい、楽しみにしてくれてた方申し訳ありませんでした。懇親会で何人かの方から「もっと見たかった」「データバインディングについて相談したい」といって頂けたのがせめてもの幸いでした。

ざっくりとですが、昨日のセッションで話したかったことを以下にまとめます。

会場アンケート

  • データバインディングを使っている: 6割
  • BindingAdapterを使ったことがある: 6割
  • InverseBindingMethodなどを使ったことがある: ほとんどいない(1,2人だけ)
  • ObservableListを使ったことがある: 2割

BindingAdapter

データバインディングの属性を自分で追加する仕組みです。Picassoによる画像読み込みをBindingAdapter化するのは割とメジャーですでに使ってる人も多いかもしれませんが、リスナーや双方向データバインディングを追加する方法は結構めんどくさいので、これらの実装方法を紹介しました。「データバインディング」と言いつつ、ビューモデルなどのデータとバインディングしない使い方もあり、単なるXML属性の拡張としても使えるので、今のアプリの設計を大幅に変えなくてもXML属性の拡張として導入しやすいと思います。

リストとの連携

ObservableListとRecyclerView.Adapterを組み合わせる方法を紹介しました。ObservableListには要素の追加・削除・移動などのリスナを登録できるので、その中でRecyclerView.Adapterのnotify…を呼ぶというのが肝です。当日紹介しませんでしたが、MVVMで実装する場合は、画面全体のビューモデルとリストアイテム一個のビューモデルを用意する実装を自分はよく使います。この辺の設計についてはこれだけで話せる内容なので、別の機会に発表したいと思います。

詰まりやすいところ

(完全に時間がなくて飛ばしました)

時間があれば、データバインディングによるUI更新時のチラツキを抑える方法と、スレッドに関する注意を話す予定でした。

  • 即時UI更新には、ViewDataBinding#executePendingBindings() を呼ぶ
  • コレクションを操作するときはメインスレッド。それ以外はどのスレッドからでも操作できる

ということが伝えたかったことです。

まとめ

ということで、一歩先を行くデータバインディングについてお話しました。 何か質問などがある方は、 Twitter: ksk_kbys までお願いします。

DroidKaigi 2017に参加した感想

聞いたセッション

1日目

  • 逆引き マテリアル デザイン
  • Android Security 最前線!!
  • Androidリアルタイム通信アプリ作成Tips
  • Data Bindingで開発を気持ちよくしよう
  • 実践アニメーション
  • オフラインファーストなアプリケーション開発
  • React Nativeはクロスプラットフォームモバイルアプリ開発の夢を見るか
  • What’s New in RxJava 2.0

2日目

  • Android ORMの選び方
  • 未熟なチーム開発
  • Data Bindingで実現するMVVM Architecture
  • 4年続くアプリにおけるチーム開発
  • コマンドなしでぼくはAndroid開発できない話
  • Fireside Chat
  • テスト0から目指すクラッシュフリー率99%

感想

結論:とてもよかった

Androidエンジニアこんなにいたんだ

過去最多の参加者ということで、かなり広い会場に人がぎっしりいて「日本にこんなにAndroidエンジニアいたのか」という驚き。最近どこの会社もAndroidエンジニア足りてないって話を聞きますけどみんな普段どこにいるんですかね。。。

セッションのアジェンダをもうちょっと詳しく/正確にして欲しい

セッションについては、公式サイト/アプリで見るアジェンダを見て予想した内容と、実際に話してる内容に結構ずれがあったのが気になりました。例えばHogeというワードがアジェンダには入ってないのにトークではHogeを使う前提で話が進む、みたいな。同じ時間帯に見たいセッションがかぶることが多く、どのセッションを見るべきかを判断するためにもこの辺の情報はできるだけ正確にして欲しいなーという印象。

MVVM + data binding

個人的には最近MVVM+data bindingに取り組んでいることもあって、「Data Bindingで実現するMVVM Architecture」のセッションが一番期待していました。セッション本体はどちらかというとMVVM初めてやる人向けかなーという感想でしたが、Office Hourでの会話がとても濃く、こういうのはネットでスライドみるだけじゃなくて実際に参加してみないと体験できないなと思いました。このセッション立ち見がかなりいたので、もっと大きい部屋でもよかったんじゃないですかね。部屋割りどうやって決めてるのか知らないけど。

もっとこんなのが聞きたい

来年に期待。

  • 設計やデザインパターンを適用として、詰まったところ&どうやって解決したか、みたいな話
  • レガシーなアプリをモダンな設計にリプレースした話
  • あまり有名じゃないけど実はおすすめなライブラリの話
  • 泥臭い話

来年は登壇したい

あと、今年CFP応募してみたんですが、残念ながらRejectされた*1ので普通に聞くだけでの参加でした。 来年こそはスピーカーとして参加したいと思います。

*1:RejectConやりたい

Goで匿名ダイアリーもどきを作ってみた

完全に趣味ですが、Goに入門しました。 匿名ダイアリーもどきのアプリケーションを作るとこまでできたので、ここまでに学んだことをメモ。 http://golang-jp.org/doc/に十分まとまっていますがね。

作ったアプリ github.com

※環境はOSXです

インストール

brewでさくっと入れました。執筆時点のバージョンは1.7.4.

brew install go

Goは$GOPATHという環境変数ワークスペースを指定します。また、$GOPATH/binにバイナリが配置されますので、.bash_profileに以下を追加。

export GOPATH="$HOME/go"
export PATH="$GOPATH/bin:$PATH"

コーディングスタイル

goは公式のスタイルが厳格に決まっていて、gofmtなるフォーマットツールも付属しているらしい。

以下のコマンド一発でフォーマットしてくれるので、git hookとかで強制的にフォーマットするとよさそう。

gofmt -w .

パッケージマネージャ

Goではライブラリはワークスペースにgetしてきたものが使われるんですが、複数のマシンで開発するときはやっぱバージョン固定したいですよね。

パッケージマネージャは色々ありますが、glideかgovendorがナウい感じ? gom, godepなどもあるみたいだけど、後述するEchoではglideとgovendorが紹介されてました。

特に強い理由はないけど、なんとなく今メジャーっぽいというだけでglideを採用。 バージョンをyamlで管理してくれます。

brew install glide

glide init
glide install
glide get github.com/hogehoge/fugafuga

WAF

RubyにおけるRailsのような、とりあえずこれ使おう的なものはまだない印象。そもそも重量級フレームワークとかを使って黒魔術ゴリゴリで書く言語ではなさそう。

  • Echo
  • Gin
  • Revel

など色々あります。開発の活発さとドキュメントの充実具合で、Echoを採用しました。

テンプレートエンジン

「標準のhtml/templateパッケージで十分」とどこかで見かけたので、これしか試してない。

ORM

WAFに同じく、あんまりGoだと使わないのかなー。デファクトスタンダードなものはなさそう。

  • gorm
  • gorp

などがあり、gormを採用しました。この選択はあまり自信ないです。

テスト

標準のtestingパッケージを使って書けばOK

所感

  • 「今風なC++」を書いている感覚
  • コンパイル言語でwebアプリを書くの初めてなので新鮮
    • 文法エラーとかビルド時に教えてくれて嬉しい
    • ちょっとした修正でもビルドが走るからtry and errorに時間かかってる。auto reload的なのあるのかな?
  • 言語仕様が引き算で考えられていて覚えやすい
  • 並列処理のgoroutineが便利らしいけど、まだ触ってないので触りたい