FlowとMortarについて

Thorben Primkeが、Squareが開発したFlowMortarという2つのライブラリをBay Area Android Devグループで紹介しました。彼はライブラリについてだけでなく、サンプルアプリケーションや、彼がお決まりのコードの使用を減らしてライフサイクルイベントを処理するのに活用しているヘルパークラスについてもディスカッションしました。


背景 (0:00)

この講演はSquareが開発した2つのライブラリ、FlowMortarについてのものです。私はこれらを約1年前、Jellyで働いていた時に使い始めました。まず初めに、なぜ私がこのライブラリを使い始めたのか、どのようにそれを導入したのかについてお話しします。

まず簡単なFlowの概要として、フラグメントを、通常のビューと比較してお話します。それから、FlowとMortarについての詳細な話を少しします。そして、少し突っ込んで、ライブラリのコンポーネントについて、何が興味深くて、どのように動いているのかをお見せします。最後に、このライブラリには含まれてはいませんが、このライブラリがプロジェクトを構築する上で役立ついくつかの事柄をお伝えします。

Jelly / Super (1:00)

Jelly 社のオリジナルのアプリ、JellyはQ&Aアプリです。私はFlowとMortarを使用して、当時のエンジニアとそのアプリを制作しました。それから、私たちは次期バージョンであるSuperの計画に入り、より面白みのあるアプリを求め作り始めました。

私たちはフラグメントと少数のアクティビティを使用することに戻ることもできましたが、すでに約1か月間、FlowとMortarを使用していたのと、それらが提供してくれたビュー同士の分離性の方を私は気に入ってしまいました。

フラグメント vs ビュー (2:10)

フラグメントを扱う多くの場合、ビューロジックとビジネスロジックをコード内に混在させがちです。数年前、私はあるアプリの速度を改善するために、いくつかのアクティビティをフラグメントに書き換えました。アクティビティが作成されるときの負荷をなくして、スレッドビューからのトランジションを高速に行うことを望んでいました。

それから詳細ビューや、マップのビューにも着手して、階層化されたフラグメントを使い始めました。Androidのフラグメントのライフサイクルは、アクティビティのライフサイクルよりも多くのステップがあり複雑なので、私たちはいくつかの箇所でつまづきました。

フラグメントマネージャはフラグメントの順序を扱うのを手伝ってくれるでしょう。私たちが追加したフラグメントが、その順序通りに正しく表示されないこともあるので、それらを調整しなければなりません。

Flow / Mortar

Flow (3:37)

Flowはスクリーンの記述と、ナビゲーションのためにバックスタックを扱う方法を提供してくれます。フラグメントでは、あなたのフラグメントを管理するためにFragmentManagerを使用します。ここでは、Flowがあなたのスクリーンを管理し、どのスクリーンが表示されるべきかを決定します。

Flowはまた、スクリーンのヒストリーも管理し、もしアプリがバックグラウンドになった場合にも状態を持続させます。そしてあなたのアプリに戻るとすぐに、Flowはきれいに、そのヒストリーとスクリーンを再構築してくれるでしょう。

単一のフローにはサブフローを含めることができます。あなたが階層化されたフラグメントを扱えるのと同様に、階層化されたフローを扱うことができるのです。そして例えば、メインのフローと、そこに編集のフローとログインのフローがあるようなケースに利用する場合、これらはしっかりと分離され、お互いに影響することはありません。メイン以外の場所にフローがある状態で、私たちはきちんとその状態を管理することができます。

Mortar (5:05)

Mortarとは、コンテキストのgetSystemServiceと統合された、アプリケーションライフサイクルの上を覆うものです。これは依存性の注入において非常に素晴らしい働きをします。

MortarはMortarScopeを使用し、必ずしもDaggerやその他のシステムと一緒に動作する必要はありません。私のケースでは、プレゼンター、ビューなどの全てに、依存性を与えるために、Daggerの持つ依存性注入をMortarと一緒に利用しています。もしすべての処理がひとつのアクティビティの中にある場合、いくつかのあなたのクラスは永続した状態である必要があるかもしれません。BundleServiceは、私がアプリケーション中のアクティビティのBundleを、コンテキストにアクセス可能なクラスに対して開放するために追加したものです。

その他のMortarの魅力は、Presenter、ViewPresenterです。これらは、Bundlerと統合され、あなたのあらゆるビューデータの永続性を扱います。のちほど、どのようにしてスクリーンの中で状態を保持するのか、そしてフローの中の様々なスクリーンに、それらを保持したり注入するのかの例を紹介します。

Flow + Mortar (6:46)

併せて、FlowとMortarは全てのスクリーンにPathクラスを継承させるでしょう。各スクリーンはそれぞれのモジュールを持っています。例えば、詳細ビューへ移動する場合、そのスクリーンにユーザーIDを渡すことができます。そのユーザーIDは、スクリーン内に存在し、プレゼンターに注入されたモジュールを提供することができます。

このように、もしそのアプリケーションがバックグラウンド状態にある場合は、onSaveInstanceStateの中でBundleに書き込まれます。私たちはIDを保存するだけでいいのです。あなたのスクリーンが次に再生成された時は、同じIDがまだスクリーン内にあり、プレゼンターに再注入させることが可能です。

これはModel-View-Presenterのコンセプトに見事につながります。ビューとプレゼンターは異なる目���のために相互にやりとりができ、一方でとても上手に分離されています。 この例では、チャットリストのスクリーンとそのビューがあります。モジュールはビューを追加して、また、自身をルートモジュールに追加しています。

@Layout(R.layout.chat_list_view)
@WithModule(ChatListScreen.Module.class)
public class ChatListScreen extends Path {
	
	@dagger.Module(
		injects = ChatListView.class,
		addsTo = RootModule.class)
	public static class Module {
		@Provides List<Chat> provideConversations(Chats chats) {
			return chats.getAll();
		}
	}
}

ビューでは、プレゼンターのインスタンスがあります。 onDetachedfromWindowでは、プレゼンターがビューが作成されたことを知らせるためにコールバックを提供します。プレゼンター側では、注入されたチャット情報に基づいてビューをすぐに設定することができます。これはonLoadの関数です。ビューでは、1つのアイテム上でクリックハンドラーも用意されています。次のスクリーンへ遷移するためにFlowを使用してプレゼンターに対してコールバックします。ここでは、先に指定されたIDのポジションを使用します。

public class ChatListView extends ListView {
	@Inject ChatListScreen.Presenter presenter;

	...

	@Override
	protected void onDetachedFromWindow() {
		super.onDetachedFromWindow();
		presenter.dropView(this);
	}
}

Deep Dive

MortarScopeをセットアップする (9:45)

さあ、では実際に、どのようにセットアップしましょうか?MortarScopeはgetSystemService中に用意されています。そこには、Daggerの注入のためのサービスがあります。ルートモジュールの @Provides です。 @Providesは最初に、ActionBarの中身のいくつかを内包します。また、GSONシリアライゼーションのように中の物を公開することもできます。以上がセットアップの大半です。アプリケーションクラスとして必要なものは他にはありません。

public class MortarDemoApplication 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");
		}

		if (rootScope.hasService(name)) return rootScope.getService(name);

		return super.getSystemService(name);
	}
}

@Module(
	includes = {
		ActionBarOwner.ActionBarModule.class,
		Chats.Module.Class
	},
	injects = MortarDemoActivity.class,
	library = true)
public class RootModule {
	@Provides
	@Singleton
	Gson provideGson() { return new GsonBuilder().create; }
}

アクティビティをセットアップする (11:01)

少しコードをアクティビティへ移動させました。ちなみにここは BundleService がMortarScope上に追加されたクラスへパーシスタンスバンドルを提供する場所です。

onCreate メソッドはFlowがセットアップされる場所であり、そして永続性を得るためにスタックされたあなたのビューのヒストリーと、いくつかのアクティビティのライフサイクルイベントに関連性を持たせるためにflowDelegateを統合する場所でもあります。パスコンテナもまた、ここでセットアップされ、トランジションを処理する包括的なコンテナです。後ほどそのトランジションが実際には提供されていないのをお見せします。

どんなUIアニメーションを実装するかはあなた次第なので、あなたが望むようにアニメーションをセットアップすることで、かなりの独自性を得られます。

@Override protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);

	...

	ObjectGraphService.inject(this, this);

	getBundleServiceRunner(activityScope).onCreate(savedInstanceState);

	actionBarOwner.takeView(this);

	...

	flowDelegate = FlowDelegate.onCreate(
		nonConfig,
		getIntent(),
		savedInstanceState,
		parceler,
		History.single(new ChatListScreen()), this);
	)
}

onResumeonSaveInstanceStateonDestroyのために、flowDelegateをコールすることができます。一番下では、dispatchが、containerにビューを追加して、一番上のアクションバーをセットアップすることができます。

もうひとつの列挙する点は、アクティビティが一つだけしかないことです。プレゼンターは何が表示されるかをコントールします。アクションバーにアクセスする注入可能な方法としては、actionBarOwnerがあります。これは、バンドラーと似ていて、生成後にライフサイクルにフックするものです。つまり、プレゼンターはアクションバーや、タイトル、ボタンなど、特定のスクリーンにとって必要な変更を行うために、このactionBarOwnerを注入します。

@Override
protected void onResume() {
	super.onResume();
	flowDelegate.onResume();
}

...

@Override public void dispatch(Flow.Traversal traversal, Flow.TraversalCallback callback) {
	path newScreen = traversal.destination.top();
	String title = newScreen.getClass().getSimpleName();
	ActionBarOwner.MenuAction menu = new ActionBarOwner.MenuAction("Friends", new Action0() {
		@Override public void call() {
			Flow.get(MortarDemoActivity.this).set(new FriendListScreen());	
		}
	});
	actionBarOwner.setConfig(
		new ActionBarOwner.Config(false, !(newScreen instanceof ChatListScreen), title, menu));

	container.dispatch(traversal, callback);
}

Flowナビゲーション (13:32)

Flowはあなたのナビゲーションを実行するのと、スクリーンのヒストリーをトラッキングし続けることに関与しています。Flowがこれを行うには、いくつかの方法があります。

Flowの最新リリースには、 setメソッドがあります。あなたがこのスタック上に存在しないスクリーンに移動したい場合、それが一番上に追加されます。もしヒストリーのどこかにすでに存在していたら、元に戻ってください。それより上の全てのスクリーンは取り出され、それが最新の画面になります。ヘルパーの History.singleを使うことで、完全にあなたのヒストリーを再構築することもできます。

バックボタンを操作する限りは、Flowはアクティビティーの中にいくつかのインテグレーションが存在します。ユーザーはビューにいくつかのアクションを実行するので、もしあなたがそれを自分でトリガーしたい場合は、Flow.goBackを使えばいいのです。

//Moves forward to this screen - top of history
Flow.get(getView()).set(new ChatScreen(position));

// Replaces the history with the new history
Flow.get(getView()).setHistory(
	History
		.emptyBuilder()
		.push(new ChatListScreen())
		.push(new ChatScreen(SOME_VALUE))
		.build(), Flow.Direction.REPLACE);

// Single screen history builder
Flow.get(getView()).setHistory(
	History.single(new ChatListScreen()), Flow.Direction.FORWARD);

Flow.get(getView()).goBack();

画面遷移とアニメーション (15:14)

先ほど私が述べたように、スクリーントランジションはあっても、アニメーションはありません。それらを実装するためには、FlowのPathContainerが継承される必要があり、performTraversalメソッドが実装されていなければなりません。このひとかたまりのコードでは、performTraversalメソッドは現在のスクリーンをみて、アニメーションを実行します。そして最後に、古いビューを実際に削除しますので、そこで新しいものを追加します。

アニメーション後、グラフからだけではなくコンテナーからも古いスクリーンを削除します。この方法では、あなたの各スクリーンは必要な限りコンテキストオブジェクト内に留まります。それぞれのスクリーンは、そのモジュール内のオブジェクトグラフに追加するものを提供することができ、あなたがそこから離れ、スクリーンを削除するとすぐに、そのコンテキストは破棄されます。そのモジュールが提供するものはもはや何もなくなりました。メモリはクリーンなままです。

@Override protected void performTraversal(final ViewGroup containerView,
	final TraversalState traversalState, final Flow.Direction direction,
	final Flow.TraversalCallback callback) {

  final PathContext context;
  final PathContext oldPath;
  if (containerView.getChildCount() > 0) {
  	oldPath = PathContext.get(containerView.getChildAt(0).getContext());
  } else {
  	oldPath = PathContext.root(containerView.getContext());
  }

  ...
}

Flowヒストリー / 状態の永続化 (16:44)

私はFlowヒストリーを、意図していたのとは違う使い方をしているかもしれません。Flowヒストリーは、状態を把握します。スクリーンヒストリーの中に保持されたあなたのスクリーン内に特定の値を格納することができます。それらは、パーセラーを経由する時に、バンドルに永続化されます。私の場合は、スクリーンをシリーズ化するために、JSON Parcelerを使用しています。

私は自分のオブジェクトに変換します。何か複雑なものを、バンドルに書き込むことができるシンプルなJSONストリングに変換することができます。 それはあなたがflowをコンポーズする際に役立ちます。なぜならこのコンポーズが状態をもっているからです。onSaveInstance はヒストリー内で何かのために呼び出されます。

/**
 * Used by History to convert your state objects to and from instances of
 * [email protected] android.os.Parcelable}.
 */
public interface StateParceler {
	Parcelable wrap(Object instance);
	Object unwrap(Parcelable parcelable);
}

public void onSaveInstanceState(Bundle outState) {
	checkArgument(outState != null, "outState may not be null");
	Parcelable parcelable = flow.getHistory().getParcelable(parceler, (state) -> {
		return !state.getClass().isAnnotatioNPresent(NotPersistent.class);
	});
	if (parcelable != null) {
	//noinspection ConstantConditions
		outState.putParcelable(HISTORY_KEY, parcelable);
	}
}

Subflow Hierachy (19:47)

一番上位のレベルでは、あなたのアプリケーションはひとつのフローの中にあります。そのフローはMainFlowに継承されていて、あなたがどのように遷移したかのヒストリーを持っています。

最初の画面を開くと、それはProfileScreenでしょう。あなたが編集を行う時、あなたはComposeFlowへ下がって行きます。そこには、ComposeStateが追加してあります。そこからあなたは、ComposeScreenRefineScreenへ行くことができます。それからもし、あなたが元の画面に戻って来ても、ComposeStateは、私がComposeFlowレベルのobject graphに追加した時のままの状態を保っています。

それを説明するために、ここに実際のComposeFlowの画面があります。一番上にComposeStateオブジェクトがあります。これはあなたがflowを入力する度に作成されるものです。プレゼンターでは、最初の因数として注入されます。これは、包括的なflow内の特定のflowの状態の把握をとても簡単にします。同じflowを複数の場所に注入することができます。

@Layout(R.layout.compose_layout)
public class ComposeScreen extends DefaultScopeNameBlueprintScreen {
	public ComposeScreen() {
	}

	@Override
	public Object getDaggerModule() {
		return new Module();
	}

	@dagger.Module(
		injects = {
			ComposeView.class,
			SuperlativeStatePicker,class,
			FaceTaggingUserItemView.class
		},
		addsTo = ComposeFlowScreen.Module.class
	)
	@Singleton
	public static class Module {...}
}

ライフサイクルイベント (21:46)

あなたは、ただ1つのアクティビティー内に存在し、Flowを通してあなたのトランジションと、またMortarを通してあなたの依存性注入を処理するだけで、より簡単にライフサイクルを作成することができます。多くの場合、あなたはライフサイクルイベントにアクセスすることが必要になるでしょう。しかし、これはFlowとMortar内には実際にはありません。もしシングルサインオンするために他のサービスと統合したい場合、もしくはあなたが自分のカメラインテグレーションを使用している場合は、ライフサイクルイベントが必要になるでしょう。簡単なルートはあなたのアクティビティをプレゼンターに注入し、コールバックを送るでしょうが、しかしそれはあまり美しくはありません。

私が見つけたひとつの良い方法は、ライフサイクルマネージャを使用するものです。アクティビティはLifecycleOwnerのインスタンスを一つ持ち、このインスタンスは、onResumeonPauseonActivityResultイベントにフックします。プレゼンターの中では、あなたはLifecycleOwnerのインスタンスを取得し、それらをレジスターすることができます。こうして、インデックスはViewPresenterをあなたに提供し、ActivityLifecycleListenerを実装します。こうして、多くの異なるイベントが存在するので、あなたに必要なのはonActivityResultだけかもしれません。そういうわけで、私たちはインターフェースを含める為に、LifecycleViewPresenterをサブクラスにします。

こちらの例では、ViewCycleOwnerはプレゼンター内にフックされています。私たちはonLoadで登録を行い、onExitScopeで登録解除を行います。私はまた、CameraCaptureViewでこれを使用しています。私は内蔵カメラを持っており、 その状態は時々、構築される必要があります。もしアプリケーションを一時停止していてもカメラが作動している場合は、他のアプリのためにカメラをブロックしてください。私がプレゼンターの中のカメラをまたアクティベートする必要があるかどうかを知らせてく���る状態オブジェクトがあります。

@Override
protected void onLoad(Bundle savedInstanceState) {
	super.onLoad(savedInstanceState);
	lifecycleOwner.register(this);
	onActivityResume();
}

@Override
protected void onExitScope() {
	super.onExitScope();
	lifecycleOwner.unregister(this);
}

Settings (25:02)

MortarとDaggerの組み合わせは、私たちに良い依存性注入をしてくれます。私たちはそれを活用したいですし、実際に全てのセッテイングを注入します。セッティングマネージャの代わりに、私たちは必要に応じて、プレゼンターへ個々のセッティングを注入することができます。どのようにそれは動作するのでしょう?私はさらに、AbstractLocalSettingextendsするStringLocalSettingSettingsModuleと一緒に、SettingsModuleを追加しています。そしてまた、 “config”のアノテーションと、providesConfigも追加します。これらのセッティングはSharedPreferencesに置かれます。

public class StringLocalSetting extends AbstractLocalSetting<String> {
	public StringLocalSetting(SharedPreferences preferences, String key) {
		super(preferences, key);
	}
}

public class SettingsModule {
	...

	@Provides
	@Config
	StringLocalSetting providesConfig(SharedPreferences preferences) {
		return new StringLocalSetting(preferences, "config");
	}
}

SettingsModuleは、私のルートモジュールに追加され、そこで私の場合は、ApplicationModuleを呼び出します。もう1つのマネージャを渡すのではなく、“config”アノテーションを介して、自分のローカルストリングセッティングに注入することができます。これらは私がgetす���度に提供されるので、私は常に最新のものを取得することができます。さらに、プレゼンターにフックを返すため、ビューをセットアップする用意ができた時、プレゼンターのdropViewtakeViewを通知を提供するために呼び出します。別のヘルパーは、私が構築した処理済みの注入Mortarです。そして私はまた、さらに楽にするために、オブジェクトグラフとButter Knifeも使います。また、反復コードの使用はより少ないです。

良い点(と悪い点) (27:40)

良い点、悪い点はなんでしょうか?私は、新しいマテリアルスタイルのビューのアニメーションのサポートが存在しないことに気が付きました。私はたったひとつのアクティビティしか持たないし、その為、それを自分自身で構築しないといけないかもしれません。

しかし、Flowによって与えられる柔軟性と、アニメーションは間違いなく実現可能です。FlowもMortarも今でも活発に変わり続けています。これはプロジェクトを前に進める、良いことと言えます。活発な開発は、人々が今でもプロジェクトについて考え、取り組んでいることを示しています。

良くない傾向は、新しくリリースされたバージョンのライブラリは、多くのAPIの非互換な変更をもたらしていることです。慣れ親しんだ仕組みから新しい仕組みに移行することは大変なことです。私たちが今日見てきた全てのものは、最新のテンプレートアプリケーションをベースにしています。多くの場合は仕組みはほぼ同じでネーミングのみが変わることがほとんどです。

私がしようとし続けていることは、プレゼンターをどのようにテストするかについて習得することです。中心部分はすでに上手に分離されています。スクリーンやビューの中のパスプレゼンターの周辺でのMortarの依存性注入はとても簡単です。私はこれらすべてが本当に優れた利便性だと思います。

Q&A (29:15)

Q: さらに詳しく説明されている本かリソースは何かあるのでしょうか?

Thorben: 特にはないですね。FlowMortar も短いREADMEファイルと、少しの解説コードが入ったレポジトリーがあります。それは全て文書です。

Q: 私たちがただ最初からアプリを再起動することができる時に、なぜ状態の永続性の実装にそんなに多くの時間を費やすのですか?もしあなたのアプリがバックグラウンドで停止している場合には価値があることは理解できます。あなたの場合には、状態を保存することの価値とは何でしょうか?

Thorben: 私はあなたのアプリケーションの前の状態を再構築すべき例をいくつも思いつきます。例えば、誰かがSuper内に新しいポストを作成するflowを構築する段階にいる時、もし彼らがブラウザーでリンクを探す場合、アプリはバックグラウンドになります。あなたは彼らがアプリに戻ってきた時、彼らがそのポストに実際にそのリンクが追加されたかを確かめられるように状態を確認してもらいたいでしょう。私が開発したどんなアプリで、私はいつもこれを再構築します。

Stephan: あなたが再構築したい理由の1つは、データインプットです。では何かをタイピングするのをやめるとしましょう、そして他の何かを開きます。あなたがアプリに戻ってきた時、あなたのテキストがまだそこにあるのは親切なことです。状態を保存する場合の別のシチュエーションは、あなたが電話に出る時や、オーディオブックを聞く時でしょう。腕の良いデベロッパー達により開発された良いゲームはたくさんあります。しかし、彼らはライフサイクルを理解していません。あなたは他のことをするためにゲームを中断した時、すぐに気がつきます。そして、あなたはゲームを再スタートしなければなりません。これは大きな違いになります。

Q:電話が掛かってきたときにどれくらいの頻度でアプリが停止するか、何かデータや考えはありますか?私はただ電話のようなものでアプリが停止するとは考えません。

Audience member 1: あなたの携帯電話は多分十分なRAMを持っていないのでしょう、だからバックグラウンドにある全てのアプリが起動した時に停止してしまうのです。もしあなたが高性能な携帯電話を持っていれば、それに気がつかないでしょう。しかし、低級の携帯電話の場合は、これは本当にたいしたことですよ。

Stephan: あなたは正しいです。高性能携帯電話では、OpenGLゲームをする時は、私は他のアプリケーションを停止しなければいけません、しかし他にはほぼありません。

Audience member 2: 携帯電話を横にした時、アクティビティーは停止します。だからアウトステート上のあなたのバンドルを保存しない場合、onCreateの中にそれが再構築されます。そして、あなたは全てのビューを失うことになります。

Thorben: MortarとFlowは永続性を使用して仕事をするのに役立ちます。だからさらに簡単にあなたのスクリーンを再構築できるのです。これはまた、あなたのアプリケーションを設計するための違う方法でもあります。ロジック内に分離があるのと同様、親切なビュー同士の分離と、プレゼンターがあります。あなたはおそらく元に戻したいと思うでしょう。例えばリストから詳細ページに遷移した場合、あなたがアプリに戻ってきた時に同じ場所にリストをつくりたいと思うでしょう。

Q: あなたがコードサンプルをダウンロードした時、Mortarは含まれていましたか?

Thorben: 1つだけFlow上にサンプルコードがありました。 Mortar repo も同様に、2つが含まれていました。1つはDagger用に、もう1つはDagger2用です。

Q: これはビューの構造自体を変えようとしているので、新しく始めるプロジェクトにとってはとても魅力的だと思いました。あなたのプロジェクトには本当にこれは統合されているのですか?

Thorben: 私はプロジェクトをゼロから始めましたが、あなたのプロジェクトがどれくらいの規模によるかによります。もしあなたのビューを書き換えるなら、あなたのビューがどのように構成されているか次第で、簡単にも難しくもなるでしょう。このプロジェクトを利用することにも、とても価値があります。なぜなら、プロジェクトを進める複数の人がいて、さらに多くの人がこれを取り入れます。おそらく、自分一人で挑戦することには価値はありません。しかし、もしあなたがすでに、かなり進んだ開発サイクルの中にいるとしたら、あなたの製品をスイッチすることが本当に有益かは私にはわかりません。

Q: Androidのライフサイクルメソッドを扱い、それを簡単にしようとするライブラリが存在します。一つはRoboBindingです。あなたは、FlowとMortarに大きく依存して、多くの将来の可能性を失っているように見えます。

Thorben: それはなにか特定のソリューションを採用するときに必ず起こると思っています。私はまだ古いバージョンを使っていて、APIが変わってしまいました。これはプロジェクトを進めるためには一仕事ですが、結局はやる必要があります。私はオリジナルのJellyアプリにとって、これらのライブラリを使うことはとても良いと思うし、魅力的に見えるので、私たちはこれらの使用をやめられません。間違いなく、最新バージョンでクラス、メソッド、ネーミングがたくさん変わったことによる影響で、いくつかの技術的な負債は貯まってはいます。

_Q: このアプローチがなぜフラグメントよりも優れているのか、もう少し教えて下さい。MVCの代わりとして、MVPがあること、また、Daggerがコードをより抽象化することは知っています。設計以外に、パフォーマンス面の利点もあるのでしょうか? _

Thorben: 私が指摘した理由の一つが、あなたが、階層化されたフラグメントを持っていて、自分でそれを管理しなければいけない状況についてです。この解決法は私が過去に陥った問題を考えると、とても魅力的です。私はほとんどステート周りの問題に出会っていませんし、それ以来アプリケーションの作り直しもしていません。現在、みなさんはアクティビティとフラグメントを利用することに慣れています。高速なデバイスにおいて、それらを処理する際の負荷はそれほど悪いものとは言えないでしょう。

Audience member 1: Squareはフラグメントが好きではなく、彼らはそのフレームワークに由来するUIを使いません。彼らは、彼らのやり方でするのが好きなだけで、そして素晴らしいプロジェクトを持っています。けれどもこのアプローチはとても革新的に見えます。

Audience member 2: テストを簡単にするためには、フラグメントから、カスタムのビューに移行することを決めなければなりません。問題の一部はフラグメントからテストすることから始まっていて、カスタムのビューをテストすることはとても簡単です。私たちのアプリケーションでは少なくとも30%のクラッシュが、バックグラウンドでタスクを終了したことで起こっていました。フラグメントの中にいると、アプリはクラッシュしますが、ビューの中ではきっとそうはなりません。

Q: あなたはどのようにしてレイアウトファイルをセットアップしましたか?それは、一般的なリニアレイアウトですか?

Thorben: はい、それはきっとあなたが普段書くものだろうと思います。スクリーンの一番上に、あなたのあらゆるレイヤーファイルを含めるでしょう。それは階層が増えるのを制御します。それは、オリジナルのMortar Flowレポジトリからは削除されました。しかし、今サンプルプロジェクトの一部になっています。あなたは、今は、どんなレイアウトファイルでもスクリーンをアノテートすることができ、つまりそれは、通常のAndroid のレイアウト用xmlファイルなのです。

Q: RoboBindingsはMortarに対して、代わりとなるライブラリと言われています。あなたがフラグメントの代わりを探す時に、なにか他にもありますか?

Audience member 1: Nucleus.

Audience member 2: 他にあるとは思いません。RoboBinding はAndroidの、MVVMパターンのライブラリで、ここ2, 3年のものです。ビューへのすべてのコールバックを扱い、とても急進的で、妨げとなりません。その上で、あなたはアプリケーションを設計しないとなりません。

Q: あなたが、プレゼンターに、シングルトンスコープとしてマークされたDaggerモジュールを持っているのを見ました。それはどれくらい残りますか?また、それはアプリケーション全体の中で存在しますか?

Thorben: いいえ、それはスクリーンがなくなった時に消えます。

Q: やろうと思えば、ひとつのフラグメントの中に、ビューを置くことは出来ますか?

Thorben: はい、あなたのビューはフラグメントを内部に持つことが出来ました。もし、あなたがアプリケーションを書き換えていて、それが、フラグメントの中にある時、あなたは、引き続き、これに切り替えて、あなたのフラグメントにラップされたビューを持つようなことができます。

Q: タブレットではスクリーンはどのように働きますか?タブレット上では異なるビューをインジェクトしていますか?

Thorben: そのようにできます。その他のレイヤーファイルであなたがしてきたことと、一緒だろうと思います。私は実際にはタブレット向けに開発を行ったり、このライブラリを使用したことはありません。しかし、どのようにレイアウトファイル構築して、異なるリソースフォルダーに含めたかによって、異なる働きをしてしまうことはないと思います。なぜなら、スクリーンは特定のレイアウトに対してアノテートされているので、どんなデバイスの上なのかを反映したビューを生成するでしょう。

Q: ダイアログのサポートはありますか?

Thorben: それらへのサポートも存在します。通常のビュープレゼンターの他に、ポップアッププレゼンターがあります。私たちは、それをナビゲーションの行き来とまったく同様に使います。

翻訳: Sohei Kitada, Sayuri Takizawa



Thorben Primke

Thorben Primke

Thorben is a Software Engineer at Pinterest working towards making all product pins buyable. Prior to Pinterest he worked on Android at Jelly, Facebook, and Gowalla.