2015/06/23

MortarScopeのためのMock

Mortarを採用したプロジェクトでは各所でMortarScopeが必要になる.
MortarScopeはContext.getSystemServiceを経由して取得されるが, InstrumentationRegistry.getTargetContext()で取得できるContextにはこれが実装されていないためMockオブジェクトを用意する必要がある.

以下はMotar/PresenterをテストするためのMortar/Viewのmockクラス.
MortarScopeを生成するためオリジナルのContextをContext.getSystemServiceのMock Methodを実装したContextWrapperでラップする.

あとはお決まりのEnter/ExitScope処理を実装すればMockは完成する.

private static class Viewer implements MyPresenter.MyViewer {
  private Context context;
  private MortarScope mortarScope;
  private MyPresenter presenter;

  public Viewer(Context context, MyPresenter presenter) {
    // MortarScopeを返すラッパーを定義する. 
    this.context = new ContextWrapper(context) {
      @Override
      public Object getSystemService(String name) {
        return mortarScope != null && mortarScope.hasService(name) ?
            mortarScope.getService(name) : super.getSystemService(name);
      }
    };
    this.presenter = presenter;
  }

  public void onCreate() {
    // お決まりのスコープ定義とEnterScope処理. 
    String scopeName = "UnitTestScope";
    mortarScope = MortarScope.buildRootScope().build("Root").buildChild()
        .withService(BundleServiceRunner.SERVICE_NAME, new BundleServiceRunner())
        .withService(ObjectGraphService.SERVICE_NAME, ObjectGraph.create(new AppHomeModule()))
        .build(scopeName);
    BundleServiceRunner.getBundleServiceRunner(getContext()).onCreate(null);

    presenter.takeView(this);
  }

  public void onDestroy() {
    // お決まりのExitScope処理. 
    presenter.dropView(this);
  }

  // MyPresenter.MyViewerの抽象メソッドを実装
  @Override
  public Context getContext() {
    return context;
  }
}

Mortar/Viewの生成と破棄はsetup/tearDownで実装した.

@Before
public void setup() throws Exception {
  presenter = new MyPresenter();
  viewer = new Viewer(InstrumentationRegistry.getTargetContext(), presenter);
  viewer.onCreate();
}

@After
public void tearDown() {
  viewer.onDestroy();
}

以上.

2015/06/14

RecyclerViewでRealmResultを扱う

Realmはクエリ結果となるRealmResultをListViewで表示するためのアダプタとしてRealmBaseAdapterを用意している.
ListViewを使用する場合は問題ないがRecyclerViewではこれを使用できない.

RealmResultをRecyclerViewで使用するためのアダプタのベースを書いた.
RealmRecyclerViewAdapter.java

import android.content.Context;
import android.support.v7.widget.RecyclerView;

import io.realm.RealmObject;
import io.realm.RealmResults;

public abstract class RealmRecyclerViewAdapter<T extends RealmObject, VH extends RecyclerView.ViewHolder>
    extends RecyclerView.Adapter<VH> {

  protected RealmResults<T> realmResults;
  protected Context context;

  public RealmRecyclerViewAdapter(Context context, RealmResults<T> realmResults) {
    if (context == null) {
      throw new IllegalArgumentException("Context cannot be null");
    }
    if (realmResults == null) {
      throw new IllegalArgumentException("RealmResults cannot be null");
    }

    this.realmResults = realmResults;
    this.context = context;
  }

  @Override
  public int getItemCount() {
    return realmResults.size();
  }
}

RecyclerView.Adapterを実装する際は上記アダプタを継承し, オリジナルのアダプタを作成する.

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import io.realm.RealmResults;

public class ModuleAdapter extends RealmRecyclerViewAdapter<Module, ModuleAdapter.ViewHolder> {

  static class ViewHolder extends RecyclerView.ViewHolder {
    private TextView value, id;

    public ViewHolder(View v) {
      super(v);
      value = (TextView) v.findViewById(android.R.id.text1);
      id = (TextView) v.findViewById(android.R.id.text2);
    }
  }

  public ModuleAdapter(Context context, RealmResults<Module> realmResults) {
    super(context, realmResults);
  }

  @Override
  public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View v = LayoutInflater.from(context)
        .inflate(android.R.layout.simple_list_item_2, parent, false);
    return new ViewHolder(v);
  }

  @Override
  public void onBindViewHolder(ViewHolder view, int position) {
    Module module = realmResults.get(position);
    view.id.setText(module.getId());
    view.value.setText("value:" + module.getValue());
  }
}

以上.