FragmentManager
は、アプリのフラグメントに対するアクション(フラグメントの追加、削除、置換、バックスタックへの追加など)を実行するためのクラスです。
Jetpack Navigation ライブラリを使用している場合は、このライブラリが FragmentManager
を操作するので、FragmentManager
を直接操作することはありません。とはいえ、フラグメントを使用するすべてのアプリは、なんらかのレベルで FragmentManager
を使用します。したがって、その機能と仕組みを理解することは重要です。
このページでは次のことを説明します。
FragmentManager
にアクセスする方法- アクティビティとフラグメントに関連する
FragmentManager
のロール FragmentManager
を使用してバックスタックを管理する方法- データと依存関係をフラグメントに提供する方法
FragmentManager にアクセスする
FragmentManager
には、アクティビティまたはフラグメントからアクセスできます。
FragmentActivity
とそのサブクラス(AppCompatActivity
など)は、getSupportFragmentManager()
メソッドを通じて FragmentManager
にアクセスできます。
フラグメントは、1 つ以上の子フラグメントをホストできます。フラグメント内で、getChildFragmentManager()
を介してフラグメントの子を管理する FragmentManager
への参照を取得できます。そのホストである FragmentManager
にアクセスする必要がある場合は、getParentFragmentManager()
を使用できます。
以下に、フラグメント、そのホスト、およびそれぞれに関連付けられた FragmentManager
インスタンスの関係を示す例を示します。
図 1 に 2 つの例を示します。それぞれに 1 つのアクティビティ ホストがあります。どちらの例のホスト アクティビティも、トップレベル ナビゲーションを BottomNavigationView
としてユーザーに表示します。このビューは、アプリ内のさまざまな画面でホスト フラグメントをスワップアウトします。各画面は別個のフラグメントとして実装されます。
例 1 のホスト フラグメントは、分割ビュー画面を構成する 2 つの子フラグメントをホストします。例 2 のホスト フラグメントは、スワイプビューの表示フラグメントを構成する単一の子フラグメントをホストします。
この設定では、各ホストに、その子フラグメントを管理する FragmentManager
が関連付けられていると考えることができます。これは、supportFragmentManager
、parentFragmentManager
、childFragmentManager
の間のプロパティ マッピングとともに、図 2 に示されています。
参照すべき適切な FragmentManager
プロパティは、呼び出し部分がフラグメント階層内のどこにあるか、アクセスしようとしているフラグメント マネージャーがどれかによって異なります。
FragmentManager
への参照を取得したら、それを使用して、ユーザーに表示するフラグメントを操作できます。
子フラグメント
一般的に、アプリはアプリ プロジェクト内の 1 つまたは少数のアクティビティで構成され、各アクティビティは関連する画面のグループを表します。アクティビティは、トップレベル ナビゲーションを配置するポイントと、フラグメント間の ViewModel
オブジェクトおよびその他のビュー状態のスコープを設定する場所を提供します。フラグメントは、アプリ内の各デスティネーションを表します。
分割ビューやダッシュボードに複数のフラグメントを同時に表示する場合は、デスティネーション フラグメントとその子フラグメント マネージャーによって管理される子フラグメントを使用できます。
子フラグメントのその他のユースケースは、次のとおりです。
- 画面スライド。親フラグメント内で
ViewPager2
を使用して一連の子フラグメント ビューを管理します。 - 関連する画面セット内のサブナビゲーション。
- Jetpack Navigation は、子フラグメントを個別のデスティネーションとして使用します。アクティビティは、単一の親
NavHostFragment
をホストし、ユーザーがアプリ内を移動するたびに、さまざまな子デスティネーション フラグメントでスペースを埋めます。
FragmentManager を使用する
FragmentManager
はフラグメントのバックスタックを管理します。実行時に、FragmentManager
は、ユーザーの操作に応じてフラグメントの追加や削除などのバックスタック オペレーションを実行できます。個々の変更セットは、FragmentTransaction
と呼ばれる 1 つの単位として一緒に commit されます。フラグメント トランザクションの詳細については、フラグメント トランザクションのガイドをご覧ください。
ユーザーがデバイスの [戻る] ボタンを押すか、アプリで FragmentManager.popBackStack()
が呼び出されると、最上位のフラグメント トランザクションがスタックからポップオフされます。フラグメント トランザクションがスタックに残っていない場合、子フラグメントを使用していなければ、「戻る」イベントがアクティビティにバブルアップされます。子フラグメントを使用している場合は、子フラグメントと兄弟フラグメントに関する特別な考慮事項をご覧ください。
トランザクションに対して addToBackStack()
を呼び出す場合は、複数のフラグメントの追加や、複数のコンテナ内でのフラグメントの置換など、任意の数のオペレーションがトランザクションに含まれる可能性があることに注意が必要です。
バックスタックがポップされると、これらすべてのオペレーションが単一のアトミック アクションとして取り消されます。しかし、popBackStack()
呼び出しの前に追加のトランザクションを commit し、トランザクションに addToBackStack()
を使用していなかった場合、これらのオペレーションは取り消されません。したがって、単一の FragmentTransaction
内では、バックスタックに影響するトランザクションと影響しないトランザクションをインターリーブしないようにしてください。
トランザクションを実行する
レイアウト コンテナ内でフラグメントを表示するには、FragmentManager
を使用して FragmentTransaction
を作成します。トランザクション内では、コンテナに対して add()
または replace()
オペレーションを実行できます。
簡単な FragmentTransaction
の例を次に示します。
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("name") // Name can be null }
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack("name") // Name can be null .commit();
この例の ExampleFragment
は、R.id.fragment_container
ID で識別されるレイアウト コンテナに現在フラグメントが存在する場合、それを置き換えます。フラグメントのクラスを replace()
メソッドに渡すと、FragmentManager
はその FragmentFactory
を使用してインスタンス化を処理できます。詳細については、フラグメントに依存関係を渡すをご覧ください。
setReorderingAllowed(true)
は、トランザクションに関与するフラグメントの状態変更を最適化し、アニメーションと遷移が正しく動作するようにします。アニメーションと遷移を使用したナビゲーションの詳細については、フラグメント トランザクションとアニメーションを使用したフラグメント間のナビゲーションをご覧ください。
addToBackStack()
を呼び出すと、トランザクションがバックスタックに commit されます。ユーザーは、[戻る] ボタンをタップすることにより、後からトランザクションを取り消して前のフラグメントに戻ることができます。1 つのトランザクション内で複数のフラグメントを追加または削除すると、バックスタックがポップされたときに、それらのオペレーションがすべて元に戻されます。addToBackStack()
呼び出しで指定された任意の名前を使用すると、popBackStack()
により特定のトランザクションにポップバックできます。
フラグメントを削除するトランザクションの実行時に addToBackStack()
を呼び出さなかった場合は、トランザクションが commit されたとき、削除されたフラグメントが破棄され、ユーザーはそのフラグメントに戻れなくなります。フラグメントの削除時に addToBackStack()
を呼び出した場合は、フラグメントは単に STOPPED
になり、その後ユーザーがそこに戻ると RESUMED
になります。この場合、ビューは破棄されます。詳細については、フラグメントのライフサイクルをご覧ください。
既存のフラグメントを見つける
findFragmentById()
を使用して、レイアウト コンテナ内の現在のフラグメントへの参照を取得できます。findFragmentById()
を使用してフラグメントを検索するには、XML からインフレートされた場合は特定の ID、FragmentTransaction
内で追加された場合はコンテナ ID を指定します。次の例をご覧ください。
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
または、フラグメントに一意のタグを割り当てて、findFragmentByTag()
で参照を取得することもできます。android:tag
XML 属性を使用して、レイアウト内で定義されているフラグメントにタグを割り当てることができます。また、FragmentTransaction
内の add()
または replace()
オペレーションでタグを割り当てることもできます。
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container, "tag") setReorderingAllowed(true) addToBackStack(null) } ... val fragment: ExampleFragment = supportFragmentManager.findFragmentByTag("tag") as ExampleFragment
Java
FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null, "tag") .setReorderingAllowed(true) .addToBackStack(null) .commit(); ... ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");
子フラグメントと兄弟フラグメントに関する特別な考慮事項
ある時点でフラグメントのバックスタックを制御できるのは、1 つの FragmentManager
のみです。アプリが同時に複数の兄弟フラグメントを画面に表示する場合、またはアプリが子フラグメントを使用している場合は、アプリのプライマリ ナビゲーションを処理する 1 つの FragmentManager
を指定します。
フラグメント トランザクション内でプライマリ ナビゲーション フラグメントを定義するには、トランザクションで setPrimaryNavigationFragment()
メソッドを呼び出し、その childFragmentManager
がプライマリの制御を行うフラグメントのインスタンスを渡します。
ナビゲーション構造を一連のレイヤと見なし、アクティビティを最も外側のレイヤと見なして、その下に子フラグメントの各レイヤをラップします。各レイヤに、単一のプライマリ ナビゲーション フラグメントがあります。
「戻る」イベントが発生したときは、最も内側のレイヤがナビゲーション動作を制御します。最も内側のレイヤで、ポップバックするフラグメント トランザクションがなくなると、その外側のレイヤに制御が戻されます。アクティビティに到達するまで、このプロセスが繰り返されます。
2 つ以上のフラグメントが同時に表示される場合、そのうちの 1 つだけをプライマリ ナビゲーション フラグメントに設定します。あるフラグメントをプライマリ ナビゲーション フラグメントとして設定すると、前のフラグメントの指定は取り消されます。前述の例では、詳細フラグメントをプライマリ ナビゲーション フラグメントとして設定すると、メイン フラグメントの指定が取り消されます。
複数のバックスタックをサポートする
場合によっては、アプリは複数のバックスタックをサポートする必要があります。よくあるのが、アプリが下部のナビゲーション バーを使用する場合です。FragmentManager
の saveBackStack()
メソッドと restoreBackStack()
メソッドを使用することで、複数のバックスタックをサポートできます。つまり、バックスタックを保存して別のバックスタックを復元することにより、バックスタックの切り替えが可能になります。
saveBackStack()
は、オプションの name
パラメータを使用して popBackStack()
を呼び出す場合と同様に動作します。指定したトランザクションと、それ以降のスタック上のトランザクションがすべてポップされます。違いは、saveBackStack()
ではポップされたトランザクション内のフラグメントの状態がすべて保存される点です。
たとえば、次のように addToBackStack()
を使用して FragmentTransaction
を commit することにより、あらかじめフラグメントをバックスタックに追加しておいたとします。
Kotlin
supportFragmentManager.commit { replace<ExampleFragment>(R.id.fragment_container) setReorderingAllowed(true) addToBackStack("replacement") }
Java
supportFragmentManager.beginTransaction() .replace(R.id.fragment_container, ExampleFragment.class, null) // setReorderingAllowed(true) and the optional string argument for // addToBackStack() are both required if you want to use saveBackStack() .setReorderingAllowed(true) .addToBackStack("replacement") .commit();
その場合、次のように saveBackStack()
を呼び出すことで、このフラグメント トランザクションと ExampleFragment
の状態を保存できます。
Kotlin
supportFragmentManager.saveBackStack("replacement")
Java
supportFragmentManager.saveBackStack("replacement");
次のように、同じ名前パラメータで restoreBackStack()
を呼び出すと、ポップされたトランザクションと保存されたフラグメントの状態をすべて復元できます。
Kotlin
supportFragmentManager.restoreBackStack("replacement")
Java
supportFragmentManager.restoreBackStack("replacement");
フラグメントに依存関係を渡す
フラグメントを追加する際に、フラグメントを手動でインスタンス化して FragmentTransaction
に追加できます。
Kotlin
fragmentManager.commit { // Instantiate a new instance before adding val myFragment = ExampleFragment() add(R.id.fragment_view_container, myFragment) setReorderingAllowed(true) }
Java
// Instantiate a new instance before adding ExampleFragment myFragment = new ExampleFragment(); fragmentManager.beginTransaction() .add(R.id.fragment_view_container, myFragment) .setReorderingAllowed(true) .commit();
フラグメント トランザクションを commit すると、作成したフラグメントのインスタンスが使用されます。ただし、構成の変更時には、アクティビティとそのすべてのフラグメントが破棄され、最も適切な Android リソースで再作成されます。この処理はすべて FragmentManager
が行います。フラグメントのインスタンスを再作成してホストに接続し、バックスタックの状態を再作成します。
デフォルトでは、FragmentManager
は、フレームワークが提供する FragmentFactory
を使用して、フラグメントの新しいインスタンスをインスタンス化します。このデフォルト ファクトリは、リフレクションを使用して、フラグメント用の引数のないコンストラクタを検索して呼び出します。つまり、このデフォルト ファクトリを使用して、フラグメントに依存関係を渡すことはできません。これは、最初にフラグメントを作成したときに使用したカスタム コンストラクタは、デフォルトでは再作成時に使用されないことも意味します。
フラグメントに依存関係を渡す場合、またはカスタム コンストラクタを使用する場合は、代わりにカスタム FragmentFactory
サブクラスを作成して FragmentFactory.instantiate
をオーバーライドします。次に、FragmentManager
のデフォルト ファクトリをカスタム ファクトリでオーバーライドします。このカスタム ファクトリを使用して、フラグメントをインスタンス化できます。
たとえば、地元で人気のデザートを表示する DessertsFragment
があり、DessertsFragment
には、ユーザーに適切な UI を表示するために必要な情報を提供する DessertsRepository
クラスへの依存関係が存在するとします。
次のようにして、DessertsFragment
がそのコンストラクタ内で DessertsRepository
インスタンスを必要とすることを定義できます。
Kotlin
class DessertsFragment(val dessertsRepository: DessertsRepository) : Fragment() { ... }
Java
public class DessertsFragment extends Fragment { private DessertsRepository dessertsRepository; public DessertsFragment(DessertsRepository dessertsRepository) { super(); this.dessertsRepository = dessertsRepository; } // Getter omitted. ... }
FragmentFactory
の簡単な実装の例を次に示します。
Kotlin
class MyFragmentFactory(val repository: DessertsRepository) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment = when (loadFragmentClass(classLoader, className)) { DessertsFragment::class.java -> DessertsFragment(repository) else -> super.instantiate(classLoader, className) } }
Java
public class MyFragmentFactory extends FragmentFactory { private DessertsRepository repository; public MyFragmentFactory(DessertsRepository repository) { super(); this.repository = repository; } @NonNull @Override public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) { Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className); if (fragmentClass == DessertsFragment.class) { return new DessertsFragment(repository); } else { return super.instantiate(classLoader, className); } } }
この例では、FragmentFactory
をサブクラス化し、instantiate()
メソッドをオーバーライドして、DessertsFragment
のカスタム フラグメント作成ロジックを提供しています。その他のフラグメント クラスは、super.instantiate()
を通して FragmentFactory
のデフォルトの動作で処理されます。
次に、FragmentManager
にプロパティを設定することで、アプリのフラグメントを作成するときに使用するファクトリとして MyFragmentFactory
を指定できます。フラグメントを再作成する際に MyFragmentFactory
が使用されるようにするには、アクティビティの super.onCreate()
の前にこのプロパティを設定する必要があります。
Kotlin
class MealActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { supportFragmentManager.fragmentFactory = MyFragmentFactory(DessertsRepository.getInstance()) super.onCreate(savedInstanceState) } }
Java
public class MealActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { DessertsRepository repository = DessertsRepository.getInstance(); getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository)); super.onCreate(savedInstanceState); } }
アクティビティ内で FragmentFactory
を設定すると、アクティビティのフラグメント階層全体でフラグメントの作成がオーバーライドされます。つまり、追加したすべての子フラグメントの childFragmentManager
は、下位レベルでオーバーライドされない限り、ここで設定されたカスタム フラグメント ファクトリを使用します。
FragmentFactory を使用してテストする
単一のアクティビティ アーキテクチャでは、FragmentScenario
クラスを使用して、フラグメントを隔離状態でテストします。アクティビティのカスタム onCreate
ロジックを利用できないため、代わりに FragmentFactory
を引数としてフラグメント テストに渡します。次の例をご覧ください。
// Inside your test val dessertRepository = mock(DessertsRepository::class.java) launchFragment<DessertsFragment>(factory = MyFragmentFactory(dessertRepository)).onFragment { // Test Fragment logic }
このテストプロセスの詳細な情報と例については、フラグメントをテストするをご覧ください。