Mit Fragmenten kommunizieren

Erstellen Sie Fragmente als vollständig eigenständige Komponenten, um sie wiederzuverwenden die ihr eigenes Layout und Verhalten definieren. Sobald Sie diese wiederverwendbaren Fragmenten, können Sie sie mit einer Aktivität verknüpfen mit der Anwendungslogik verknüpfen, um die gesamte zusammengesetzte UI zu realisieren.

Um richtig auf Nutzerereignisse zu reagieren und Statusinformationen zu teilen, brauchen Kommunikationskanäle zwischen einer Aktivität und ihrer oder zwischen zwei oder mehr Fragmenten. Damit Fragmente eigenständig bleiben, nicht Fragmente direkt mit anderen Fragmenten kommunizieren oder mit der Gastgeberaktivität.

Die Fragment-Bibliothek bietet zwei Kommunikationsoptionen: eine gemeinsam genutzte ViewModel und das Fragment Result API Die empfohlene Option hängt vom Anwendungsfall ab. Zum Teilen nichtflüchtige Daten mit benutzerdefinierten APIs verwenden, verwenden Sie ViewModel. Für ein einmaliges Ergebnis mit Daten, die in einem Bundle, verwenden Sie das Fragment Result API

In den folgenden Abschnitten wird gezeigt, wie Sie ViewModel und das Fragment verwenden. Result API zur Kommunikation zwischen Ihren Fragmenten und Aktivitäten.

Daten mit einem ViewModel teilen

ViewModel ist die ideale Wahl, wenn müssen Sie Daten zwischen oder zwischen Fragmenten und ihrer Hostaktivität. ViewModel-Objektspeicher und UI-Daten zu verwalten. Weitere Informationen zu ViewModel finden Sie unter ViewModel – Übersicht

Daten mit dem Organisator teilen

In einigen Fällen müssen Sie Daten zwischen Fragmenten und die Aktivitäten des Organisators. Sie können z. B. die globale Benutzeroberfläche Komponente basiert auf einer Interaktion innerhalb eines Fragments.

Sehen Sie sich die folgende 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 Klasse MutableLiveData. LiveData ist ein Lebenszyklus-orientierter Holder-Klasse für beobachtbare Daten. MutableLiveData lässt seinen Wert geändert. Weitere Informationen zu LiveData finden Sie unter LiveData-Übersicht

Sowohl das Fragment als auch seine Hostaktivität können eine freigegebene Instanz abrufen eines ViewModel mit Aktivitätsumfangs durch Übergeben der Aktivität in den ViewModelProvider -Konstruktor. Der ViewModelProvider Handle Instanziierung der ViewModel oder abrufen, falls sie bereits vorhanden sind. Beide Komponenten können ändern können.

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 oft sich gegenseitig helfen. Stellen Sie sich z. B. ein Fragment vor, das eine Liste und eine weitere, mit der der Nutzer verschiedene Filter auf die Liste anwenden kann. Die Implementierung dieses Falls ist ohne die Fragmente nicht trivial direkt kommunizieren, aber dann sind sie nicht mehr als eigenständiges Produkt. Außerdem müssen beide Fragmente das Szenario verarbeiten können. das andere Fragment noch nicht erstellt oder sichtbar ist.

Diese Fragmente können eine ViewModel unter Verwendung ihres Aktivitätsumfangs teilen um diese Kommunikation abzuwickeln. Wenn Sie ViewModel auf diese Weise teilen, müssen die Fragmente nicht voneinander wissen, und die Aktivität Sie müssen nichts unternehmen, um die Kommunikation zu erleichtern.

Das folgende Beispiel zeigt, wie zwei Fragmente ein gemeinsam genutztes ViewModel für die Kommunikation:

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 den ViewModelProvider Da die Fragmente denselben Umfang verwenden, erhalten sie dieselbe Instanz von ViewModel, wodurch sie miteinander kommunizieren können. hin und her.

Daten zwischen über- und untergeordnetem Fragment teilen

Bei der Arbeit mit untergeordneten Fragmenten werden das übergeordnete und dessen untergeordnete Fragment müssen Fragmente möglicherweise Daten miteinander teilen. Um Daten zwischen Verwenden Sie das übergeordnete Fragment als Bereich ViewModel, wie gezeigt. im folgenden Beispiel:

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);
        ...
    }
}

Bereich „ViewModel“ dem Navigationsdiagramm zuweisen

Wenn Sie die Navigationsbibliothek verwenden, haben Sie außerdem folgende Möglichkeiten: einen ViewModel auf den Lebenszyklus eines Ziels NavBackStackEntry Für Beispiel: ViewModel kann auf die NavBackStackEntry beschränkt werden. für ListFragment:

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 zum Zuordnen eines ViewModel- zu einem NavBackStackEntry-Element finden Sie unter Programmatisch mit der Navigationskomponente interagieren.

Ergebnisse mit der Fragment Result API abrufen

In einigen Fällen möchten Sie vielleicht einen einmaligen Wert zwischen zwei Fragmenten übergeben. oder zwischen einem Fragment und seiner Hostaktivität. Beispiel: Sie haben eine das QR-Codes liest und die Daten an ein vorheriges Fragment zurückübergibt.

Ab Fragment-Version 1.3.0 jeweils FragmentManager implements FragmentResultOwner Dies bedeutet, dass eine FragmentManager als zentraler Speicher für das Fragment fungieren kann Ergebnisse. Durch diese Änderung können Komponenten miteinander kommunizieren, Fragmentergebnisse festlegen und auf diese Ergebnisse warten, ohne dass diese Komponenten direkten Bezug aufeinander haben.

Ergebnisse zwischen Fragmenten übergeben

Zur Übergabe von Daten an Fragment A aus Fragment B legen Sie zunächst einen Ergebnis-Listener fest. zu Fragment A, dem Fragment, das das Ergebnis erhält. Anruf setFragmentResultListener() auf FragmentManager von Fragment A, 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.
        }
    });
}
<ph type="x-smartling-placeholder">
</ph> Fragment b sendet Daten mithilfe eines FragmentManagers an das Fragment a.
Abbildung 1: Fragment B sendet Daten mithilfe einer FragmentManager

Legen Sie in Fragment B, dem Fragment, das das Ergebnis hervorbringt, das Ergebnis fest. am selben FragmentManager mit demselben requestKey-Wert. Sie können Folgendes tun: Wenn Sie also die setFragmentResult() API:

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 STARTED

Sie können für einen bestimmten Schlüssel nur einen Listener und ein Ergebnis haben. Wenn Sie setFragmentResult() mehr als einmal für denselben Schlüssel und wenn der Listener nicht STARTED ist, ersetzt das System alle ausstehenden Ergebnisse durch Ihre aktualisierten Ergebnis.

Wenn Sie ein Ergebnis ohne entsprechenden Listener festlegen, Das Ergebnis wird im FragmentManager gespeichert, bis Sie einen Listener mit denselben Schlüssel ein. Sobald ein Listener ein Ergebnis empfängt und das Ereignis onFragmentResult()-Callback ist, wird das Ergebnis gelöscht. Dieses Verhalten hat zwei wesentliche Auswirkungen:

  • Fragmente auf dem Back Stack erhalten erst Ergebnisse, wenn sie und sind STARTED.
  • Wenn ein Fragment, das auf ein Ergebnis wartet, STARTED ist, wenn das Ergebnis festgelegt wird, wird der Callback des Listeners sofort ausgelöst.

Ergebnisse des Testfragments

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

Um setFragmentResultListener() zu testen, erstellen Sie ein Szenario mit der Fragment, das setFragmentResultListener() aufruft. Als Nächstes rufen Sie setFragmentResult() direkt auf und überprü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 die Aufruf von setFragmentResult(). Rufen Sie als Nächstes setFragmentResultListener() an und überprü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))
        }
    }
}

Ergebnisse zwischen über- und untergeordneten Fragmenten übergeben

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

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.
            }
        });
}
<ph type="x-smartling-placeholder">
</ph> Ein untergeordnetes Fragment kann mithilfe von FragmentManager ein Ergebnis senden.
            an übergeordnetes Element
Abbildung 2: Ein untergeordnetes Fragment kann FragmentManager, um ein Ergebnis an das übergeordnete Element zu senden.

Das untergeordnete Fragment legt das Ergebnis auf seinem 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

Legen Sie einen Ergebnis-Listener fest, um ein Fragmentergebnis in der Hostaktivität zu erhalten im Fragmentmanager mithilfe getSupportFragmentManager()

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.
            }
        });
    }
}