今更ながらRoboGuiceを使ってみたので忘れずメモ。
RoboGuiceについて簡単に触れておくとGoogle GuiceベースのAndroid用のDIコンテナである。
roboguice - Project Hosting on Google Code
細かいViewの制御は抜きにして以下のようなアプリを例としてRoboGuiceを使用して作成してみる。
トップ画面より入力された名前を次の画面で文字列を付け足して表示する。
まず
前エントリーを参考にMavenプロジェクトからAndroidプロジェクトを作成。
pom.xmlを追記
- <dependency>
- <groupId>org.roboguice</groupId>
- <artifactId>roboguice</artifactId>
- <version>1.1.2</version>
- </dependency>
<dependency>
<groupId>org.roboguice</groupId>
<artifactId>roboguice</artifactId>
<version>1.1.2</version>
</dependency>
TopActivity
- public class TopActivity extends RoboActivity {
-
- @InjectResource(R.string.message)
- String message;
-
- @InjectView(R.id.txt_msg)
- TextView mTextView;
-
- @InjectView(R.id.edt_name)
- EditText mEditText;
-
- @InjectView(R.id.btn_decision)
- Button mButton;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
-
- setContentView(R.layout.top);
- mTextView.setText(message);
- mButton.setOnClickListener(new OnClickListener() {
-
- public void onClick(View v) {
- Intent intent = new Intent(TopActivity.this, HelloActivity.class);
- intent.putExtra("name", mEditText.getText().toString());
- startActivity(intent);
- }
- });
- }
-
- }
public class TopActivity extends RoboActivity {
@InjectResource(R.string.message)
String message;
@InjectView(R.id.txt_msg)
TextView mTextView;
@InjectView(R.id.edt_name)
EditText mEditText;
@InjectView(R.id.btn_decision)
Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// mTextView.setText(message); // ここでViewにアクセスするとエラー
setContentView(R.layout.top); // ここでViewがInjectされる
mTextView.setText(message);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(TopActivity.this, HelloActivity.class);
intent.putExtra("name", mEditText.getText().toString());
startActivity(intent);
}
});
}
}
RoboActivityを継承してアクティビティを作成する。
※1.1からGuiceActivityからRoboActivityに変更された
ListActivityの場合はRoboListActivity、TabActivityの場合はRoboTabActivityなどそれぞれ対応したもの
が存在する。
ソースを見るとわかる通り、@InjectView(resouceId)でViewをInjectしてくれる。
リソースを参照したい場合は@InjectResource(resourceId)を使用すればいい。
他にもこんなことが可能である。
Drawable icon = getResources().getDrawable(R.drawable.icon);
↓
@InjectResource(R.drawable.icon)
Drawable icon;
LocationManager loc = (LocationManager) getSystemService(Activity.LOCATION_SERVICE);
↓
@Inject
LocationManager loc;
HelloActivityを実装する前にインターフェースと実装クラスを使用して
少しDIっぽい処理を入れてみる。
RoboSampleService
- public interface RoboSampleService {
-
- public String hello(String name);
- }
public interface RoboSampleService {
public String hello(String name);
}
RoboSampleServiceImpl
- public class RoboSampleServiceImpl implements RoboSampleService {
-
- public String hello(String name) {
- return "Hello " + name;
- }
-
- }
public class RoboSampleServiceImpl implements RoboSampleService {
public String hello(String name) {
return "Hello " + name;
}
}
続いて上記サービスをバインド
MyApplication
- public class MyApplication extends RoboApplication {
-
- @Override
- protected void addApplicationModules(List<Module> modules) {
- modules.add(new MyModule());
- }
-
- static class MyModule extends AbstractAndroidModule {
-
- @Override
- protected void configure() {
-
- bind(RoboSampleService.class).to(RoboSampleServiceImpl.class);
- }
-
- }
- }
public class MyApplication extends RoboApplication {
@Override
protected void addApplicationModules(List<Module> modules) {
modules.add(new MyModule());
}
static class MyModule extends AbstractAndroidModule {
@Override
protected void configure() {
// RoboSampleServiceのInject要求に対してRoboSampleServiceImplを返すようバインドする
bind(RoboSampleService.class).to(RoboSampleServiceImpl.class);
}
}
}
※こちらもGuiceApplicationからRoboApplicationに変更されているので注意
アプリケーションのエントリポイントとして上記クラスを呼び出す必要があるので
AndroidManifest.xmlに以下を追加
<application...
android:name=".MyApplication"
...>
最後にHelloActivityの実装
HelloActivity
- public class HelloActivity extends RoboActivity {
-
- @InjectExtra("name")
- String name;
-
- @InjectView(R.id.hello_msg)
- TextView textView;
-
- @Inject
- RoboSampleService service;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.hello);
- textView.setText(service.hello(name));
- }
-
- }
public class HelloActivity extends RoboActivity {
@InjectExtra("name")
String name;
@InjectView(R.id.hello_msg)
TextView textView;
@Inject
RoboSampleService service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hello);
textView.setText(service.hello(name));
}
}
@InjectExtra("name")でTopActivityでputExtraした値が入る。
また、RoboSampleServiceに@Injectアノテーションをつけることで上記でバインドした
RoboSampleServiceImplをInjectしてくれるのでインスタンスを生成せず使用することが出来る。
続いてDIの長所を活かしてHelloActivityのテストを書いてみる。
テストプロジェクトを作成する前にテスト対象のプロジェクトのクラスパスをエクスポート。
通常通りAndroidテストプロジェクトを作成する。
今回作成したサンプルプログラムのhelloメソッドは静的な文字列を付加するだけの
シンプルな作りであるが、実際は動的であったり、バグが混入していたり、
決まっていなかったりするので依存するクラスの振る舞いを固定化するモックを作成する。
MockServiceImpl
- public class MockServiceImpl implements RoboSampleService {
-
- @Override
- public String hello(String name) {
- return "test";
- }
-
- }
public class MockServiceImpl implements RoboSampleService {
@Override
public String hello(String name) {
return "test";
}
}
続いてテストコードの実装
HelloActivityTest
- public class HelloActivityTest extends RoboActivityUnitTestCase<HelloActivity> {
-
- private Context mContext;
-
- public HelloActivityTest() {
- super(HelloActivity.class);
- }
-
- class MockApplication extends RoboApplication {
-
- public MockApplication(Context context) {
- super();
- attachBaseContext(context);
- }
-
- @Override
- protected void addApplicationModules(List<Module> modules) {
- modules.add(new MockModule());
- }
-
- }
-
- class MockModule extends AbstractAndroidModule {
-
- @Override
- protected void configure() {
-
- bind(RoboSampleService.class).to(MockServiceImpl.class);
- }
-
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getContext();
- setApplication(new MockApplication(mContext));
- }
-
- @MediumTest
- public void testShouldBeHelloMsg() {
- Intent intent = new Intent(mContext, HelloActivity.class);
- intent.putExtra("name", "name");
- HelloActivity a = startActivity(intent, null, null);
- assertNotNull(a);
-
-
- assertEquals(((TextView)a.findViewById(R.id.hello_msg)).getText(), "test");
- }
- }
public class HelloActivityTest extends RoboActivityUnitTestCase<HelloActivity> {
private Context mContext;
public HelloActivityTest() {
super(HelloActivity.class);
}
class MockApplication extends RoboApplication {
public MockApplication(Context context) {
super();
attachBaseContext(context);
}
@Override
protected void addApplicationModules(List<Module> modules) {
modules.add(new MockModule());
}
}
class MockModule extends AbstractAndroidModule {
@Override
protected void configure() {
// RoboSampleServiceのInject要求に対してMockServiceImplを返すようバインドする
bind(RoboSampleService.class).to(MockServiceImpl.class);
}
}
@Override
protected void setUp() throws Exception {
super.setUp();
mContext = getInstrumentation().getContext();
setApplication(new MockApplication(mContext));
}
@MediumTest
public void testShouldBeHelloMsg() {
Intent intent = new Intent(mContext, HelloActivity.class);
intent.putExtra("name", "name");
HelloActivity a = startActivity(intent, null, null);
assertNotNull(a);
// Mockで返す文字列を"test"にしているため
assertEquals(((TextView)a.findViewById(R.id.hello_msg)).getText(), "test");
}
}
RoboActivityUnitTestCase<対象アクティビティ>を継承してテストコードを作成する。
RoboSampleServiceのInject要求に対してモックを返すようにバインドさせ、
setApplicationでセットすることで依存したオブジェクトの振る舞いを固定化させることが出来る。
Run As -> Android JUnit Testで...
これで一通り完了。案外使いやすいかも?