Help us understand the problem. What is going on with this article?

Androidのパッケージ構成について考えてみた

More than 1 year has passed since last update.

更新しました (2017/4/12)

この記事を書いたときから大分時間が経ったので、最新版をこちらに書き直しました。

基本的な階層構造

AndroidでMVCを適用しようとすると、Activity/FragmentがViewとController両方の責務を担っていて綺麗にVとCを分離できず、もやっとしてしました。そこで、色々な資料やサンプルを見たところ、 http://www.slideshare.net/mokemokechicken/iosandroidmodel にある階層構造が一番しっくり来たのでこれをベースにパッケージ構造を考えることにします。

ユーザー操作に近い順に、以下の3階層に分類されます。

  • プレゼンター層
    • UIの制御やユーザーの入力に対する制御を扱う層。MVCでいうとVCにあたる。
  • ビジネス層
    • ビジネスロジックを扱う層。MVCでいうとMのうち抽象度の高い部分。
  • データ層
    • データリソース(データベースやサーバーAPIなど)へのアクセスを行う層。MVCでいうとMのうち抽象度の低い部分。

パッケージ構造

基本的な階層構造をもとに、もう少し細かい単位で分けたものをパッケージ構造に一致させます。

com.example.myapp
|
|  (プレゼンター層)
|- activities
|- fragments
|- views
|
|  (ビジネス層)
|- usecases
|- entities
|
|  (データ層)
|- network
|- storage

または、階層を強く意識させるなら、一段階掘ってこういう階層構造もありかと(やりすぎ?)

com.example.myapp
|
|- presenters
|  |- activities
|  |- fragments
|  |- views
|
|- business
|  |- usecases
|  |- entities
|
|- data
   |- network
   |- storage

各パッケージ

activities

Activityが入るパッケージです。Activityが担うべき責務としては、

  • Fragmentの追加・変更・削除
  • ActionBar, Menu, Drawerの管理
  • 他のActivityへの遷移

などがあります。ActionBar以外の画面内のビューについては、Fragmentにやらせるので触らないようにします。

fragments

Fragmentが入るパッケージです。Fragmentが担うべき責務としては、

  • UIの制御(ActionBarは除く)
  • ユーザーの操作やライフサイクルに合わせて、Model(後述するusecases)を呼び出す。また、処理完了時の通知を受け取ってUIに反映する

があります。

なお、Fragment in Fragmentはライフサイクルがややこしくなるので避けるのがベターだと思っています(Fragment in ViewPager in Fragmentとかは除く)。複数のFragmentで共通して利用したい部品は、後述するviewsパッケージにカスタムビューを作ります。

views

カスタムビューが入るパッケージです。色々なFragmentで共通して利用するViewやView周りのロジックを共通化します。

usecases

ビュー(Activity, Fragment, etc)が直接呼ぶビジネスロジックをここに入れます。Twitterクライアントアプリを例にすると、

  • ログイン済みかどうかをチェックする
  • ツイートする
  • 自分のタイムラインを取得する

などが挙げられます。

内部では後述するnetwork/storageパッケージのクラスを使ってデータの取得や更新を行います。自分のタイムラインをRESTful APIを叩いてサーバーから取得するかSQLiteのキャッシュを使うかなどはビューが知るところではないので、usecasesクラスのAPIでは抽象化します。

TwitterUseCase.java
/**
 * Twitter関連のビジネスロジック
 */
public class TwitterUseCase {

    /**
     * タイムライン取得時にプレゼンテーション層に返すイベント
     */
    public class TimeLineEvent {
        public final List<Tweet> tweets;
        public TimeLineEvent(List<Tweet> tweets) { this.tweets = tweets; }
    }

    /**
     * タイムラインを取得する
     * これを呼ぶクラス(Fragment)は、中でどんなデータリソースにアクセスしてるかは知らない。
     */
    public void getTimeline() {
        // ローカルキャッシュから取得
        List<Tweet> tweets = LocalTweetCache.getAll();
        if (tweet != null) {
            EventBus.getDefault().post(new TimeLineEvent(tweets));
            return;
        }

        // キャッシュから取れなかった場合、APIから取得
        ApiClient apiClient = new ApiClient();
        apiClient.getTimeLine(new TimeLineListener() {
            @Override
            public void onSuccess(List<Tweet> tweets) {
                EventBus.getDefault().post(new TimeLineEvent(tweets));
            }
        });
    }
}

http://www.slideshare.net/mokemokechicken/iosandroidmodel にある通り、非同期処理を行った後のビューへの結果の返し方は、通知を使います。ライブラリには EventBus を使います。ビューのライフサイクルは短いのですが(ユーザーの操作によって簡単に破棄されるので)、EventBusでは通知先が死んでいても気にせずにイベントをpostできて楽です。

あとusecasesという名前はいまいちしっくり来てないので、他にいい名前がないか考えています。modelsだとカバーする範囲が広すぎるし、managersもなあ。。。

entities

データそのものを表すPOJOが入るパッケージです。データの取得や保存は後述するstorageやnetパッケージのクラスにやらせるので、純粋なデータを表すだけのクラスにします。

データ層でデータベースやRESTful APIから取得した生のデータからEntityに変換され、そのままビジネス層, プレゼンテーション層へと渡っていきます。

Tweet.java
public class Tweet {
    public long id;
    public User user;
    public String body;
    public Date createdAt;
}

network

RESTful APIのクライアントなど、ネットワーク関連のクラスが入るパッケージです。APIクライアントのやるべきことを時系列に並べると、

  • 設定やパラメータからHTTPリクエストを組み立てる
  • HTTPリクエストを発行する
  • HTTPレスポンス(主にJSON文字列)をパースし、Entityに変換する
  • 呼び出し元のスレッドで、Entityを渡したコールバックを実行する

という流れになります。自分は内部実装には Volley + Gson をよく使います。

storage

データベース(SQLite, SharedPreferences, ローカルファイル etc)関連のクラスが入るパッケージです。

  • データベースからEntityを取得
  • Entityをデータベースに保存

などを行います。

内部では ActiveAndroid などのORMapperやContentProvierを使用してデータベースへのアクセスを行います。

最後に

こんな感じでパッケージを分けておくと、クラスを設計するときにパッケージの粒度に従って設計すればよいので、複数メンバーで作業していても抽象化のレベルを統一でき、楽にクラスの責務を分離できるのではないかと思っています。

開発が進むにつれてアプリが大きくなっていくと他のアーキテクチャや設計に変えるかもしれませんが、今のところはこれくらいがちょうどいいような気がしています。

参考資料

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away