2011年9月19日月曜日

RESTのFeedを一覧表示するActivityの生成

8月27,28日に開催されたクラウド温泉@小樽のセッション、「クラウドアプリケーション(App Engine&Android)自動生成 - SimpleModeler/g3/g4デモ」でのSimpleModelerからAndroidアプリの自動生成のデモの話の続きです。

SimpleModelerでは、アプリケーション開発のベースとなるドメインモデルの実装を「自動コーディング」します。この部分はドメインモデルが決まれば、プラットフォーム上での実装はほぼ決まるので自動生成の格好のターゲットです。逆に、この部分を手組みでコーディングしていると相当の工数が必要となります。プログラム開発の生産性を上げるためには、この部分の生産性向上が重要になってきます。

ただし、エンティティに対するCRUD処理については自動生成である程度のモノを生成可能です。アプリケーション部分のサンプルという意味もこめて、データに対するCRUD処理を行なうActivityを生成する予定で、一部実現しています。

現在SimpleModelerが自動生成しているのは、サーバー上に格納されているエンティティCustomerをREST経由でアクセスして一覧表示を行うプログラムが以下のCustomerRestViewActivityです。

  1. package com.demo;  
  2.   
  3. import android.content.Context;  
  4. import android.os.Bundle;  
  5. import java.math.*;  
  6. import java.util.*;  
  7. import org.goldenport.android.*;  
  8. import org.goldenport.android.traits.ListViewTrait;  
  9.   
  10. public class CustomerRestViewActivity extends GActivity<DemoController> {  
  11.       
  12.     // @LayoutView(R.id.header)  
  13.     // TextView mHeader;  
  14.     // @ResourceString(R.string.header)  
  15.     // String mHeaderLabel;  
  16.     // @ResourceColor(R.color.header)  
  17.     // Color mHeaderColor;  
  18.     // @IntentExtra("message")  
  19.     // String mMessage;  
  20.       
  21.     public CustomerRestViewActivity() {  
  22.         addTrait(new ListViewTrait());  
  23.     }  
  24.       
  25.     @Override  
  26.     protected void onCreate(Bundle savedInstanceState) {  
  27.         super.onCreate(savedInstanceState);  
  28.     }  
  29.       
  30.     @Override  
  31.     protected int get_Layout_id() {  
  32.         return R.layout.customer_rest_view;  
  33.     }  
  34.       
  35.     @Override  
  36.     protected void onStart() {  
  37.         super.onStart();  
  38.     //    if (mMessage != null) {  
  39.     //        mHeader.setText(mMessage);  
  40.     //    } else {  
  41.     //        mHeader.setText(mHeaderLabel);  
  42.     //    }  
  43.         set_list_adapter(gcontroller.getCustomerRestFeedAdapter());  
  44.     }  
  45. }  

GActivity


CustomerRestViewActivityは、サーバー上に格納されているエンティティCustomerをRESTでアクセスしてListViewの一覧として表示するActivityです。基底クラスはGActivityで、型パラメータとしてDemoアプリケーションのコントローラであるDemoControllerを指定しています。

org.goldenport.android.GActivityが、g4におけるActivityの基底クラスです。主に以下の機能を提供しています。

  • DI
  • コントローラクラスの自動バインド
  • トレイトによる機能拡張
  • 非同期更新処理

DI(Dependency Injection)

g4では、Google Guice (without AOP)を用いたDIコンテナ機能を提供しています。XMLで定義された各種ViewやリソースをActivityのインスタンス変数に自動インジェクトすることができます。
自動生成されたCustomerRestViewActivityを、自前で拡張するためのヒントとして以下のコメントが入っています。
この機能は次回の記事で説明することにします。
  1. // @LayoutView(R.id.header)  
  2.     // TextView mHeader;  
  3.     // @ResourceString(R.string.header)  
  4.     // String mHeaderLabel;  
  5.     // @ResourceColor(R.color.header)  
  6.     // Color mHeaderColor;  
  7.     // @IntentExtra("message")  
  8.     // String mMessage;  

コントローラクラスの自動バインド

g4では、画面表示を行うクラスであるGActivity(Activityのサブクラス)から、アプリケーション・ロジックを分離してコントローラとして実装します。コントローラはGControllerのサブクラスになります。
Activityからアプリケーションロジックを分離して、コントローラ側で実現することにより、以下の効果を期待しています。
  • アプリケーション全体のロジックをコントローラに集中させることで、アプリケーションの見通しを良くし、拡張性、保守性を高める。
  • GUIを経由しないで、アプリケーションロジックのユニットを可能にする。
  • アプリケーションロジックを複数のActivityから共用できる。
GActivityでは、コントローラクラスを自動的にインジェクトするので、GActivityの実装では、特に初期化処理なしでコントローラを参照して使うことができます。 
生成されたonStartメソッドにはヒントのコメントがあります。この不要なコメントを削除したonStartメソッドは以下のものになります。
  1. @Override  
  2.     protected void onStart() {  
  3.         super.onStart();  
  4.         set_list_adapter(gcontroller.getCustomerRestFeedAdapter());  
  5.     }  
「super.onStart()」はお約束。
set_list_adapterメソッドで、コントローラから返されるListAdapterを設定しています。
GActivityで定義しているインスタンス変数gcontrollerから参照しているコントローラ(DemoController)から、ドメインエンティティCustomerをRESTのフィードとしてアクセスするListAdapterを取得しています。
このListAdapterは、バックエンドにページング付きのREST通信と通信結果のキャッシュ機能を持っています。この機能を実現するために、SimpldeModelerが生成するAndroidコードで説明したとおり、ActivityからRESTドライバにいたるまで相当数のコードが自動生成されています。また、g4ベースでサーバサイドのコードも自動生成していることは、SimpleModeler (クラウド温泉@小樽)で触れました。このあたりのメカニズムはいずれ紹介したいと思います。

トレイトによる機能拡張

ActivityにListViewやGralleryなどを操作するロジックをハードコーディングしてしまうと、ロジックを他の目的に再利用させることができません。
これらの機能を持つ基底クラスを作るのが次善策ですが、拡張性や保守性に問題があります。
この問題を解決するために、g4ではScalaのトレイトライクなメカニズムを導入しました。
ListViewTraitは、ListViewを操作する機能を持つトレイトです。GActivityのaddTraitメソッドによりListViewTraitを追加することによりGActivityにListViewを操作する機能が追加されます。
以下のようにコンストラクタでトレイトListViweTraitを追加しています。
  1. public CustomerRestViewActivity() {  
  2.         addTrait(new ListViewTrait());  
  3.     }  

非同期更新処理

ListViewTraitが内部で自動的にハンドリングしているので、CustomerRestViewActivityには直接みえていません。
ListViewTraitが、一覧データのローディング時にプログレスバーを画面上に表示し、ローディングが完了した時点で表示を終了する処理を行っています。

2011年9月1日木曜日

SimpldeModelerが生成するAndroidコード

8月27,28日に開催されたクラウド温泉@小樽では、「クラウドアプリケーション(App Engine&Android)自動生成?SimpleModeler/g3/g4デモ」のセッションで、SimpleModelerからAndroidアプリの自動生成のデモを行いました。

クラウド温泉@小樽に向けてブログに書いてきたものを実演しました。

デモに使ったモデルは以下のCSV、demo.csvをコンバートして生成したDEACustomer.java、DEEBuy.java、DERGoods.javaの3つのScala DSLで記述したものです。

demo.csv
  1. #actor,parts,attrs  
  2. customer,,phone  
  3. #resource  
  4. goods,,note  
  5. #event  
  6. buy,customer;goods  
そのうちの一つ、DEACustomer.javaのソースコードは以下のものです。DEEBuy.javaとDERGoods.javaもだいたい同じコードになります。

DEACustomer.java
  1. package com.demo  
  2.   
  3. import org.simplemodeling.dsl._  
  4. import org.simplemodeling.dsl.datatype._  
  5. import org.simplemodeling.dsl.domain._  
  6. import org.simplemodeling.dsl.domain.values._  
  7.   
  8. case class DEACustomer extends DomainActor {  
  9.   term = "customer"  
  10.   caption = "customer"  
  11.   brief = <t></t>  
  12.   description = <text></text>  
  13.   
  14.   id("customerId", DVICustomerId())  
  15.   attribute("name", DVNCustomerName())  
  16.   attribute("summary", XString)  
  17.   attribute("phone", XString)  
  18. }  
  19.   
  20. case class DVICustomerId extends DomainValueId {  
  21.   term = "customerId"  
  22.   caption = "customerId"  
  23.   brief = <t></t>  
  24.   description = <text></text>  
  25.   
  26.   attribute("value", XString)  
  27. }  
  28.   
  29. case class DVNCustomerName extends DomainValueName {  
  30.   term = "customerName"  
  31.   caption = "customerName"  
  32.   brief = <t></t>  
  33.   description = <text></text>  
  34.   
  35.   attribute("value", XString)  
  36. }  
デモでは、このモデルからg4上で動作するJavaソースコードを生成し、Androidアプリケーションとして動作させました。Javaソースコードの生成は、一部デモに間に合わなかったものもあったので、その後、少し改良しました。その改良版では、以下のソースコード(33ファイル、4.7Kステップ)を生成します。
  • BuyRestFeedAdapter.java
  • BuyRestFeedRepository.java
  • BuyRestViewActivity.java
  • CustomerRestFeedAdapter.java
  • CustomerRestFeedRepository.java
  • CustomerRestViewActivity.java
  • DDBuy.java
  • DDCustomer.java
  • DDGoods.java
  • DEACustomer.java
  • DEEBuy.java
  • DERGoods.java
  • DVIBuyId.java
  • DVICustomerId.java
  • DVIGoodsId.java
  • DVNCustomerName.java
  • DVNGoodsName.java
  • DemoAgent.java
  • DemoApplication.java
  • DemoContext.java
  • DemoContract.java
  • DemoController.java
  • DemoErrorModel.java
  • DemoFactory.java
  • DemoG3Driver.java
  • DemoModel.java
  • DemoModule.java
  • DemoProvider.java
  • DemoRepository.java
  • GoodsRestFeedAdapter.java
  • GoodsRestFeedRepository.java
  • GoodsRestViewActivity.java
  • IDemoRestDriver.java
このJavaプログラムをクラス図にすると以下のようになります。
BuyRestViewActivity, CustomerRestViewActivity, GoodsRestViewActivityがAndroidの画面クラスであるActivity(Androidの画面クラス)です。これらのActivityがサーバー上に格納されているデータを画面に表示します。
これらのActivityからDemoControllerを経由して、ドメインモデルを管理するクラスDemoModelにアクセスします。データの管理は、サーバーから取得したフィードデータをメモリ上に保持するクラスBuyRestFeedRepository, CustomerRestFeedRepository, GoodsRestFeedRepositoryで行い、これらのクラスをAndroidのウィジェットであるListView用のアダプタBuyRestFeedAdapter, CustomerRestFeedAdapter, GoodsRestFeedAdapterが使用します。
また、サーバーとの通信はDemoG3Driverが行います。今回のデモではサーバー側はg3なので、g3と通信するためのドライバを使用していますが、ドライバはインタフェースIDemoRestDriverを実装していれば取り替え可能な構造になっています。
クラス図の下に、Documentオブジェクト(DTO)であるDDCustomer, DDBuy, DDGoodsがありますが、これらのオブジェクトでデータのやりとりを行います。
クラス図のざっくりとした説明は以上の通りです。追々詳細な情報を書いていく予定です。
ここで強調したいのは、(トイプログラムではなくて)ある程度本格的なアプリケーションを作成する場合、ごく小さなドメインモデルからでも、これらのクラス群を作成しなければならないということです。一般的なJavaプログラムでも必要なものもありますし、Android特有のクラスもありますが、いずれにしても相当量のコーディングが必要になります。しかも、これらのクラス群はドメインモデルが決まれば、Android上でどう実装するのかというのも、ほとんど決まってしまうので、人力でコーディングするのは、かなりもったいない作業です。こういった定型部分を自動コーディングしてしまうことがSimpleModelerの目的です。