スポンサーリンク

Androidアプリ開発用のMVCフレームワーク 「Android-MVC」 ver0.2をリリース


AndroidのMVCフレームワーク,「Android-MVC framework」がバージョンアップした。



※ver0.1のときに1分で描いた暫定ロゴ画像


本ツールは,生産的なAndroidアプリ開発を支援するための,Java製のオープンソース・フレームワーク。

ver0.1がリリースされたのは2012/02/15である。


このたび 3月23日付で,バージョン0.2をGoogle Code上に公開した。

プロジェクトのトップページから,最新版をダウンロード可能。

AndroidのMVCフレームワーク - 「Android-MVC」
http://code.google.com/p/android-mvc-...


今回の ver0.2 の特徴は,下記のページで詳しく解説されている。

ver0.2の新機能と改良点(M・V・Cの各レイヤで,サンプルコードを満載)
http://code.google.com/p/android-mvc-...

上記の解説ページの内容を,本エントリに引用する。




(※ここからは,現時点でのGoogleCode上の記述の引用となる。最新の情報はGoogleCodeを参照のこと。)


ver0.2の特徴:概観

ver0.2では,MVCフレームワークとしての基本構造が,よりいっそう強固になった。


全体のアーキテクチャは,ver0.1の時とほぼ同様である。(※こちらのページのクラス図をご覧いただきたい。)

このアーキテクチャを基に,ver0.2では

  • M・V・Cの各 レイヤ内部 の改良
  • M・V・Cの各 レイヤ間連携 の改良

が施されている。


各レイヤごとに,機能面での主な改良点を取り上げる。

  • View層
    • オプションメニューを,気軽に構築可能になった。
    • タブレイアウトを,気軽に実現可能になった。
    • 文字列リソースの参照が極めて容易になった。
  • Controller層
    • より柔軟な制御フロー・処理フローを,より明快・簡潔なコードで実現可能になった。
    • バリデーション・ロジックが,きわめて簡潔に記述できるようになった。
    • エンティティを含む任意の自作オブジェクトが,Intent経由で気軽に運搬可能になった。
  • Model層
    • ORMの機能が強化された。CRUDの処理は最初から組み込み済みになった。

これらの特徴は,同梱のサンプルアプリを動作させて確認することができる。

下記では,これらの特徴を,サンプルコードを引用しつつ詳細に説明する。

ver0.2の特徴:詳細


説明の順番としては,V→C→Mの順に見てゆくのが一番わかりやすい。

(このベクトルが,システムの外側から内側へ向かうため。)

View層:

■オプションメニューを,気軽に構築可能になった。

下記のようなコードをActivityに記述して,動的にオプションメニューを構築可能である。

        // オプションメニューを構築
        return new OptionMenuBuilder(context)
            .add(
                new OptionMenuDescription()
                {
                    @Override
                    protected String displayText() {return "DB登録";}

                    @Override
                    protected void onSelected() {
                        // 画面遷移
                        MainController.submit(activity, "EDIT_DB");
                    }
                }
            )
            .add(
                new OptionMenuDescription()
                {
                    @Override
                    protected String displayText() {return "DB閲覧";}

                    @Override
                    protected void onSelected() {
                        // 画面遷移
                        MainController.submit(activity, "VIEW_DB");
                    }
                }
            )
        ;

※オプションが選択された時には,Controllerを呼び出し,画面遷移などを実行させている。

引用元:TopActivity.java
http://code.google.com/p/android-mvc-...

■タブレイアウトを,気軽に実現可能になった。

下記のようなコードをActivityに記述して,動的にタブレイアウトを構築可能である。

        // タブの定義を記述する。
        new TabHostBuilder(context)
            .setChildActivities( FuncDBController.getChildActivities(this) )
            .add(
                new TabDescription("TAB_EDIT_DB")
                    .text("DB登録")
                    .icon(android.R.drawable.ic_menu_add)
                ,

                new TabDescription("TAB_VIEW_DB")
                    .text("DB閲覧")
                    .icon(android.R.drawable.ic_menu_agenda)
                ,

                new TabDescription("TAB_FUNC_NET")
                    .text("通信")
                    .noIcon()

            )
            .display()
        ;

各タブの中には,コンテンツとしてActivityを表示できる。

引用元:SampleTabHostActivity.java
http://code.google.com/p/android-mvc-...

つまりView層では,一般のView部品に加えて,メニューやタブも動的に手軽に構築できるようになった。

レイアウトXMLに手を加える必要は一切ない。

■文字列リソースの参照が極めて容易になった。

文字列リソースはRクラスから取得するわけだが,その手間は,もはや手間ではなくなった。

Activity上のサンプルコード:

              tv2 = new MTextView(context)
                .text("このアプリの名称:" + $._(R.string.app_name) )
                .widthWrapContent()

このアイデアの着想については,下記のページを参照のこと。
型安全な方を採用してある。

Androidアプリで,_("リソース名") と書くだけで,簡単に文字列を参照しよう
http://language-and-engineering.hatenablog.jp/entry/20110815/p1

なお「$」は,CommonActivityUtilのインスタンスである。

ver0.1では頭文字を取って「cau」という変数名で参照可能だったが,ver0.2ではこのように簡略化された。

View層の便利オブジェクトとして「$」という変数を利用する,という習慣は,特にWebアプリケーション開発に携わってきた者であれば,非常に親しみやすいだろう。

「$」は,今後もどんどん便利化していく計画である。

Controller層:

■より柔軟な制御フロー・処理フローを,より明快・簡潔なコードで実現可能になった。

コントローラ層では,処理フローが下記のように明確化された。

  • もし必要なら,Activityから受け取った値の バリデーション。
  • もしバリデーションを通過すれば, ビジネスロジック(「Action」) を実行。
  • ビジネスロジックの実行が完了したら,実行結果に基づいて, 遷移先の画面へルーティング。
  • 最後に, UI上で後処理 を実行。

これが,コントローラ層の要となるフローである。

もちろん,複雑な処理が無い場合は,ただ単に「Intentにデータを詰めて画面遷移するだけ」というのも可能。


サンプルを掲載する。

まずは,単純に画面遷移するだけのコード。Routerクラスが活躍する。

    /**
     * TOP画面からの遷移時
     */
    public static void submit(TopActivity activity, String button_type) {
        if( "EDIT_DB".equals(button_type) )
        {
            // 編集画面へ
            Router.goWithData(activity, DBEditActivity.class,
                new Intent().putExtra("hoge", "Intentで値を渡すテスト").putExtra("fuga", 1)
            );
        }
        else
        if( "VIEW_DB".equals(button_type) )
        {
            // 一覧画面へ
            Router.go(activity, DBListActivity.class);
        }
        else
        if( "TAB_SAMPLE".equals(button_type) )
        {
            // タブ画面へ
            Router.go(activity, SampleTabHostActivity.class);
        }
    }

goなら遷移するだけ。goWithDataなら,インテントにデータを詰め込んで運搬できる。

引用元:MainController.java
http://code.google.com/p/android-mvc-...

次に,バリデーション+ビジネスロジック+ルーティング+UI後処理 というフローをフルに活用しているサンプルを掲載する。

    /**
     * DB登録画面からの遷移時
     */
    public static void submit(final DBEditActivity activity)
    {
        new ControlFlowDetail<DBEditActivity>( activity )
            .setValidation( new ValidationExecutor(){
                @Override
                public ValidationResult doValidate()
                {
                    // バリデーション処理
                    return new FuncDBValidation().validate( activity );
                }

                @Override
                public void onValidationFailed()
                {
                    showErrMessages();

                    // バリデーション失敗時の遷移先
                    //goOnValidationFailed( DBEditActivity.class );
                    stayInThisPage();
                }
            })
            .setBL( new BLExecutor(){
                @Override
                public ActionResult doAction()
                {
                    // BL
                    return new DBEditAction( activity ).exec();
                }
            })
            .onBLExecuted(
                // BL実行後の遷移先の一覧
                new RoutingTable().map("success", DBListActivity.class )

                // onBLExecutedにこれを渡せば,BLの実行結果にかかわらず画面遷移を常に抑止。
                //STAY_THIS_PAGE_ALWAYS

                // BL実行結果が特定の状況のときのみ,画面遷移を抑止することも可能。
                //new RoutingTable().map("success", STAY_THIS_PAGE )

            )
            .startControl();
        ;

    }

引用元:FuncDBController.java
http://code.google.com/p/android-mvc-...

バリデーションは行なわないで済ませる,等の柔軟な書き変えも可能。

    /**
     * HTTP通信画面からの遷移時
     */
    public static void submit(final HttpNetActivity activity)
    {
        new ControlFlowDetail<HttpNetActivity>( activity )
            .setValidation( new ValidationExecutor(){
                @Override
                public ValidationResult doValidate()
                {
                    // バリデーション処理
                    return new FuncNetValidation().validate( activity );
                }

                @Override
                public void onValidationFailed()
                {
                    showErrMessages();

                    // バリデーション失敗時の遷移先
                    stayInThisPage();
                }
            })
            .setBL( new BLExecutor(){
                @Override
                public ActionResult doAction()
                {
                    // BL
                    return new HttpNetAction( activity ).exec();
                }
            })
            .onBLExecuted(
                // BL実行後の遷移先
                STAY_THIS_PAGE_ALWAYS
            )
            .startControl();
        ;

    }

引用元:FuncNetController.java
http://code.google.com/p/android-mvc-...

もし,ビジネスロジックを実行した後で,その結果を参照しつつUI操作を実行したい場合は,Activity側に下記のようなメソッドを置くだけで実行される。

ここでは,HTTP通信ロジックの処理結果を参照している。

    @Override
    public void afterBLExecuted(ActionResult ares)
    {
        UIUtil.longToast(this, "通信処理が完了しました。");

        // 通信の結果を表示
        HttpPostResponse response = (HttpPostResponse)ares.get("http_response");
        if( response.isSuccess() )
        {
            tv2.setText( response.getText() );
        }
        else
        {
            tv2.setText( response.getErrMsg() );
        }
    }

引用元:HttpNetActivity.java
http://code.google.com/p/android-mvc-...

なお,コントローラの処理フローは,UIスレッドとは別のスレッド上で非同期で行なわれる。

したがって,その上に記述されるDB操作やHTTP通信などのビジネスロジックは,非同期を意識することなく,全く同期的に(単一スレッドモデルとして)記述できる。

■バリデーション・ロジックが,きわめて簡潔に記述できるようになった。

アクティビティ上の値の妥当性を検証するために,ユーザが記述するコードの量が,劇的に減った。

    /**
     * DB登録画面での入力値を検証
     */
    public ValidationResult validate(DBEditActivity activity)
    {
        initValidationOf(activity);

        assertNotEmpty("name");

        assertNotEmpty("age");
        assertValidInteger("age");
        assertNumberOperation("age", greaterThan(0));

        return getValidationResult();
    }

JUnit等でおなじみの,assert系のメソッドを並べるだけでいい。

このようにバリデーションメソッドが共通化された結果,

「○○は△△で入力してください。」のようなエラーメッセージは,フレームワーク内部で自動的に構築される事になった。

よって,検証処理自体も,検証結果の通知処理も,大幅に実装の手間が減った。

引用元:FuncDBValidation.java
http://code.google.com/p/android-mvc-...

ユーザが自由にバリデーションメソッドを追加する事も可能である。


なお,本機能のアイデアの着想については,下記ページの情報を参照のこと。

「バリデーション」APIと「単体テスト」APIの類似性,およびそのスタイルが時代と共に洗練される過程の概観
http://language-and-engineering.hatenablog.jp/entry/20120320/p1

■エンティティを含む任意の自作オブジェクトが,Intent経由で気軽に運搬可能になった。

IntentPortable(インテント経由で運搬可能である事を表すためのインタフェース)が導入された。

ユーザは任意のオブジェクトに対して,「implements IntentPortable」するだけで,そのオブジェクトをIntent経由で運搬可能になる。

エンティティやバリデーション結果など,フレームワーク内の主要なオブジェクトは,自動的に「IntentPortable」になる。

その結果,画面連携の負担が大幅に削減される。


下記にサンプルコードを掲載する。


Activity上で,送られてきたIntentからデータを取り出す側のコード:

        if( $.actionResultHasKey( "new_friend_obj" ) )
        {
            // Intentから情報を取得
            Friend f = (Friend)($.getActionResult().get("new_friend_obj"));

            // UIに表示
            tv2.text(f.getName() + "さんが,たった今新規登録されました。").visible();
        }

ビジネスロジックの実行結果やバリデーションの実行結果は,遷移先の画面に向かう際に,自動的にIntentの中に格納される。

つまり,処理の結果を遷移先の画面に運搬したい場合,ビジネスロジックの実行結果(ActionResultオブジェクト)に詰め込んでおけばよい。
ということになる。

もし手動で任意のオブジェクトを格納したい場合は,上述の通り,Router#goWithData などのメソッドを使えばよい。

引用元:DBListActivity.java
http://code.google.com/p/android-mvc-...

本機能の着想については,下記ページの情報を参照のこと。

Android SDKの,ParcelableとSerializableの違いを比較 - Intentで独自オブジェクトを運搬する際,役立つのはどちら?
http://language-and-engineering.hatenablog.jp/entry/20120313/p1

Model層

■ORMの機能が強化された。CRUDの処理は最初から組み込み済みになった。

RDBの操作は,CRUDのいずれも極めてシンプルかつ明快なコードになった。

下記のサンプルを見れば,一目瞭然であろう。

    // ------------ C --------------


    /**
     * 1人の友達を保存。
     */
    public Friend create(String name, Integer age, Boolean favoriteFlag)
    {
        // 論理エンティティを構築
        Friend f = new Friend();
        f.setName(name);
        f.setAge( age );
        f.setFavorite_flag( favoriteFlag );

        // DB登録
        f.save(helper);

        return f;
    }


    // ------------ R --------------


    /**
     * 友達を全て新しい順に返す。
     */
    public ArrayList<Friend> findAll()
    {
        return findAll(helper, Friend.class);
    }


    /**
     * 特定のIDの友達を1人返す。
     */
    public Friend findById(Long friend_id)
    {
        return findById( helper, Friend.class, friend_id );
    }

        // NOTE: 細かい条件で検索したい場合は,Finderを利用すること。
        // findAllやfindByIdの実装を参照。


    // ------------ U --------------


    /**
     * 既存の友達のお気に入り状態を反転させる。
     */
    public Friend invertFavoriteFlag( Long friend_id )
    {
        // idをもとに検索
        Friend f = findById( friend_id );

        // フラグを反転する
        f.setFavorite_flag( ! f.getFavorite_flag() );

        // DB更新
        f.save(helper);

        return f;
    }


    // ------------ D --------------


    /**
     * 特定のIDの友達を削除。
     */
    public void deleteById( Long friend_id )
    {
        Friend f = findById(friend_id);

        // DBからの削除を実行
        f.delete(helper);
    }

DAO自体が,findAll() とかfindById()みたいなメソッドを持つようになった。

またエンティティ自体,save()で新規登録と更新の両方が可能になり,delete()メソッドで自身の削除も可能となった。

DB操作のためのユーザのコード記述量は,圧倒的に削減された。

引用元:FriendDAO.java
http://code.google.com/p/android-mvc-...

それだけではなく,SELECT文を発行する際には,細かい条件を指定する事も可能である。

findAll()やfindById()の実装を見ればわかる。

    /**
     * レコードを全て新しい順に返す。
     */
    public ArrayList<T> findAll(DBHelper helper, Class<T> entity_class)
    {
        // 有効な湯キーを持つ全件を降順に
        return new Finder<T>(helper)
            .where("id > 0")
            .orderBy("id DESC")
            .findAll(entity_class)
        ;
    }

ユーザは,このコードを模倣して,任意の検索条件を持った検索メソッドを実装可能である。

引用元:BaseDAO.java
http://code.google.com/p/android-mvc-...

なお,このFinderオブジェクトの考案にあたっては,Ruby on Rails3のActiveRecordのエンジン「Arel」を意識した。

結び

以上が,ver0.2における改良点のサマリである。

M・V・Cの全レイヤについて,DSLが提供され,ユーザのコード記述量が減り,生産性が劇的に向上しているのを分かって頂ける事と思う。



ver0.1は「MVCの骨組み」であった。

ver0.2は,MVCの構造がよりいっそう強固になり,Androidアプリ開発における「スタンダードな設計方針」を示し,それを理想的な実装コードの形態で具現化した。


この調子でバージョンアップを重ねていけば,あたかも既存の部品を手早く組み合わせるだけで素早くアプリを完成できるような,魅力的なフレームワークになってゆくのは間違いない。

ぜひ,次バージョンも楽しみにして頂きたい。


(ここまで引用)





では以上をもって,「Android-MVC」バージョン0.2のリリース報告とさせて頂く。