Mortar library
この章はmotar - GitHubの内容を参考に筆者の解釈のもと多くの加筆・修正が加えられています.
Mortarについての正確な情報を確認するには上記のオリジナルページをご確認ください.
一言で言うと, MortarはAndroidの厄介なライフサイクルをいい具合に隠蔽してくれるライブラリだ.
Mortarは(Fragmentが目指した)モジュールユニットなView層を提供するアーキテクチャの基礎となり, DaggerによるObjectGraphと柔軟なScopeの概念によりActivityのライフサイクルからPresenterを分離しMVPを促進する.
MortarはDaggerのObjectGraphあるいはDagger2のComponentsをSystemServiceかのような形式で提供することもできる. もちろん不要であれば気にする必要はない. Daggerは完全に分離されている.
Mortarでは全ての管理をSingletonなMortarScope
が行う.
MortarScope
はMortarの肝になる概念で, オブジェクトにその名の通り”スコープ”を与える. 通常はApplicationやActivityといった範囲でスコープを割り当てる. 重要なのは”Activity”がスコープなのであって”Activityのライフサイクル”がスコープではないということだ.
Activityを実装するケースでは, Activityのライフサイクル単位をスコープとするのではなく, ConfigurationChangeやRecreateといったAndroid Framework都合のライフサイクルを超えた”Activity”をスコープとすることが多い.
つまり, より抽象的には”1画面”をスコープとしたいのだ. MotarScope
はこういったスコープの制御を提供してくれる.
MortarScopeは通常Application
のコンテキストで保持する. あるいは独自の短命なスコープを持った一過性のオブジェクトとして定義することも可能だ. スコープはサブスコープとしてネストさせることもできる. ネストされたスコープはより上位のスコープを隠蔽し透過的に扱える.
例えば短命なスコープを持つウィザード画面へのオブジェクトグラフを形成するには次のように記述できる.
// ObjectGraphServiceは内部でgetSystemServiceを呼んでいるに過ぎない.
ObjectGraphService.inject(getContext(), this)
Mortarが提供する強力なプリインサービスの1つにBundleService
がある.
このサービスはView(あるいはActivity Contextにアクセスするオブジェクト)がActivityの持つBundleへ安全にアクセスできる機能を提供する.
MVPアーキテクチャのアプローチではBundleService
上でPresenterを構築し持続性を持たせる.
前述の通りPresenterはViewからは完全に分離され, Activityのライフサイクル(ConfigurationChangeやRecreate)とは無縁の関係にあるべきだ. BundleService
がViewへのシンプルで安全なアクセスを提供してくれる.
The Big Picture
アプリケーションはsingletonなMortarScope
を持つことが通常である.
MortarScope
はgetSystemService
メソッドをデリゲートし次のように機能させる.
public class MyApplication extends Application {
private MortarScope rootScope;
@Override public Object getSystemService(String name) {
if (rootScope == null) rootScope =
MortarScope.buildRootScope().build();
return rootScope.hasService(name) ?
rootScope.getService(name) : super.getSystemService(name);
}
}
これでsingletonなコアサービスとしてのスコープを定義できる.
スコープはサブスコープを持つことができる. サブスコープはスコープの登録/解除を対で呼ぶ責任を負う.
Scoped.onExnterScope(MortarScope)
Scoped.onExitScope(MortarScope)
スコープの中でDaggerが持つObjectGraphのように他のサービスを提供するには次のように記述する.
@Override public Object getSystemService(String name) {
if (rootScope == null) {
rootScope = MortarScope.buildRootScope()
.with(ObjectGraphService.SERVICE_NAME,
ObjectGraph.create(new RootModule()))
.build();
}
return rootScope.hasService(name) ?
rootScope.getService(name) : super.getSystemService(name);
}
Context
をこのObjectGraphに登録するにはMortarScopeにプリインされているサービスObjectGraphService
を使えば容易にできる.
public class MyView extends LinearLayout {
@Inject SomeService service;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
ObjectGraphService.inject(context, this);
}
}
MortarはActivityのライフサイクルに合うサブスコープを形成するにはActivityのgetSystemService
メソッドでこれを構築する. ActivityでMortarScopeを構築する際にはBundleServiceRunner
を設定しておく. ActivityのConfigurationChangeやRecreateによる厄介な問題をBundleServiceRunner
は手助けする.
BundleServiceRunner
のBundleに必要な情報を詰め込み, Activityが然るべきタイミングでBundleServiceRunnerのonCreate
とonSaveInstanceState
を呼び出すことで情報のsave/restoreを可能にする.
public class MyActivity extends Activity {
private MortarScope activityScope;
@Override public Object getSystemService(String name) {
MortarScope activityScope =
MortarScope.findChild(getApplicationContext(), getScopeName());
if (activityScope == null) {
activityScope = MortarScope.buildChild(getApplicationContext(), getScopeName())
.withService(BundleServiceRunner.SERVICE_NAME, new BundleServiceRunner())
.withService(HelloPresenter.class.getName(), new HelloPresenter())
.build();
}
return activityScope.hasService(name) ?
activityScope.getService(name) : super.getSystemService(name);
}
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BundleServiceRunner.getBundleServiceRunner(this).onCreate(savedInstanceState);
setContentView(R.layout.main_view);
}
@Override protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
BundleServiceRunner.getBundleServiceRunner(this).onSaveInstanceState(outState);
}
@Override protected void onDestroy() {
if (isFinishing()) {
MortarScope activityScope = findChild(getApplicationContext(), getScopeName());
if (activityScope != null) activityScope.destroy();
}
super.onDestroy();
}
}
View.onSaveInstanceStateで求められるようなParcellable
化の手間はもはや必要なくなる.
MortarScopeを得た今, 多くのオブジェクトがBundleService
にアクセスできる状態である.
つまり, save/restoreはBundleServiceRunner
に任せることができる状態である.