RecyclerViewの実装をMVPアーキテクチャベースで実装する.
MVPの実装を助けるライブラリとしてはmortarとdaggerを使用する.
MainApp.java
アプリケーションスコープのMortarScopeを提供するためgetSystemService
をオーバライドする.
このMortarScopeはDaggerのObjectGraphをObjectGraphServiceとしてアプリケーションスコープの粒度でアプリ内に提供する.
public class MainApp extends Application {
private MortarScope rootScope;
@Override
public Object getSystemService(String name) {
if (rootScope == null) {
rootScope = MortarScope.buildRootScope()
.withService(ObjectGraphService.SERVICE_NAME, ObjectGraph.create(new RootModule()))
.build("Root");
}
return rootScope.hasService(name) ? rootScope.getService(name) : super.getSystemService(name);
}
}
IDEの設定によってはgetSystemService
の引数に渡せる定数を縛っていため警告が表示されるので無効化するか警告のレベルを下げておく.
RootModule.java
今回はDIライブラリとしてDaggerを採用している. Daggerのためにルートモジュールを作成しておく.
@Module(
injects = MainRecyclerView.class
)
public class RootModule {
@Provides
@Singleton
public MainPresenter provideMainPresenter() {
return new MainPresenter();
}
}
MainActivity.java
ActivityはMVPでいうところのViewに位置する. このサンプルではActivityは単なるアクティビティスコープを提供するコンポーネントに過ぎない.
PresenterのためにアクティビティスコープはBundleServiceRunnerを提供する.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Return the identifier of the task this activity is in.
// This identifier will remain the same for the lifetime of the activity.
// Return Task identifier, an opaque integer.
String scopeName = getLocalClassName() + "-task-" + getTaskId();
MortarScope parentScope = MortarScope.getScope(getApplication());
activityScope = parentScope.findChild(scopeName);
if (activityScope == null) {
activityScope = parentScope.buildChild()
.withService(BundleServiceRunner.SERVICE_NAME, new BundleServiceRunner())
.build(scopeName);
}
BundleServiceRunner.getBundleServiceRunner(this).onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public Object getSystemService(String name) {
return activityScope != null && activityScope.hasService(name) ? activityScope.getService(name)
: super.getSystemService(name);
}
MainRecyclerView.java
RecyclerViewを拡張し, Presenterと関連できるMainRecyclerViewを定義する.
public class MainRecyclerView extends RecyclerView {
@Inject
MainPresenter presenter;
MainRecyclerViewはMVPでいうViewに位置するため, ViewHolderとそれを更新するメソッドもこのクラスに含めておく.
static class MainViewHolder extends RecyclerView.ViewHolder {
private TextView titleTextView;
private TextView summaryTextView;
public MainViewHolder(View itemView) {
super(itemView);
titleTextView = (TextView) itemView.findViewById(android.R.id.text1);
summaryTextView = (TextView) itemView.findViewById(android.R.id.text2);
}
// called from Presenter.
public void setText(String title, String summary) {
titleTextView.setText(title.toUpperCase());
summaryTextView.setText("-" + summary);
}
}
RecyclerView自体のレイアウト定義もこのクラスの責務になる.
setLayoutManager
でのレイアウト指定はコンストラクタで済ませておく.
ただし, AdapterについてはModelとの関連やビジネスロジックを含むためPresenter側に定義する.
public MainRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
// レイアウトの決定はViewの責務.
this.setLayoutManager(new LinearLayoutManager(context));
// Modelとの関連づけはPresenterの責務
// this.setHasFixedSize(false);
// this.setAdapter(recyclerViewAdapter);
ObjectGraphService.inject(context, this);
}
RecyclerViewのリストアイテムを選択した場合のイベントはPresenterに伝える.
private final OnClickListener itemClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = MainRecyclerView.this.getChildPosition(v);
// Delegate to Presenter
presenter.onItemSelected(position);
}
};
public MainViewHolder createViewHolder(ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(android.R.layout.simple_list_item_2, parent, false);
v.setOnClickListener(itemClickListener);
return new MainViewHolder(v);
}
あとはMortarでお決まりのコードを書いておく.
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
presenter.takeView(this);
}
@Override
protected void onDetachedFromWindow() {
presenter.dropView(this);
super.onDetachedFromWindow();
}
MainPresenter.java
最後にPresenter. こちらはRecyclerViewのAdapterに相当する責務を書く.
RecyclerViewをセットアップし,
@Override
protected void onLoad(Bundle savedInstanceState) {
MainRecyclerView recyclerView = getView();
recyclerViewAdapter = new RecyclerViewAdapter(getView());
// Modelとの関連づけはPresenterの責務
recyclerView.setHasFixedSize(false);
recyclerView.setAdapter(recyclerViewAdapter);
// レイアウトの決定はViewの責務
// recyclerView.setLayoutManager(new LinearLayoutManager(context));
リストアイテムが選択されたときの処理を記述し,
public void onItemSelected(int position) {
Log.i("yuki", "Item Selected! " + datasource.get(position));
}
Adapterの処理を追加して仕上げる.
private class RecyclerViewAdapter
extends RecyclerView.Adapter<MainRecyclerView.MainViewHolder> {
private MainRecyclerView recyclerView;
RecyclerViewAdapter(MainRecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
@Override
public MainRecyclerView.MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return recyclerView.createViewHolder(parent);
}
@Override
public void onBindViewHolder(MainRecyclerView.MainViewHolder view, int position) {
view.setText(datasource.get(position), datasource.get(position));
}
@Override
public int getItemCount() {
return datasource.size();
}
}
当然ModelとViewへの参照も持つ.
public class MainPresenter extends ViewPresenter<MainRecyclerView> {
private RecyclerViewAdapter recyclerViewAdapter;
private List<String> datasource
= Arrays.asList("data1", "data2", "data3", "data4", "data5", "data6", "data7", "data8", "data9");
以上.