Mit Fragmenten kommunizieren

Wenn Sie Fragmente wiederverwenden möchten, erstellen Sie sie als vollständig in sich geschlossene Komponenten, die ihr eigenes Layout und Verhalten definieren. Nachdem Sie diese wiederverwendbaren Fragmente definiert haben, können Sie sie einer Aktivität zuordnen und sie mit der Anwendungslogik verbinden, um die zusammengesetzte UI zu erstellen.

Um richtig auf Nutzerereignisse reagieren und Statusinformationen teilen zu können, benötigen Sie häufig Kommunikationskanäle zwischen einer Aktivität und ihren Fragmenten oder zwischen zwei oder mehr Fragmenten. Damit Fragmente eigenständig bleiben, dürfen sie nicht direkt mit anderen Fragmenten oder mit deren Hostaktivität kommunizieren.

Die Fragment-Bibliothek bietet zwei Kommunikationsoptionen: ein freigegebenes ViewModel und die Fragment Result API. Die empfohlene Option hängt vom Anwendungsfall ab. Verwenden Sie ViewModel, um nichtflüchtige Daten für benutzerdefinierte APIs freizugeben. Für ein einmaliges Ergebnis mit Daten, die in einem Bundle platziert werden können, verwende die Fragment Result API.

In den folgenden Abschnitten wird gezeigt, wie Sie ViewModel und die Fragment Result API für die Kommunikation zwischen Ihren Fragmenten und Aktivitäten verwenden.

Daten mit einem ViewModel freigeben

ViewModel ist eine ideale Wahl, wenn Sie Daten zwischen mehreren Fragmenten oder zwischen Fragmenten und deren Hostaktivität teilen müssen. ViewModel-Objekte speichern und verwalten UI-Daten. Weitere Informationen zu ViewModel finden Sie in der Übersicht zu ViewModel.

Daten für die Hostaktivität freigeben

In einigen Fällen müssen Sie möglicherweise Daten zwischen Fragmenten und deren Hostaktivität austauschen. Sie können beispielsweise eine globale UI-Komponente basierend auf einer Interaktion innerhalb eines Fragments umschalten.

Sehen Sie sich die folgenden ItemViewModel an:

Kotlin

class ItemViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

Java

public class ItemViewModel extends ViewModel {
    private final MutableLiveData<Item> selectedItem = new MutableLiveData<Item>();
    public void selectItem(Item item) {
        selectedItem.setValue(item);
    }
    public LiveData<Item> getSelectedItem() {
        return selectedItem;
    }
}

In diesem Beispiel sind die gespeicherten Daten in eine MutableLiveData-Klasse eingeschlossen. LiveData ist eine beobachtbare Dateninhaberklasse, die den Lebenszyklus berücksichtigt. Bei MutableLiveData kann der Wert geändert werden. Weitere Informationen zu LiveData finden Sie in der LiveData-Übersicht.

Sowohl das Fragment als auch seine Hostaktivität können eine freigegebene Instanz eines ViewModel mit Aktivitätsbereich abrufen. Dazu übergeben Sie die Aktivität an den Konstruktor ViewModelProvider. ViewModelProvider übernimmt die Instanziierung des ViewModel oder den Abruf, falls er bereits vorhanden ist. Beide Komponenten können diese Daten beobachten und ändern.

Kotlin

class MainActivity : AppCompatActivity() {
    // Using the viewModels() Kotlin property delegate from the activity-ktx
    // artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by viewModels()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.selectedItem.observe(this, Observer { item ->
            // Perform an action with the latest item data.
        })
    }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ItemViewModel by activityViewModels()

    // Called when the item is clicked.
    fun onItemClicked(item: Item) {
        // Set a new item.
        viewModel.selectItem(item)
    }
}

Java

public class MainActivity extends AppCompatActivity {
    private ItemViewModel viewModel;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(ItemViewModel.class);
        viewModel.getSelectedItem().observe(this, item -> {
            // Perform an action with the latest item data.
        });
    }
}

public class ListFragment extends Fragment {
    private ItemViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ItemViewModel.class);
        ...
        items.setOnClickListener(item -> {
            // Set a new item.
            viewModel.select(item);
        });
    }
}

Daten zwischen Fragmenten teilen

Zwei oder mehr Fragmente in derselben Aktivität müssen häufig miteinander kommunizieren. Stellen Sie sich zum Beispiel ein Fragment vor, das eine Liste anzeigt, und ein anderes, mit dem der Nutzer verschiedene Filter auf die Liste anwenden kann. Die Implementierung dieses Falls ist nicht einfach, ohne dass die Fragmente direkt kommunizieren. Dann sind sie aber nicht mehr eigenständig. Außerdem müssen beide Fragmente das Szenario bewältigen, in dem das andere Fragment noch nicht erstellt oder sichtbar ist.

Diese Fragmente können eine ViewModel gemeinsam mit ihrem Aktivitätsbereich verwenden, um diese Kommunikation zu verarbeiten. Wenn Sie das ViewModel auf diese Weise teilen, müssen die Fragmente nicht voneinander wissen und die Aktivität muss nichts tun, um die Kommunikation zu erleichtern.

Das folgende Beispiel zeigt, wie zwei Fragmente ein freigegebenes ViewModel für die Kommunikation verwenden können:

Kotlin

class ListViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    private val originalList: LiveData<List<Item>>() = ...
    val filteredList: LiveData<List<Item>> = ...

    fun addFilter(filter: Filter) { ... }

    fun removeFilter(filter: Filter) { ... }
}

class ListFragment : Fragment() {
    // Using the activityViewModels() Kotlin property delegate from the
    // fragment-ktx artifact to retrieve the ViewModel in the activity scope.
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class FilterFragment : Fragment() {
    private val viewModel: ListViewModel by activityViewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            // Update the selected filters UI.
        }
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)

    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

Java

public class ListViewModel extends ViewModel {
    private final MutableLiveData<Set<Filter>> filters = new MutableLiveData<>();

    private final LiveData<List<Item>> originalList = ...;
    private final LiveData<List<Item>> filteredList = ...;

    public LiveData<List<Item>> getFilteredList() {
        return filteredList;
    }

    public LiveData<Set<Filter>> getFilters() {
        return filters;
    }

    public void addFilter(Filter filter) { ... }

    public void removeFilter(Filter filter) { ... }
}

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        });
    }
}

public class FilterFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireActivity()).get(ListViewModel.class);
        viewModel.getFilters().observe(getViewLifecycleOwner(), set -> {
            // Update the selected filters UI.
        });
    }

    public void onFilterSelected(Filter filter) {
        viewModel.addFilter(filter);
    }

    public void onFilterDeselected(Filter filter) {
        viewModel.removeFilter(filter);
    }
}

Beide Fragmente verwenden ihre Hostaktivität als Bereich für ViewModelProvider. Da die Fragmente denselben Bereich verwenden, erhalten sie dieselbe Instanz von ViewModel, sodass sie hin- und her kommunizieren können.

Daten zwischen einem übergeordneten und einem untergeordneten Fragment teilen

Wenn Sie mit untergeordneten Fragmenten arbeiten, müssen das übergeordnete Fragment und seine untergeordneten Fragmente möglicherweise Daten untereinander teilen. Wenn Sie Daten zwischen diesen Fragmenten teilen möchten, verwenden Sie das übergeordnete Fragment als Bereich ViewModel, wie im folgenden Beispiel gezeigt:

Kotlin

class ListFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel.
    private val viewModel: ListViewModel by viewModels()
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // Update the list UI.
        }
    }
}

class ChildFragment: Fragment() {
    // Using the viewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the parent fragment's scope
    private val viewModel: ListViewModel by viewModels({requireParentFragment()})
    ...
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(this).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

public class ChildFragment extends Fragment {
    private ListViewModel viewModel;
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        viewModel = new ViewModelProvider(requireParentFragment()).get(ListViewModel.class);
        ...
    }
}

„Scope a ViewModel“ dem Navigationsdiagramm zuordnen

Wenn Sie die Navigationsbibliothek verwenden, können Sie eine ViewModel auch auf den Lebenszyklus der NavBackStackEntry eines Ziels festlegen. Beispielsweise kann ViewModel auf NavBackStackEntry für ListFragment beschränkt werden:

Kotlin

class ListFragment: Fragment() {
    // Using the navGraphViewModels() Kotlin property delegate from the fragment-ktx
    // artifact to retrieve the ViewModel using the NavBackStackEntry scope.
    // R.id.list_fragment == the destination id of the ListFragment destination
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            // Update the list UI.
        }
    }
}

Java

public class ListFragment extends Fragment {
    private ListViewModel viewModel;

    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.list_fragment)

        viewModel = new ViewModelProvider(backStackEntry).get(ListViewModel.class);
        viewModel.getFilteredList().observe(getViewLifecycleOwner(), list -> {
            // Update the list UI.
        }
    }
}

Weitere Informationen dazu, wie Sie ein ViewModel einer NavBackStackEntry zuordnen, finden Sie unter Programmatisch mit der Navigationskomponente interagieren.

Ergebnisse mit der Fragment Result API abrufen

In manchen Fällen möchten Sie vielleicht einen Einmalwert zwischen zwei Fragmenten oder zwischen einem Fragment und seiner Hostaktivität übergeben. Angenommen, Sie haben ein Fragment, das QR-Codes liest und die Daten an ein vorheriges Fragment zurückgibt.

In Fragment Version 1.3.0 und höher implementiert jedes FragmentManager-Objekt FragmentResultOwner. Das bedeutet, dass ein FragmentManager als zentraler Speicher für Fragmentergebnisse dienen kann. Durch diese Änderung können Komponenten miteinander kommunizieren, indem sie Fragmentergebnisse festlegen und diese Ergebnisse überwachen, ohne dass diese Komponenten direkte Verweise aufeinander haben müssen.

Übergabeergebnisse zwischen Fragmenten

Um Daten von Fragment B an Fragment A zurückzugeben, legen Sie zuerst einen Ergebnis-Listener auf Fragment A fest, dem Fragment, das das Ergebnis empfängt. Rufen Sie setFragmentResultListener() für FragmentManager von Fragment A auf, wie im folgenden Beispiel gezeigt:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported.
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
            // We use a String here, but any type that can be put in a Bundle is supported.
            String result = bundle.getString("bundleKey");
            // Do something with the result.
        }
    });
}
Fragment b sendet mithilfe eines FragmentManager Daten zum Fragmentieren von .
Abbildung 1: Fragment B sendet Daten mithilfe eines FragmentManager an Fragment A.

Legen Sie in Fragment B, dem Fragment, das das Ergebnis erzeugt, das Ergebnis auf dieselbe FragmentManager fest. Dazu verwenden Sie dieselbe requestKey. Dazu können Sie die setFragmentResult() API verwenden:

Kotlin

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Fragment A empfängt dann das Ergebnis und führt den Listener-Callback aus, sobald das Fragment den Status STARTED hat.

Sie können für einen bestimmten Schlüssel nur einen Listener und ein Ergebnis angeben. Wenn Sie setFragmentResult() mehrmals für denselben Schlüssel aufrufen und der Listener nicht STARTED ist, ersetzt das System alle ausstehenden Ergebnisse durch das aktualisierte Ergebnis.

Wenn Sie ein Ergebnis ohne entsprechenden Listener für den Empfang festlegen, wird das Ergebnis im FragmentManager gespeichert, bis Sie einen Listener mit demselben Schlüssel festlegen. Sobald ein Listener ein Ergebnis erhält und den Callback onFragmentResult() auslöst, wird das Ergebnis gelöscht. Dieses Verhalten hat zwei wesentliche Auswirkungen:

  • Fragmente im Back-Stack erhalten erst Ergebnisse, wenn sie per Pop-up geöffnet wurden und den Status STARTED haben.
  • Wenn ein Fragment, das auf ein Ergebnis wartet, STARTED ist, wenn das Ergebnis festgelegt wird, wird der Callback des Listeners sofort ausgelöst.

Fragmentergebnisse testen

Verwenden Sie FragmentScenario, um Aufrufe von setFragmentResult() und setFragmentResultListener() zu testen. Erstellen Sie mit launchFragmentInContainer oder launchFragment ein Szenario für das zu testende Fragment und rufen Sie dann die Methode, die nicht getestet wird, manuell auf.

Um setFragmentResultListener() zu testen, erstellen Sie ein Szenario mit dem Fragment, das setFragmentResultListener() aufruft. Rufen Sie als Nächstes direkt setFragmentResult() auf und prüfen Sie das Ergebnis:

@Test
fun testFragmentResultListener() {
    val scenario = launchFragmentInContainer<ResultListenerFragment>()
    scenario.onFragment { fragment ->
        val expectedResult = "result"
        fragment.parentFragmentManager.setFragmentResult("requestKey", bundleOf("bundleKey" to expectedResult))
        assertThat(fragment.result).isEqualTo(expectedResult)
    }
}

class ResultListenerFragment : Fragment() {
    var result : String? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Use the Kotlin extension in the fragment-ktx artifact.
        setFragmentResultListener("requestKey") { requestKey, bundle ->
            result = bundle.getString("bundleKey")
        }
    }
}

Um setFragmentResult() zu testen, erstellen Sie ein Szenario mit dem Fragment, das setFragmentResult() aufruft. Rufen Sie als Nächstes direkt setFragmentResultListener() auf und prüfen Sie das Ergebnis:

@Test
fun testFragmentResult() {
    val scenario = launchFragmentInContainer<ResultFragment>()
    lateinit var actualResult: String?
    scenario.onFragment { fragment ->
        fragment.parentFragmentManager
                .setFragmentResultListener("requestKey") { requestKey, bundle ->
            actualResult = bundle.getString("bundleKey")
        }
    }
    onView(withId(R.id.result_button)).perform(click())
    assertThat(actualResult).isEqualTo("result")
}

class ResultFragment : Fragment(R.layout.fragment_result) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        view.findViewById(R.id.result_button).setOnClickListener {
            val result = "result"
            // Use the Kotlin extension in the fragment-ktx artifact.
            setFragmentResult("requestKey", bundleOf("bundleKey" to result))
        }
    }
}

Übergabeergebnisse zwischen übergeordneten und untergeordneten Fragmenten

Um ein Ergebnis von einem untergeordneten Fragment an ein übergeordnetes Element zu übergeben, verwenden Sie beim Aufrufen von setFragmentResultListener() getChildFragmentManager() aus dem übergeordneten Fragment anstelle von getParentFragmentManager().

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Set the listener on the child fragmentManager.
    childFragmentManager.setFragmentResultListener("requestKey") { key, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Set the listener on the child fragmentManager.
    getChildFragmentManager()
        .setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
}
Ein untergeordnetes Fragment kann mit FragmentManager ein Ergebnis an sein übergeordnetes Element senden.
Abbildung 2: Ein untergeordnetes Fragment kann FragmentManager verwenden, um ein Ergebnis an sein übergeordnetes Element zu senden.

Das untergeordnete Fragment legt das Ergebnis anhand seines FragmentManager fest. Das übergeordnete Element erhält dann das Ergebnis, sobald das Fragment STARTED ist:

Kotlin

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Java

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        // The child fragment needs to still set the result on its parent fragment manager.
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});

Ergebnisse in der Hostaktivität erhalten

Um ein Fragmentergebnis in der Hostaktivität zu erhalten, legen Sie im Fragmentmanager mit getSupportFragmentManager() einen Ergebnis-Listener fest.

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            // We use a String here, but any type that can be put in a Bundle is supported.
            val result = bundle.getString("bundleKey")
            // Do something with the result.
        }
    }
}

Java

class MainActivity extends AppCompatActivity {
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getSupportFragmentManager().setFragmentResultListener("requestKey", this, new FragmentResultListener() {
            @Override
            public void onFragmentResult(@NonNull String requestKey, @NonNull Bundle bundle) {
                // We use a String here, but any type that can be put in a Bundle is supported.
                String result = bundle.getString("bundleKey");
                // Do something with the result.
            }
        });
    }
}