この出版物の目的は、フラグメント、ダイアログ、およびアクティビティの依存関係を取得する方法を示すことです。
会話のリスナーを設定する
継承されたプロジェクトの1つで、ダイアログの次の実装に出会いました。
public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } @Override public void onAttach (Context context) { super.onAttach(context); if(context instanceOf Listener) { listener = (Listener) context; } else { listener = (Listener) getParentFragment(); } } }
「コンポーネントはいつリスナーを検索する必要があるのか」と私はその瞬間に考えました。
リスナーインターフェースを誰が正確に実装しているかがフラグメントに分からないようにしましょう。
多くの人は、たとえば次のオプションをすぐに提供できます。
public class ExampleDialogFragment extends DialogFragment { private Listener listener; public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(Listener listener) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); dialogFragment.listener = listener; return dialogFragment; } }
このダイアログを埋め込むアクティベーションコード:
public class ExampleActivity extends AppCompatActivity { void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new DialogFragment.Listener() { @Override void onMessageEntered(String msg) { // TODO } }); dialogFragment.show(getFragmentManager(), "dialog"); } }
このソリューションには1つの重大な欠点があります。 構成を変更する(たとえば、画面を切り替える)と、次のチェーンが得られます:ダイアログは
Bundle
状態を保存し、破棄されます->アクティビティが削除されます->アクティビティの新しいインスタンスが作成されます->ダイアログは
Bundle
保存された状態に基づいて再び作成されます その結果、明らかに保存および復元されなかったため、ダイアログ内のリスナーへのリンクが失われます。 もちろん、アクティビティライフサイクルコールバックの1つで
setListener()
手動で呼び出すことができますが、別のオプションがあります。
Bundle
匿名クラスと通常のクラスのインスタンスを保存することはできないため、次の条件を遵守する必要があります。
- リスナーは
Serializable
またはParcelable
実装する必要がありParcelable
-
setArguments(Bundle args)
作成するときに、フラグメント引数を介してリスナーを渡します - リスナーは
onSaveInstanceState(Bundle outState)
メソッドに保存する必要があります - リスナーは、
Dialog onCreateDialog(Bundle savedInstanceState)
メソッドDialog onCreateDialog(Bundle savedInstanceState)
で復元する必要があります
原則として、リスナーインターフェイスは
Activity
や
Fragment
などのAndroidコンポーネントによって実装されます。 このようなコンポーネントは
Bundle
に格納されることを意図していないため、ソリューションへの別のアプローチを見つける必要があります。 リスナー自身ではなく、彼を見つけることができる「プロバイダー」を伝えましょう。 この場合、誰もそれをシリアライズ可能にして
Bundle
保存することを気にしません。
コンポーネントとの対話中に「探偵」が状態を変更してはならない場合、
onSaveInstanceState(Bundle outState)
メソッドをオーバーライドして、
Dialog onCreateDialog(Bundle savedInstanceState)
メソッドを呼び出すときに引数から依存関係を復元することはできません。
実装を見てみましょう:
public class ExampleDialogFragment extends DialogFragment { private static final String LISTENER_PROVIDER = "listener_provider"; private Listener listener; public interface ListenerProvider extends Serializable { Listener from(DialogFragment dialogFragment); } public interface Listener { void onMessageEntered(String msg); } public static DialogFragment newInstance(ListenerProvider provider) { ExampleDialogFragment dialogFragment = new ExampleDialogFragment(); Bundle args = new Bundle(); args.putSerializable(LISTENER_PROVIDER, provider); dialogFragment.setArguments(args); return dialogFragment; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle args = getArguments(); if(args == null || !args.containsKey(LISTENER_PROVIDER)) { throw new IllegalStateException("Listener provider is missing"); } ListenerProvider listenerProvider = (ListenerProvider) args.getSerializable(LISTENER_PROVIDER); Listener listener = listenerProvider.from(this); ... } }
この場合、アクティビティのコードは次の形式になります。
public class ExampleActivity extends AppCompatActivity implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment .newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getActivity(); } } }
フラグメントからダイアログ表示を実装する必要がある場合、次のコードを取得します。
public class ExampleFragment extends Fragment implements ExampleDialogFragment.Listener { @Override public void onMessageEntered(String msg) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return (ExampleDialogFragment.Listener) dialogFragment.getParentFragment(); } } }
その結果、呼び出し側コンポーネント自体が、ダイアログがリスナーを見つけるのに役立つことがわかります。 同時に、概念の断片は、それが誰であるか(リスナー)を知りません。 たとえば、何らかの理由で
Activity
直接連絡したくない場合、誰も単純にイベントをスローし、それをキャッチして適切な場所で処理する必要はありません(ダイアログコードを変更する必要さえありません):
public class ExampleFragment extends Fragment { void onMessageEvent(Message message) { // TODO } void showDialog() { DialogFragment dialogFragment = ExampleDialogFragment.newInstance(new ListenerProvider()); dialogFragment.show(getFragmentManager(), "dialog"); } private static class Message { public final String content; private Message(String content) { this.content = content; } } private static class ListenerProvider implements ExampleDialogFragment.ListenerProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public ExampleDialogFragment.Listener from(DialogFragment dialogFragment) { return new ExampleDialogFragment.Listener() { @Override public void onMessageEntered(String msg) { EventBus.getDefault().post(new Message(msg)); } }; } } }
彼らはある種のダイアログを理解しました。 さらに進んでいます。
フラグメント内の依存関係を検索する
多くの場合、
Activity <-> Fragment
または
Fragment <-> Fragment
タイプの相互作用を1つのアクティビティ内に整理する必要があります。 一般的な原則は上記と同じままです。インターフェース(リスナーなど)および「探偵」を介して、コンポーネント間の通信が編成されます。 この記事では、一方向の相互作用を検討します。
例として、フラグメントでプレゼンターを受信する場合を分析します。 私たちはそれぞれ次のことに出会ったと思います。
public interface Presenter { ... } public class ExampleFragment extends Fragment { private Presenter presenter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter = App.get().getExampleFragmentComponent().getPresenter(); } }
そしてすべてがうまくいくようで、依存関係の作成を隠しましたが、それらの同様の領収書はプロジェクト全体に散らばっています。 私の意見では、少なくとも原因となる人はこれらの依存関係を提供するか、それらを見つけるのを助けるべきです。
繰り返しますが、「探偵」を使用してこの手法を適用します。
public class ExampleFragment extends Fragment { private static final String DI_PROVIDER = "di_provider"; private Presenter presenter; public interface DependencyProvider implements Serializable { Presenter getPresenterOf(Fragment fragment); } public static Fragment newInstance(DependencyProvider dependencyProvider) { Fragment fragment = new ExampleFragment(); Bundle args = new Bundle(); args.putSerializable(DI_PROVIDER, dependencyProvider); fragment.setArguments(args); return fragment; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle args = getArguments(); if(args == null || !args.containsKey(DI_PROVIDER)) { throw new IllegalStateException("DI provider is missing"); } DependencyProvider diProvider = (DependencyProvider) args.getSerializable(DI_PROVIDER); presenter = diProvider.getPresenterOf(this); } } public class ExampleActivity extends AppCompatActivity { void showFragment() { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment fragment = ExampleFragment .newInstance(new DiProvider()); ft.add(R.id.container, fragment); ft.commit(); } private static class DiProvider implements ExampleFragment.DependencyProvider { private static final long serialVersionUID = -5986444973089471288L; @Override public Presenter get(Fragment fragment) { return App.get().getExampleFragmentComponent().getPresenter(); } } }
したがって、フラグメントをより普遍的にしたため、コンポーネントコードを不必要に変更することなく、プロジェクト間で簡単に転送できます。
同様に、
Activity
で依存関係の受信を整理できます。
そのようなアプローチの実装に関する小さな例はここにあります 。
説明したアプローチがプロジェクトの実装においてあなたにとって有用であり、利益をもたらすことを願っています。
ご清聴ありがとうございました!