読者です 読者をやめる 読者になる 読者になる

サイボウズLiveの新アプリのAndroid版が公開になりました

Android Java

こんにちは。サイボウズLiveチームの山川です。

12月14日に公開となったサイボウズLiveの新アプリ「サイボウズLive TIMELINE」のAndroid版の設計および実装を担当しました。

サイボウズLive TIMELINE - Google Play の Android アプリ

f:id:cybozuinsideout:20151217172419p:plain

Androidユーザーの方は是非ダウンロードをよろしくお願いします。このエントリでは、Androidの新アプリについて実装面でのお話をしようと思います。

はじめに

今回の新アプリはサイボウズLiveチームにおいて、Androidアプリの初のネイティブアプリ開発(既存のアプリは Titanium )でした。そのため、開発期間の最初の1ヶ月間ほどは調査期間として、必要になるであろう機能を簡易実装したサンプルアプリを作りました。サンプルアプリといえど1ヶ月の期間があると、それなりのボリュームの物が出来上がります。そして、気づいたころには Activity が肥大化していました。メソッドの切り出し等のリファクタリングをするにしても、あまりスマートになる気がせず・・・。

これは本番実装ではもう少し賢く書かないと後々大変だと思い、もろもろ調べたり人に聞いたりして、最終的に「 Model view presenter(MVP) + Domain-driven design(DDD) 」のアーキテクチャを意識した構成となりました。 こちらの記事 などはとても参考になりました。

MVPとDDDに関しての詳細説明は割愛します。ここではMVP+DDDをAndroidで実装した話をまとめます。

パッケージ構成と全体処理の流れ

本アプリのjavaパッケージ構成は以下のようになっています。

├data
│├dxo (XML→entity)
│├repository (domain/repositoryの実装)
│└util (repositoryの実装で使ういろいろ)
│
├domain
│├entity (サイボウズLive固有のエンティティ)
│├repotisory (entityのCRUDインターフェイス)
│└usage (repositoryの利用の切り出し)
│  
└ui
  ├dxo (entity→vmo)
  ├presenter (presenterインターフェイス)
  │└impl (presenterの実装)
  ├view (viewインターフェイス)
  │└activity (viewの実装)
  └vmo (表示用エンティティ要約オブジェクト)
    

各パッケージの詳細は追って説明します。全体的な処理の流れを以下の図にまとめます。模式図ですが、処理の流れと各層の処理内容がお分かりいただけると思います。

f:id:cybozuinsideout:20151217110355p:plain

各パッケージの役割

domain アプリケーションの中心

アプリケーションの中心となる domain 層は entity と repository が主な要素です。

entity はサイボウズLive固有の entity です。Androidとは無関係なメンバー変数に持つことになります。 ただし画像情報などを担う entity は画像の保存先のファイルパスなどを保持することはあります。

repository は基本的には entity のCRUDを提供するインターフェイスとなります。以下に「Group」の entity をリストで取得するインターフェイスの例を示します。

public interface GroupRepository {

    void getGroupList(OnGetGroupListFinishListener listener);

    interface OnGetGroupListFinishListener {
        void onGetGroupListFinished(List<Group> groupList);
        void onError(ErrorType errorType);
    }
}

Androidの制約でメインスレッド上でのネットワーク接続は禁止されています。したがって通信が発生する処理は、非同期処理となります。本アプリでも多くはAPI経由でデータを取ってくるものなので、それを考慮したうえで、コールバックインターフェイスにしておきます。

usage パッケージは、repositoryの利用でコールバックを受け取り、そのコールバックで再び repository を利用するような、多段のコールバックとなってしまうような処理をまとめておくものです。これは、単純にコールバックの連続はコードとして読みにくいということと、複数個所から全く同じ repository の連続利用がある場合に一連の処理を切り出して共通化したい、という理由からです。DDDの考え方だと、UseCase を設けてそこのクラス経由で repository 利用するのが一般的かつ、重要なのですが、本アプリでは実は UseCase は存在しません。その理由は後述しますが、UseCase がビジネスロジックの分離を目的としているのに対して、usage は単純な利用の切り出しといった位置にあるため、(将来、コードを見る人が勘違いしないように)異なる名前にしておきました。

data domianの実装

domain の repository インターフェイスを実装します。その多くはAPI経由でデータを取得して entity を作成することです。API通信以外にも、ローカルデータの場合は SharedPreference 、アイコンや画像を扱う場合はファイルへの書き込みと読み込みを行う場合もあります。SharedPreferenceのキー名の管理や、ファイルパスの管理は repository インターフェイスを介すことで、data 層内に集約することができます。非同期処理、通信、XMLのパースや組み立て、OAuth用の汎用処理は data 層の util 内に配置します。 dxo はAPIの結果のXMLを entity に変換するクラスを配置するパッケージです。

ui view の実装をするActivityとpresenter

ui パッケージにはまず、view インターフェイスと presenter インターフェイスという2大要素があります。view インターフェイスの実装はActivityやFragmentが担います。view の構築およびイベントリスナーなどを配備するのが仕事です。イベントは presenter に伝えます。 presenter は view の参照を持ち、Activityからのイベント呼び出しを待ちます。イベントが呼び出されてたら、必要な処理を実行し、その結果をviewインターフェイスを介して反映させる、というのが presenter の役割です。当然処理内容に repository の利用がある場合は、コールバックを受け取るのは presenter の実装クラスとなります。

repositry から entity や entity のリストを取得してそれらをviewに渡す際に登場するのが vmo (View Model Objectの略、筆者が命名)です。これは「 entity を表示用に要約する」という位置づけのものです。例えば、グループのメンバー名簿を表示することを考えます。表示の仕様は

  • メンバーの姓名を1行目に表示

  • 2行目には最終ログイン日時を最終ログイン:○月○日」と表示、年が操作時と異なる場合は年も表示

  • グループ管理者の場合は姓名を太字にする

とします。グループメンバーの entity が姓、名、最終ログイン日時、管理者フラグを持っていれば、これを仕様通り表示させるのは造作もないことです。しかしこれを view の実装でだらだら書くと、 レイアウトwidget の作成と混在することになります。そこで view に渡す前に、vmo で要約してあげることで「表示したいもの」の作成と「表示すること」を分離します。以下にこの例における entity と vmo の模式図を示します。

f:id:cybozuinsideout:20151217110349p:plain

vmo はとにかく「何をどう表示するか」を扱いやすくなっていればいいので、変数名などは必ずしも中身と相関しなくてもいいと思います。この辺りは分離しなくても問題ない場合も多いですが、多言語対応など将来的に表示に関わる改善を見越したときのことを考えると、やっておいて損はないと思います。

UseCaseがない理由

本アプリではDDDを意識したと言いつつ、DDDで重要とされる UseCase がありません。その理由として、すでにビジネスロジックを解決済みのAPIを利用しているから、ということなります。つまり、APIがすでに UseCase の外側に位置するので、Androidアプリ内ではAPIの結果を entity に変換した時点でビジネスロジックの分離が完了しているのです。ただこれは、APIの提供側とAPIの利用側が同じ目的を達成したい場合に限ります。利用するAPIがアプリの達成したいロジックを提供していない場合、当然ながら UseCase を設けることは大変有効だと思います。

MVPパターンにするメリット

格段にコードが読みやすくなります、と言うと主観的な感想になってしまいますが、view インターフェイスや presenter インターフェイスを設けることで、データの取得( repository の利用)と view の構築が自然と分離されるのは大きいです。またAndroid依存の実装箇所も自然と分離されるので、保守性の観点からもうれしいです。

MVPパターンにするデメリット

まさにデメリットというものはあまりないですが、そもそもActivityクラスというものが view の実装専用のクラスではないという点は懸念材料です。今後Androidプラットフォームがどのように進化していくかはわかりません。Activityが view の実装という割り切りを崩す必要があるような変更が将来あるかもしれません。

おわりに

今回はAndroidアプリの実装についてお話させていただきました。とりわけアーキテクチャというものは、抽象理論の時点だと「なるほどー」となりますが、実際うまい具合に実装できるかは、やってみないと分からないことが多々あります。さらにそれがAndroidという開発者を悩ませることの多いプラットフォーム上となると・・・。

そんなAndroidですが、世界シェアではiOSより全然大きかったりします(日本は違いますが笑)。 苦労する分、工夫のしがいはあるよ!という前向きな言葉を結びといたします。

皆さまもぜひ幸せなAndroidライフを送りましょう。