フラグメント マネージャー

FragmentManager は、アプリのフラグメントに対するアクション(フラグメントの追加、削除、置換、バックスタックへの追加など)を実行するためのクラスです。

Jetpack Navigation ライブラリを使用している場合は、このライブラリが FragmentManager を操作するので、FragmentManager を直接操作することはありません。とはいえ、フラグメントを使用するすべてのアプリは、なんらかのレベルで FragmentManager を使用します。したがって、その機能と仕組みを理解することは重要です。

このトピックでは、FragmentManager へのアクセス方法、アクティビティとフラグメントに関する FragmentManager の役割、FragmentManager によるバックスタックの管理、データと依存関係をフラグメントに渡す方法について説明します。

FragmentManager にアクセスする

アクティビティ内でアクセスする

すべての FragmentActivity とそのサブクラス(AppCompatActivity など)は、getSupportFragmentManager() メソッドを介して FragmentManager にアクセスできます。

フラグメント内でアクセスする

フラグメントは、1 つ以上の子フラグメントをホストすることもできます。フラグメント内で、getChildFragmentManager() を介してフラグメントの子を管理する FragmentManager への参照を取得できます。そのホストである FragmentManager にアクセスする必要がある場合は、getParentFragmentManager() を使用できます。

フラグメント、そのホスト、およびそれぞれに関連付けられた FragmentManager インスタンスの関係を確認するため、いくつかの例を見てみましょう。

フラグメントとそのホスト アクティビティとの関係を示す 2 つの UI レイアウトの例
図 1. フラグメントとそのホスト アクティビティとの関係を示す 2 つの UI レイアウトの例

図 1 に 2 つの例を示します。それぞれに 1 つのアクティビティ ホストがあります。どちらの例のホスト アクティビティも、トップレベル ナビゲーションを BottomNavigationView としてユーザーに表示します。このビューは、アプリ内のさまざまな画面でホスト フラグメントをスワップアウトします。各画面は別個のフラグメントとして実装されます。

例 1 のホスト フラグメントは、分割ビュー画面を構成する 2 つの子フラグメントをホストします。例 2 のホスト フラグメントは、スワイプビューの表示フラグメントを構成する単一の子フラグメントをホストします。

この設定では、各ホストに、その子フラグメントを管理する FragmentManager が関連付けられていると考えることができます。これは、supportFragmentManagerparentFragmentManagerchildFragmentManager の間のプロパティ マッピングとともに、図 2 に示されています。

各ホストに、その子フラグメントを管理する独自の FragmentManager が関連付けられている
図 2. 各ホストに、その子フラグメントを管理する独自の FragmentManager が関連付けられている

参照すべき適切な FragmentManager プロパティは、呼び出し部分がフラグメント階層内のどこにあるか、アクセスしようとしているフラグメント マネージャーがどれかによって異なります。

FragmentManager への参照を取得したら、それを使用して、ユーザーに表示するフラグメントを操作できます。

子フラグメント

一般的に、アプリはアプリ プロジェクト内の 1 つまたは少数のアクティビティで構成され、各アクティビティは関連する画面のグループを表します。アクティビティは、トップレベル ナビゲーションを配置するポイントと、フラグメント間の ViewModels およびその他のビュー状態のスコープを設定する場所を提供できます。アプリ内の各デスティネーションをフラグメントで表す必要があります。

分割ビューやダッシュボードに複数のフラグメントを同時に表示するには、デスティネーション フラグメントとその子フラグメント マネージャーによって管理される子フラグメントを使用する必要があります。

子フラグメントのその他のユースケースには、次のようなものがあります。

  • 画面スライド。親フラグメント内で 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 つだけをプライマリ ナビゲーション フラグメントに設定できます。あるフラグメントをプライマリ ナビゲーション フラグメントとして設定すると、前のフラグメントの指定は取り消されます。前述の例では、詳細フラグメントをプライマリ ナビゲーション フラグメントとして設定すると、メイン フラグメントの指定が取り消されます。

複数のバックスタックをサポートする

場合によっては、アプリは複数のバックスタックをサポートする必要があります。よくあるのが、アプリが下部のナビゲーション バーを使用する場合です。FragmentManagersaveBackStack() メソッドと restoreBackStack() メソッドを使用することで、複数のバックスタックをサポートできます。つまり、バックスタックを保存して別のバックスタックを復元することにより、バックスタックの切り替えが可能になります。

saveBackStack() は、オプションの name パラメータを使用して popBackStack() を呼び出す場合と同様に動作します。指定したトランザクションと、それ以降のスタック上のトランザクションがすべてポップされます。違いは、saveBackStack() ではポップされたトランザクション内のフラグメントの状態がすべて保存される点です。

たとえば、次のように addToBackStack() を使用して FragmentTransaction をコミットすることにより、あらかじめフラグメントをバックスタックに追加しておいたとします。

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();

その場合、次のように saveState() を呼び出すことで、このフラグメント トランザクションと 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
}

このテストプロセスの詳細な情報と例については、アプリのフラグメントをテストするをご覧ください。